Re: [RFC PATCH v3 12/35] drivers/clk/renesas: clk-sh7750.c SH7750/7751 CPG driver.

Geert Uytterhoeven <[email protected]>
Newsgroups gmane.linux.kernel.clk,gmane.linux.ports.sh.devel
Message-ID <CAMuHMdWzvgwxtQB1WgW=On7gPf66PR1DGtN2dVYxEmsAqTAYWg@mail.gmail.com>
Hi Sato-san,

On Sat, Oct 14, 2023 at 4:54 PM Yoshinori Sato
<[email protected]> wrote:
> This driver supported SH7750/7751 PLL/Divider clock and module stop.
>
> Signed-off-by: Yoshinori Sato <[email protected]>

Thanks for your patch!

> ---
>  drivers/clk/renesas/Kconfig      |  21 +-
>  drivers/clk/renesas/Makefile     |   1 +
>  drivers/clk/renesas/clk-sh7750.c | 787 +++++++++++++++++++++++++++++++
>  3 files changed, 807 insertions(+), 2 deletions(-)
>  create mode 100644 drivers/clk/renesas/clk-sh7750.c
>
> diff --git a/drivers/clk/renesas/Kconfig b/drivers/clk/renesas/Kconfig
> index 37632a0659d8..c52c038ce715 100644
> --- a/drivers/clk/renesas/Kconfig
> +++ b/drivers/clk/renesas/Kconfig
> @@ -1,8 +1,8 @@
>  # SPDX-License-Identifier: GPL-2.0
>
>  config CLK_RENESAS
> -       bool "Renesas SoC clock support" if COMPILE_TEST && !ARCH_RENESAS
> -       default y if ARCH_RENESAS
> +       bool "Renesas SoC clock support" if COMPILE_TEST && !ARCH_RENESAS && SUPERH

"!SUPERH", so the symbol becomes invisible but enabled on SH.

> +       default y if ARCH_RENESAS || SUPERH
>         select CLK_EMEV2 if ARCH_EMEV2
>         select CLK_RZA1 if ARCH_R7S72100
>         select CLK_R7S9210 if ARCH_R7S9210
> @@ -39,6 +39,11 @@ config CLK_RENESAS
>         select CLK_R9A07G054 if ARCH_R9A07G054
>         select CLK_R9A09G011 if ARCH_R9A09G011
>         select CLK_SH73A0 if ARCH_SH73A0
> +       select CLK_SH7750 if CPU_SUBTYPE_SH7750
> +       select CLK_SH7750 if CPU_SUBTYPE_SH7750S
> +       select CLK_SH7750 if CPU_SUBTYPE_SH7750R
> +       select CLK_SH7750 if CPU_SUBTYPE_SH7751
> +       select CLK_SH7750 if CPU_SUBTYPE_SH7751R

Please combine all of the above into a single statement using "||",
like is done for other shared drivers.

>
>  if CLK_RENESAS
>
> @@ -188,6 +193,10 @@ config CLK_SH73A0
>         select CLK_RENESAS_CPG_MSTP
>         select CLK_RENESAS_DIV6
>
> +config CLK_SH7750
> +       bool "SH7750/7751 family clock support" if COMPILE_TEST
> +       help
> +         This is a driver for SH7750 / SH7751 CPG.
>
>  # Family
>  config CLK_RCAR_CPG_LIB
> @@ -218,6 +227,14 @@ config CLK_RZG2L
>         bool "Renesas RZ/{G2L,G2UL,V2L} family clock support" if COMPILE_TEST
>         select RESET_CONTROLLER
>
> +config CLK_SH7750
> +       bool "Renesas SH7750/7751 family clock support"

"if COMPILE_TEST", so the symbol becomes invisible when auto-enabled.

> +       depends on CPU_SUBTYPE_SH7750 || CPU_SUBTYPE_SH7750S || \
> +                  CPU_SUBTYPE_SH7750R || \
> +                  CPU_SUBTYPE_SH7751 || CPU_SUBTYPE_SH7751R || COMPILE_TEST

Please drop this dependency, as it is not needed.

> --- /dev/null
> +++ b/drivers/clk/renesas/clk-sh7750.c

> +static void sbcr_sw(struct clk_hw *hw, bool on)
> +{
> +       struct clk_gate *gate = to_clk_gate(hw);
> +       u8 msk = BIT(gate->bit_idx);
> +       u8 val;
> +
> +       val = readb(gate->reg);
> +       if (on)
> +               val &= ~msk;
> +       else
> +               val |= msk;
> +       writeb(val, gate->reg);

RMW (Read-Modify-Write) needs locking.

> +}
> +
> +static int sbcr_enable(struct clk_hw *hw)
> +{
> +       sbcr_sw(hw, true);
> +       return 0;
> +}
> +
> +static void sbcr_disable(struct clk_hw *hw)
> +{
> +       sbcr_sw(hw, false);
> +}
> +
> +static int sbcr_is_enabled(struct clk_hw *hw)
> +{
> +       u8 val;
> +       struct clk_gate *gate = to_clk_gate(hw);
> +
> +       val = readb(gate->reg);
> +       val &= 1 << gate->bit_idx;
> +       return val != 0;
> +}
> +
> +static const struct clk_ops gate_stbcr_ops = {
> +       .enable = sbcr_enable,
> +       .disable = sbcr_disable,
> +       .is_enabled = sbcr_is_enabled,
> +};

