Re: [PATCH 05/11] clk: eyeq: add driver

From: Théo Lebrun
Date: Thu Apr 11 2024 - 06:46:22 EST


Hello,

On Thu Apr 11, 2024 at 5:22 AM CEST, Stephen Boyd wrote:
> Quoting Théo Lebrun (2024-04-10 10:12:34)
> > diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
> > index 50af5fc7f570..1eb6e70977a3 100644
> > --- a/drivers/clk/Kconfig
> > +++ b/drivers/clk/Kconfig
> > @@ -218,6 +218,17 @@ config COMMON_CLK_EN7523
> > This driver provides the fixed clocks and gates present on Airoha
> > ARM silicon.
> >
> > +config COMMON_CLK_EYEQ
> > + bool "Clock driver for the Mobileye EyeQ platform"
> > + depends on OF || COMPILE_TEST
>
> The OF build dependency looks useless as we have the MACH_ dependency
> below.

Indeed. I thought explicit dependency could be useful. Will remove.

> > + depends on MACH_EYEQ5 || MACH_EYEQ6H || COMPILE_TEST
> > + default MACH_EYEQ5 || MACH_EYEQ6H
> > + help
> > + This driver provides clocks found on Mobileye EyeQ5, EyeQ6L and Eye6H
> > + SoCs. Controllers live in shared register regions called OLB. Driver
> > + provides read-only PLLs, derived from the main crystal clock (which
> > + must be constant). It also exposes some divider clocks.
> > +
> > config COMMON_CLK_FSL_FLEXSPI
> > tristate "Clock driver for FlexSPI on Layerscape SoCs"
> > depends on ARCH_LAYERSCAPE || COMPILE_TEST
> > diff --git a/drivers/clk/clk-eyeq.c b/drivers/clk/clk-eyeq.c
> > new file mode 100644
> > index 000000000000..bb2535010ae6
> > --- /dev/null
> > +++ b/drivers/clk/clk-eyeq.c
> > @@ -0,0 +1,644 @@
> > +// SPDX-License-Identifier: GPL-2.0-only
> > +/*
> > + * PLL clock driver for the Mobileye EyeQ5, EyeQ6L and EyeQ6H platforms.
> > + *
> > + * This controller handles read-only PLLs, all derived from the same main
> > + * crystal clock. It also exposes divider clocks, those are children to PLLs.
> > + * Parent clock is expected to be constant. This driver's registers live in
> > + * a shared region called OLB. Some PLLs are initialised early by of_clk_init().
>
> Is OLB a different DT node? It sounds like maybe this is trying to jam a
> driver into DT when the OLB node should be a #clock-cells node.

Yes OLB is a different DT node. It looks like on EyeQ5:

olb: system-controller@e00000 {
compatible = "mobileye,eyeq5-olb", "syscon", "simple-mfd";
reg = <0 0xe00000 0x0 0x400>;
ranges = <0x0 0x0 0xe00000 0x400>;
#address-cells = <1>;
#size-cells = <1>;

reset: reset-controller@e00000 {
compatible = "mobileye,eyeq5-reset";
reg = <0x000 0x0c>, <0x200 0x34>, <0x120 0x04>;
reg-names = "d0", "d1", "d2";
#reset-cells = <2>;
};

clocks: clock-controller@e0002c {
compatible = "mobileye,eyeq5-clk";
reg = <0x02c 0x50>, <0x11c 0x04>;
reg-names = "plls", "ospi";
#clock-cells = <1>;
clocks = <&xtal>;
clock-names = "ref";
};

pinctrl: pinctrl@e000b0 {
compatible = "mobileye,eyeq5-pinctrl";
reg = <0x0b0 0x30>;
};
};

Keep in mind OLB is a complex beast. On EyeQ5, it hosts something like
150 registers, describing 20ish various hardware features. We have to
expose registers to drivers for one-off reads/writes. One example found
upstream: I2C speed mode register. Others will be Ethernet, eMMC DMA
config, etc. A syscon makes sense.

I2C looks like like this for example, look at mobileye,olb.

i2c@300000 {
compatible = "mobileye,eyeq5-i2c", "arm,primecell";
reg = <0x300000 0x1000>;
interrupt-parent = <&gic>;
interrupts = <GIC_SHARED 1 IRQ_TYPE_LEVEL_HIGH>;
clock-frequency = <400000>;
#address-cells = <1>;
#size-cells = <0>;
clocks = <&i2c_ser_clk>, <&i2c_clk>;
clock-names = "i2cclk", "apb_pclk";
mobileye,olb = <&olb 0>;
};

See commits 7d4c57abb928 and 1b9a8e8af0d9:
i2c: nomadik: support Mobileye EyeQ5 I2C controller
dt-bindings: i2c: nomadik: add mobileye,eyeq5-i2c bindings and example

> > +
> > +static int eqc_probe(struct platform_device *pdev)
> > +{
> > + struct device *dev = &pdev->dev;
> > + void __iomem *div_resources[EQC_MAX_DIV_COUNT];
> > + struct device_node *np = dev->of_node;
> > + const struct eqc_match_data *data;
> > + struct eqc_priv *priv = NULL;
> > + struct clk_hw *hw;
> > + unsigned int i;
> > +
> > + data = device_get_match_data(dev);
> > + if (!data)
> > + return -ENODEV;
> > +
> > + if (data->early_pll_count) {
> > + /* Device got inited early. Retrieve clock provider from list. */
> > + struct eqc_priv *entry;
> > +
> > + spin_lock(&eqc_list_slock);
> > + list_for_each_entry(entry, &eqc_list, list) {
> > + if (entry->np == np) {
> > + priv = entry;
> > + break;
> > + }
> > + }
> > + spin_unlock(&eqc_list_slock);
> > +
> > + if (!priv)
> > + return -ENODEV;
>
> This can be a sub-function.