These are almost identical to clk_gate_ops, except that they use
8-bit instead of 32-bit register accesses.
Perhaps you can extend the standard gate clock in drivers/clk/clk-gate.c
instead, using a new CLK_GATE_* flag?

> +
> +static void clkstp00_sw(struct clk_hw *hw, bool on)
> +{
> +       u32 val;
> +       struct clk_gate *gate = to_clk_gate(hw);
> +
> +       val = BIT(gate->bit_idx);
> +       if (on)
> +               writel(val, gate->reg + CLKSTPCLR00);
> +       else
> +               writel(val, gate->reg);
> +}
> +
> +static int clkstp00_enable(struct clk_hw *hw)
> +{
> +       clkstp00_sw(hw, true);
> +       return 0;
> +}
> +
> +static void clkstp00_disable(struct clk_hw *hw)
> +{
> +       clkstp00_sw(hw, false);
> +}
> +
> +static int clkstp00_is_enabled(struct clk_hw *hw)
> +{
> +       u8 val;
> +       struct clk_gate *gate = to_clk_gate(hw);
> +
> +       val = readb(gate->reg);

readl()?

> +       val &= 1 << gate->bit_idx;
> +       return val == 0;
> +}
> +
> +static const struct clk_ops gate_clkstp00_ops = {
> +       .enable = clkstp00_enable,
> +       .disable = clkstp00_disable,
> +       .is_enabled = clkstp00_is_enabled,
> +};

These look identical to clk_gate_ops, so you can just use
clk_register_gate() instead?

> +static int register_div(struct device_node *node, struct cpg_priv *cpg)
> +{
> +       static const char * const divout[] = {
> +               "fck", "bck", "ick",
> +       };
> +       static const char * const mstpout[] = {
> +               "sci_clk", "rtc_clk", "tmu012_clk",     /* STBCR */
> +               "scif_clk", "dmac_clk",                 /* STBCR */
> +               "ubc_clk", "sq_clk",                    /* STBCR2 */

FTR, qemu does not support STBCR and STBCR2 yet, causing
"qemu-system-sh4 -M r2d" to crash.  I have posted a patch to fix that:
"[PATCH RFC] hw/sh4/sh7750: Add STBCR/STBCR2 register support"
https://lore.kernel.org/all/[email protected]

> +               "intc_clk", "tmu34_clk", "pcic_clk",    /* CLKSTP00 */
> +       };

The order of the entries in these two arrays is tied intimately to
the values in include/dt-bindings/clock/sh7750.h.  Hence I think it
would be good to reflect that in the data and code below, by using
the SH7750_CPG_* identifier.

> +       struct sh_clk_reg_arg args[ARRAY_SIZE(divout) + ARRAY_SIZE(mstpout)];
> +       unsigned int i, j;
> +       int ret;
> +
> +       for (i = 0; i < ARRAY_SIZE(divout); i++) {
> +               args[i].type = CLK_DIV;
> +               args[i].name  = divout[i];
> +               args[i].parent = PLLOUT;
> +               args[i].reg = cpg->frqcr;
> +               args[i].ops = &sh_clk_divider_ops;
> +               args[i].div.shift = i * 3;
> +               args[i].div.width = 3;
> +               args[i].div.table = (i == 0) ? pdiv_table : div_table;
> +       }
> +       for (i = 0, j = ARRAY_SIZE(divout); j < ARRAY_SIZE(args); i++, j++) {
> +               args[j].type = CLK_GATE;
> +               args[j].name  = mstpout[i];
> +               args[j].parent = divout[0];
> +               switch (i) {
> +               case 0 ... 4:
> +                       args[j].reg = cpg->frqcr + STBCR;
> +                       args[j].gate.bit_idx = i;
> +                       args[j].ops = &gate_stbcr_ops;
> +                       break;
> +               case 5 ... 6:
> +                       args[j].reg = cpg->frqcr + STBCR2;
> +                       args[j].gate.bit_idx = i - 5;
> +                       args[j].ops = &gate_stbcr_ops;
> +                       break;
> +               case 7 ... 9:
> +                       args[j].reg = cpg->clkstp00;
> +                       args[j].gate.bit_idx = i - 7;
> +                       args[j].ops = &gate_clkstp00_ops;
> +                       break;
> +               }
> +       }
> +
> +       ret = _clk_register(node, ARRAY_SIZE(args), args,
> +                           &cpg->clklock, &cpg->hw);
> +       if (ret < 0)
> +               pr_err("%pOF: failed to register clock (%d)\n",
> +                      node, ret);
> +       return ret;
> +}
> +
> +static struct cpg_priv *sh7750_cpg_setup(struct device_node *node)
> +{
> +       unsigned int num_parents;
> +       u32 mode;
> +       struct cpg_priv *cpg;
> +       int ret = 0;
> +
> +       num_parents = of_clk_get_parent_count(node);
> +       if (num_parents < 1) {
> +               pr_err("%s: no parent found", node->name);
> +               return ERR_PTR(-ENODEV);
> +       }
> +
> +       of_property_read_u32_index(node, "renesas,mode", 0, &mode);
> +       if (mode >= 7) {
> +               pr_err("%s: Invalid clock mode setting (%u)\n",
> +                      node->name, mode);
> +               return ERR_PTR(-EINVAL);
> +       }
> +
> +       cpg = kzalloc(sizeof(struct cpg_priv), GFP_KERNEL);
> +       if (!cpg) {
> +               pr_err("%pOF: failed to alloc memory", node);

No need to print an error message on allocation failure, as the
core memory allocation code already took care of that.

> +               return ERR_PTR(-ENOMEM);
> +       }
> +
> +       cpg->frqcr = of_iomap(node, 0);
> +       if (cpg->frqcr == NULL) {

!cpg->frqcr

> +               pr_err("%pOF: failed to map divide register", node);
> +               ret = -ENODEV;
> +               goto cpg_free;
> +       }
> +
> +       cpg->clkstp00 = of_iomap(node, 1);
> +       if (cpg->clkstp00 == NULL) {

!cpg->clkstp00

> +               pr_err("%pOF: failed to map clkstp00 register", node);
> +               ret = -ENODEV;
> +               goto unmap_frqcr;
> +       }
> +       cpg->mode = mode;
> +
> +       ret = register_pll(node, cpg);
> +       if (ret < 0)
> +               goto unmap_clkstp00;
> +
> +       ret = register_div(node, cpg);
> +       if (ret < 0)
> +               goto unmap_clkstp00;
> +
> +       return cpg;
> +
> +unmap_clkstp00:
> +       iounmap(cpg->clkstp00);
> +unmap_frqcr:
> +       iounmap(cpg->frqcr);
> +cpg_free:
> +       kfree(cpg);
> +       return ERR_PTR(ret);
> +}
> +
> +static void __init sh7750_cpg_init(struct device_node *node)
> +{
> +       cpg_data = sh7750_cpg_setup(node);
> +       if (IS_ERR(cpg_data))
> +               cpg_data = NULL;
> +       else
> +               cpg_data->have_div1 = !of_device_is_compatible(node, "renesas,sh7750r");

Please no explicit of_device_is_compatible() checks in code that is
called through a compatible-based match.

You can differentiate using multiple CLK_OF_DECLARE_DRIVER() stances,\
each with a different init function.

> +}
> +
> +CLK_OF_DECLARE_DRIVER(sh7750_cpg, "renesas,sh7750-cpg",
> +                     sh7750_cpg_init);
> +
> +static int sh7750_cpg_probe(struct platform_device *pdev)
> +{
> +       if (cpg_data)
> +               return 0;
> +       cpg_data = sh7750_cpg_setup(pdev->dev.of_node);
> +       if (IS_ERR(cpg_data))
> +               return PTR_ERR(cpg_data);
> +       cpg_data->have_div1 = (of_device_get_match_data(&pdev->dev) != NULL);
> +       return 0;
> +}
> +
> +static const struct of_device_id sh7750_cpg_of_match[] = {
> +       { .compatible = "renesas,sh7750-cpg",
> +         .data = (void *)1 },
> +       { .compatible = "renesas,sh7750s-cpg",
> +         .data = (void *)1 },
> +       { .compatible = "renesas,sh7750r-cpg",
> +         .data = 0 },
> +       { .compatible = "renesas,sh7751-cpg",
> +         .data = (void *)1 },
> +       { .compatible = "renesas,sh7751r-cpg",
> +         .data = 0 },
> +       { }
> +};
> +
> +static struct platform_driver sh7750_cpg_driver = {
> +       .probe  = sh7750_cpg_probe,
> +       .driver = {
> +               .name = "sh7750-cpg",
> +               .of_match_table = sh7750_cpg_of_match,
> +       },
> +};
> +builtin_platform_driver(sh7750_cpg_driver);

As the clock driver is always probed early through
CLK_OF_DECLARE_DRIVER, what is the use of the extra platform driver?

Gr{oetje,eeting}s,

                        Geert

--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- [email protected]

In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
                                -- Linus Torvalds
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.