Will do.

[...]

> > + for (i = 0; i < data->pll_count; i++) {
> > + const struct eqc_pll *pll = &data->plls[i];
> > + unsigned long mult, div, acc;
> > + u32 r0, r1;
> > + u64 val;
> > + int ret;
>
> All variables should be declared at the start of the function. Once it
> becomes "too heavy" you can split it up into smaller functions, that
> again have all variables declared at the start of the function.

Will avoid variables declarations at start of loops.

> > +
> > + val = readq(priv->base_plls + pll->reg64);
> > + r0 = val;
> > + r1 = val >> 32;
> > +
> > + ret = eqc_pll_parse_registers(r0, r1, &mult, &div, &acc);
> > + if (ret) {
> > + dev_warn(dev, "failed parsing state of %s\n", pll->name);
> > + priv->cells->hws[pll->index] = ERR_PTR(ret);
> > + continue;
> > + }
> > +
> > + hw = clk_hw_register_fixed_factor_with_accuracy_fwname(dev,
> > + dev->of_node, pll->name, "ref", 0, mult, div, acc);
> > + priv->cells->hws[pll->index] = hw;
> > + if (IS_ERR(hw))
> > + dev_warn(dev, "failed registering %s: %pe\n", pll->name, hw);
> > + }
> > +
> > + BUG_ON(ARRAY_SIZE(div_resources) < data->div_count);
>
> Can this be a static assert instead on the arrays these are based on?
> Put some static_assert() near the match data macros.

I hesitated before sending. Will update.

> > +
> > + for (i = 0; i < data->div_count; i++) {
> > + const struct eqc_div *div = &data->divs[i];
> > + void __iomem *base = NULL;
> > + struct clk_hw *parent;
> > + unsigned int j;
> > +
> > + /*
> > + * Multiple divider clocks can request the same resource. Store
> > + * resource pointers during probe(). For each divider clock,
> > + * check if previous clocks referenced the same resource name.
> > + *
> > + * See EQ6HC_SOUTH_DIV_OSPI_REF and EQ6HC_SOUTH_DIV_OSPI_SYS.
> > + */
> > + for (j = 0; j < i; j++) {
> > + if (strcmp(data->divs[j].resource_name, div->resource_name) == 0) {
> > + base = div_resources[j];
> > + break;
> > + }
> > + }
> > +
> > + /* Resource is first encountered. */
> > + if (!base) {
> > + base = devm_platform_ioremap_resource_byname(pdev, div->resource_name);
> > + if (IS_ERR(base)) {
> > + dev_warn(dev, "failed to iomap resource for %s\n", div->name);
> > + priv->cells->hws[div->index] = base;
> > + continue;
> > + }
> > + }
>
> I don't get this code at all. The driver should simply map the
> resources because it knows that there's an io resource. I'll look at the
> binding which is probably wrong and causing the driver to be written
> this way.

This is here for a single reason: EyeQ6H south OLB has two clocks that
live in the same register:

- div-ospi-ref, reg offset 0x90, mask GENMASK(9, 8) == 0x300.
- div-ospi-sys, reg offset 0x90, mask GENMASK(12, 4) == 0x1FF0.

Calling twice devm_platform_ioremap_resource_byname() with the same
resource name gives an error. So we need to buffer resources already
requested.

If there is a simpler & better solution I'd be happy to take it.

[...]

> > +static void __init eqc_init(struct device_node *np)
> > +{
> > + const struct eqc_match_data *data;
> > + unsigned int nb_clks = 0;
> > + struct eqc_priv *priv;
> > + unsigned int i;
> > + int ret;
> > +
> > + data = of_match_node(eqc_match_table, np)->data;
> > +
> > + /* No reason to early init this clock provider. Do it at probe. */
> > + if (data->early_pll_count == 0)
>
> You can have a different match table for this function then.

Ah, clever. Will do.

[...]

> > + /*
> > + * We expect named resources if divider clocks are present.
> > + * Else, we only expect one resource.
> > + */
>
> Please avoid named resources. They give the false sense of hope that the
> binding can re-order the reg property when that can't be done. Instead,
> just index and know which index to use in the driver.

It is unclear what you mean by not being able to re-order reg property?
Are you talking about reg-names being most often defined as items const
list and therefore cannot be reordered? Here binding declare things
using minItems/maxItems/enum so it can be reordered, looking like:

properties:
reg:
minItems: 2
maxItems: 2
reg-names:
minItems: 2
maxItems: 2
items:
enum: [ plls, ospi ]

If this is not what you are talking about then I rambled about garbage
and I'll use indexed resources.

Thanks,

--
Théo Lebrun, Bootlin
Embedded Linux and Kernel engineering
https://bootlin.com