Message ID | 20240123-mbly-clk-v3-8-392b010b8281@bootlin.com (mailing list archive) |
---|---|
State | Superseded |
Headers | show |
Series | Add support for Mobileye EyeQ5 system controller | expand |
On 23/01/2024 19:46, Théo Lebrun wrote: > Add the Mobileye EyeQ5 clock controller driver. It might grow to add > support for other platforms from Mobileye. > > It handles 10 read-only PLLs derived from the main crystal on board. It > exposes a table-based divider clock used for OSPI. Other platform > clocks are not configurable and therefore kept as fixed-factor > devicetree nodes. > > Two PLLs are required early on and are therefore registered at > of_clk_init(). Those are pll-cpu for the GIC timer and pll-per for the > UARTs. > > +#define OLB_PCSR1_RESET BIT(0) > +#define OLB_PCSR1_SSGC_DIV GENMASK(4, 1) > +/* Spread amplitude (% = 0.1 * SPREAD[4:0]) */ > +#define OLB_PCSR1_SPREAD GENMASK(9, 5) > +#define OLB_PCSR1_DIS_SSCG BIT(10) > +/* Down-spread or center-spread */ > +#define OLB_PCSR1_DOWN_SPREAD BIT(11) > +#define OLB_PCSR1_FRAC_IN GENMASK(31, 12) > + > +static struct clk_hw_onecell_data *eq5c_clk_data; > +static struct regmap *eq5c_olb; Drop these two. No file-scope regmaps for drivers. Use private container structures. ... > +static void __init eq5c_init(struct device_node *np) > +{ > + struct device_node *parent_np = of_get_parent(np); > + int i, ret; > + > + eq5c_clk_data = kzalloc(struct_size(eq5c_clk_data, hws, EQ5C_NB_CLKS), > + GFP_KERNEL); > + if (!eq5c_clk_data) { > + ret = -ENOMEM; > + goto err; > + } > + > + eq5c_clk_data->num = EQ5C_NB_CLKS; > + > + /* > + * Mark all clocks as deferred. We register some now and others at > + * platform device probe. > + */ > + for (i = 0; i < EQ5C_NB_CLKS; i++) > + eq5c_clk_data->hws[i] = ERR_PTR(-EPROBE_DEFER); > + > + /* > + * Currently, if OLB is not available, we log an error, fail init then How it could be not available? Only with broken initcall ordering. Fix your initcall ordering and then simplify all this weird code. > + * fail probe. We might want to change this behavior and assume all > + * clocks are in bypass mode; this is what is being done in the vendor > + * driver. > + * > + * It is unclear if there are valid situations where the OLB region > + * would be inaccessible. Best regards, Krzysztof
Hello, On Wed Jan 24, 2024 at 8:05 AM CET, Krzysztof Kozlowski wrote: > On 23/01/2024 19:46, Théo Lebrun wrote: > > Add the Mobileye EyeQ5 clock controller driver. It might grow to add > > support for other platforms from Mobileye. > > > > It handles 10 read-only PLLs derived from the main crystal on board. It > > exposes a table-based divider clock used for OSPI. Other platform > > clocks are not configurable and therefore kept as fixed-factor > > devicetree nodes. > > > > Two PLLs are required early on and are therefore registered at > > of_clk_init(). Those are pll-cpu for the GIC timer and pll-per for the > > UARTs. > > > > > > +#define OLB_PCSR1_RESET BIT(0) > > +#define OLB_PCSR1_SSGC_DIV GENMASK(4, 1) > > +/* Spread amplitude (% = 0.1 * SPREAD[4:0]) */ > > +#define OLB_PCSR1_SPREAD GENMASK(9, 5) > > +#define OLB_PCSR1_DIS_SSCG BIT(10) > > +/* Down-spread or center-spread */ > > +#define OLB_PCSR1_DOWN_SPREAD BIT(11) > > +#define OLB_PCSR1_FRAC_IN GENMASK(31, 12) > > + > > +static struct clk_hw_onecell_data *eq5c_clk_data; > > +static struct regmap *eq5c_olb; > > Drop these two. No file-scope regmaps for drivers. Use private container > structures. I wouldn't know how to handle the two steps then. Two clocks and the clk provider are registered at of_clk_init() using CLK_OF_DECLARE_DRIVER(). The rest is at platform device probe. Without a static, there are no way to pass the struct clk_hw_onecell_data from one to the other. I've looked at all clock drivers that do CLK_OF_DECLARE_DRIVER() and register a platform driver. - The following use a static variable: drivers/clk/axis/clk-artpec6.c drivers/clk/clk-aspeed.c drivers/clk/clk-ast2600.c drivers/clk/clk-eyeq5.c drivers/clk/clk-gemini.c drivers/clk/clk-milbeaut.c drivers/clk/mediatek/clk-mt2701.c drivers/clk/mediatek/clk-mt6797.c drivers/clk/mediatek/clk-mt8173-infracfg.c drivers/clk/nxp/clk-lpc18xx-creg.c drivers/clk/ralink/clk-mt7621.c drivers/clk/ralink/clk-mtmips.c drivers/clk/sunxi/clk-mod0.c drivers/clk/axis/clk-artpec6.c - Those two declare different clock providers at init and probe: drivers/clk/ralink/clk-mt7621.c drivers/clk/sunxi/clk-mod0.c - It doesn't register new clocks at probe (only resets) so no need to share variables. drivers/clk/ralink/clk-mtmips.c > > ... > > > +static void __init eq5c_init(struct device_node *np) > > +{ > > + struct device_node *parent_np = of_get_parent(np); > > + int i, ret; > > + > > + eq5c_clk_data = kzalloc(struct_size(eq5c_clk_data, hws, EQ5C_NB_CLKS), > > + GFP_KERNEL); > > + if (!eq5c_clk_data) { > > + ret = -ENOMEM; > > + goto err; > > + } > > + > > + eq5c_clk_data->num = EQ5C_NB_CLKS; > > + > > + /* > > + * Mark all clocks as deferred. We register some now and others at > > + * platform device probe. > > + */ > > + for (i = 0; i < EQ5C_NB_CLKS; i++) > > + eq5c_clk_data->hws[i] = ERR_PTR(-EPROBE_DEFER); > > + > > + /* > > + * Currently, if OLB is not available, we log an error, fail init then > > How it could be not available? Only with broken initcall ordering. Fix > your initcall ordering and then simplify all this weird code. of_syscon_register() and regmap_init_mmio() lists many reasons for it to not be available. Am I missing something? Thanks, -- Théo Lebrun, Bootlin Embedded Linux and Kernel engineering https://bootlin.com
On 24/01/2024 17:41, Théo Lebrun wrote: > Hello, > > On Wed Jan 24, 2024 at 8:05 AM CET, Krzysztof Kozlowski wrote: >> On 23/01/2024 19:46, Théo Lebrun wrote: >>> Add the Mobileye EyeQ5 clock controller driver. It might grow to add >>> support for other platforms from Mobileye. >>> >>> It handles 10 read-only PLLs derived from the main crystal on board. It >>> exposes a table-based divider clock used for OSPI. Other platform >>> clocks are not configurable and therefore kept as fixed-factor >>> devicetree nodes. >>> >>> Two PLLs are required early on and are therefore registered at >>> of_clk_init(). Those are pll-cpu for the GIC timer and pll-per for the >>> UARTs. >>> >> >> >>> +#define OLB_PCSR1_RESET BIT(0) >>> +#define OLB_PCSR1_SSGC_DIV GENMASK(4, 1) >>> +/* Spread amplitude (% = 0.1 * SPREAD[4:0]) */ >>> +#define OLB_PCSR1_SPREAD GENMASK(9, 5) >>> +#define OLB_PCSR1_DIS_SSCG BIT(10) >>> +/* Down-spread or center-spread */ >>> +#define OLB_PCSR1_DOWN_SPREAD BIT(11) >>> +#define OLB_PCSR1_FRAC_IN GENMASK(31, 12) >>> + >>> +static struct clk_hw_onecell_data *eq5c_clk_data; >>> +static struct regmap *eq5c_olb; >> >> Drop these two. No file-scope regmaps for drivers. Use private container >> structures. > > I wouldn't know how to handle the two steps then. Two clocks and the clk > provider are registered at of_clk_init() using CLK_OF_DECLARE_DRIVER(). Right, if some clocks have to be early, CLK_OF_DECLARE_DRIVER needs static ones. But your commit subject says it is a platform driver and all other pieces of this code is rather incompatible with this approach. Do not use CLK_OF_DECLARE_DRIVER for cases where you have dependencies because it forces you to manually order initcalls, which is exactly what we do not want. > The rest is at platform device probe. Without a static, there are no > way to pass the struct clk_hw_onecell_data from one to the other. > > I've looked at all clock drivers that do CLK_OF_DECLARE_DRIVER() and > register a platform driver. Even though the code is correct, using arguments "other did it" will not work. You want to say that you implement legacy, poor code because you saw legacy, poor code? > > - The following use a static variable: > drivers/clk/axis/clk-artpec6.c > drivers/clk/clk-aspeed.c > drivers/clk/clk-ast2600.c > drivers/clk/clk-eyeq5.c > drivers/clk/clk-gemini.c > drivers/clk/clk-milbeaut.c > drivers/clk/mediatek/clk-mt2701.c > drivers/clk/mediatek/clk-mt6797.c > drivers/clk/mediatek/clk-mt8173-infracfg.c > drivers/clk/nxp/clk-lpc18xx-creg.c > drivers/clk/ralink/clk-mt7621.c > drivers/clk/ralink/clk-mtmips.c > drivers/clk/sunxi/clk-mod0.c > drivers/clk/axis/clk-artpec6.c > > - Those two declare different clock providers at init and probe: > drivers/clk/ralink/clk-mt7621.c > drivers/clk/sunxi/clk-mod0.c > > - It doesn't register new clocks at probe (only resets) so no need to > share variables. > drivers/clk/ralink/clk-mtmips.c > >> >> ... >> >>> +static void __init eq5c_init(struct device_node *np) >>> +{ >>> + struct device_node *parent_np = of_get_parent(np); >>> + int i, ret; >>> + >>> + eq5c_clk_data = kzalloc(struct_size(eq5c_clk_data, hws, EQ5C_NB_CLKS), >>> + GFP_KERNEL); >>> + if (!eq5c_clk_data) { >>> + ret = -ENOMEM; >>> + goto err; >>> + } >>> + >>> + eq5c_clk_data->num = EQ5C_NB_CLKS; >>> + >>> + /* >>> + * Mark all clocks as deferred. We register some now and others at >>> + * platform device probe. >>> + */ >>> + for (i = 0; i < EQ5C_NB_CLKS; i++) >>> + eq5c_clk_data->hws[i] = ERR_PTR(-EPROBE_DEFER); >>> + >>> + /* >>> + * Currently, if OLB is not available, we log an error, fail init then >> >> How it could be not available? Only with broken initcall ordering. Fix >> your initcall ordering and then simplify all this weird code. > > of_syscon_register() and regmap_init_mmio() lists many reasons for > it to not be available. Am I missing something? Yes, initcall ordering. Best regards, Krzysztof
Hi, On Thu Jan 25, 2024 at 8:46 AM CET, Krzysztof Kozlowski wrote: > On 24/01/2024 17:41, Théo Lebrun wrote: > > Hello, > > > > On Wed Jan 24, 2024 at 8:05 AM CET, Krzysztof Kozlowski wrote: > >> On 23/01/2024 19:46, Théo Lebrun wrote: > >>> Add the Mobileye EyeQ5 clock controller driver. It might grow to add > >>> support for other platforms from Mobileye. > >>> > >>> It handles 10 read-only PLLs derived from the main crystal on board. It > >>> exposes a table-based divider clock used for OSPI. Other platform > >>> clocks are not configurable and therefore kept as fixed-factor > >>> devicetree nodes. > >>> > >>> Two PLLs are required early on and are therefore registered at > >>> of_clk_init(). Those are pll-cpu for the GIC timer and pll-per for the > >>> UARTs. > >>> > >> > >> > >>> +#define OLB_PCSR1_RESET BIT(0) > >>> +#define OLB_PCSR1_SSGC_DIV GENMASK(4, 1) > >>> +/* Spread amplitude (% = 0.1 * SPREAD[4:0]) */ > >>> +#define OLB_PCSR1_SPREAD GENMASK(9, 5) > >>> +#define OLB_PCSR1_DIS_SSCG BIT(10) > >>> +/* Down-spread or center-spread */ > >>> +#define OLB_PCSR1_DOWN_SPREAD BIT(11) > >>> +#define OLB_PCSR1_FRAC_IN GENMASK(31, 12) > >>> + > >>> +static struct clk_hw_onecell_data *eq5c_clk_data; > >>> +static struct regmap *eq5c_olb; > >> > >> Drop these two. No file-scope regmaps for drivers. Use private container > >> structures. > > > > I wouldn't know how to handle the two steps then. Two clocks and the clk > > provider are registered at of_clk_init() using CLK_OF_DECLARE_DRIVER(). > > Right, if some clocks have to be early, CLK_OF_DECLARE_DRIVER needs > static ones. But your commit subject says it is a platform driver and > all other pieces of this code is rather incompatible with this approach. That is my bad on the commit subject. What do you refer to by "all other pieces of this code is rather incompatible with this approach"? I've tried to minimise the use of static variables. Therefore as soon as the probe is started, we switch to the usual way of using a private struct that contains our info. > > Do not use CLK_OF_DECLARE_DRIVER for cases where you have dependencies > because it forces you to manually order initcalls, which is exactly what > we do not want. What should I be using? I got confirmation from Stephen that this mixture of CLK_OF_DECLARE_DRIVER() + platform driver is what I should be using as review in my V1. https://lore.kernel.org/lkml/fa32e6fae168e10d42051b89197855e9.sboyd@kernel.org/ > > > > The rest is at platform device probe. Without a static, there are no > > way to pass the struct clk_hw_onecell_data from one to the other. > > > > I've looked at all clock drivers that do CLK_OF_DECLARE_DRIVER() and > > register a platform driver. > > Even though the code is correct, using arguments "other did it" will not > work. You want to say that you implement legacy, poor code because you > saw legacy, poor code? Yes I see what you mean. It's just that this is not the sort of things that are documented. And learning The Right Way(TM) when you don't know it can only be done by looking at existing stuff. I'm being exhaustive to avoid basing my approach on one old-school driver that is using the wrong approach. > >> ... > >> > >>> +static void __init eq5c_init(struct device_node *np) > >>> +{ > >>> + struct device_node *parent_np = of_get_parent(np); > >>> + int i, ret; > >>> + > >>> + eq5c_clk_data = kzalloc(struct_size(eq5c_clk_data, hws, EQ5C_NB_CLKS), > >>> + GFP_KERNEL); > >>> + if (!eq5c_clk_data) { > >>> + ret = -ENOMEM; > >>> + goto err; > >>> + } > >>> + > >>> + eq5c_clk_data->num = EQ5C_NB_CLKS; > >>> + > >>> + /* > >>> + * Mark all clocks as deferred. We register some now and others at > >>> + * platform device probe. > >>> + */ > >>> + for (i = 0; i < EQ5C_NB_CLKS; i++) > >>> + eq5c_clk_data->hws[i] = ERR_PTR(-EPROBE_DEFER); > >>> + > >>> + /* > >>> + * Currently, if OLB is not available, we log an error, fail init then > >> > >> How it could be not available? Only with broken initcall ordering. Fix > >> your initcall ordering and then simplify all this weird code. > > > > of_syscon_register() and regmap_init_mmio() lists many reasons for > > it to not be available. Am I missing something? > > Yes, initcall ordering. You said the regmap can only not be available with broken initcall ordering. I say that is not the only reason. About initcall, I've removed those that used initcall in the three drivers I'm using except this clk one that requires two clocks at of_clk_init(). Thanks, -- Théo Lebrun, Bootlin Embedded Linux and Kernel engineering https://bootlin.com
On 25/01/2024 12:53, Théo Lebrun wrote: > Hi, > > On Thu Jan 25, 2024 at 8:46 AM CET, Krzysztof Kozlowski wrote: >> On 24/01/2024 17:41, Théo Lebrun wrote: >>> Hello, >>> >>> On Wed Jan 24, 2024 at 8:05 AM CET, Krzysztof Kozlowski wrote: >>>> On 23/01/2024 19:46, Théo Lebrun wrote: >>>>> Add the Mobileye EyeQ5 clock controller driver. It might grow to add >>>>> support for other platforms from Mobileye. >>>>> >>>>> It handles 10 read-only PLLs derived from the main crystal on board. It >>>>> exposes a table-based divider clock used for OSPI. Other platform >>>>> clocks are not configurable and therefore kept as fixed-factor >>>>> devicetree nodes. >>>>> >>>>> Two PLLs are required early on and are therefore registered at >>>>> of_clk_init(). Those are pll-cpu for the GIC timer and pll-per for the >>>>> UARTs. >>>>> >>>> >>>> >>>>> +#define OLB_PCSR1_RESET BIT(0) >>>>> +#define OLB_PCSR1_SSGC_DIV GENMASK(4, 1) >>>>> +/* Spread amplitude (% = 0.1 * SPREAD[4:0]) */ >>>>> +#define OLB_PCSR1_SPREAD GENMASK(9, 5) >>>>> +#define OLB_PCSR1_DIS_SSCG BIT(10) >>>>> +/* Down-spread or center-spread */ >>>>> +#define OLB_PCSR1_DOWN_SPREAD BIT(11) >>>>> +#define OLB_PCSR1_FRAC_IN GENMASK(31, 12) >>>>> + >>>>> +static struct clk_hw_onecell_data *eq5c_clk_data; >>>>> +static struct regmap *eq5c_olb; >>>> >>>> Drop these two. No file-scope regmaps for drivers. Use private container >>>> structures. >>> >>> I wouldn't know how to handle the two steps then. Two clocks and the clk >>> provider are registered at of_clk_init() using CLK_OF_DECLARE_DRIVER(). >> >> Right, if some clocks have to be early, CLK_OF_DECLARE_DRIVER needs >> static ones. But your commit subject says it is a platform driver and >> all other pieces of this code is rather incompatible with this approach. > > That is my bad on the commit subject. What do you refer to by "all other > pieces of this code is rather incompatible with this approach"? That you depend on syscon. If it was regular MMIO block in SoC space, then no problem. If you depend on anything else providing you regmap, then any initcall ordering is fragile and error-prone. Avoid. > > I've tried to minimise the use of static variables. Therefore as soon as > the probe is started, we switch to the usual way of using a private > struct that contains our info. > >> >> Do not use CLK_OF_DECLARE_DRIVER for cases where you have dependencies >> because it forces you to manually order initcalls, which is exactly what >> we do not want. > > What should I be using? I got confirmation from Stephen that this > mixture of CLK_OF_DECLARE_DRIVER() + platform driver is what I should > be using as review in my V1. > > https://lore.kernel.org/lkml/fa32e6fae168e10d42051b89197855e9.sboyd@kernel.org/ I see. In such case I believe it is error on relying on syscon. Best regards, Krzysztof
diff --git a/MAINTAINERS b/MAINTAINERS index 6dc4251e1ac2..3ea96ab7d2b8 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -14793,6 +14793,7 @@ F: Documentation/devicetree/bindings/soc/mobileye/ F: arch/mips/boot/dts/mobileye/ F: arch/mips/configs/eyeq5_defconfig F: arch/mips/mobileye/board-epm5.its.S +F: drivers/clk/clk-eyeq5.c F: include/dt-bindings/clock/mobileye,eyeq5-clk.h F: include/dt-bindings/soc/mobileye,eyeq5.h diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig index 50af5fc7f570..d5043ce2a75c 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_EYEQ5 + bool "Clock driver for the Mobileye EyeQ5 platform" + depends on OF + depends on MACH_EYEQ5 || COMPILE_TEST + default MACH_EYEQ5 + help + This driver provides the clocks found on the Mobileye EyeQ5 SoC. Its + registers live in a shared register region called OLB. It provides 10 + read-only PLLs derived from the main crystal clock which must be constant + and one divider clock based on one PLL. + config COMMON_CLK_FSL_FLEXSPI tristate "Clock driver for FlexSPI on Layerscape SoCs" depends on ARCH_LAYERSCAPE || COMPILE_TEST diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile index 14fa8d4ecc1f..81c4d11ca437 100644 --- a/drivers/clk/Makefile +++ b/drivers/clk/Makefile @@ -32,6 +32,7 @@ obj-$(CONFIG_ARCH_CLPS711X) += clk-clps711x.o obj-$(CONFIG_COMMON_CLK_CS2000_CP) += clk-cs2000-cp.o obj-$(CONFIG_ARCH_SPARX5) += clk-sparx5.o obj-$(CONFIG_COMMON_CLK_EN7523) += clk-en7523.o +obj-$(CONFIG_COMMON_CLK_EYEQ5) += clk-eyeq5.o obj-$(CONFIG_COMMON_CLK_FIXED_MMIO) += clk-fixed-mmio.o obj-$(CONFIG_COMMON_CLK_FSL_FLEXSPI) += clk-fsl-flexspi.o obj-$(CONFIG_COMMON_CLK_FSL_SAI) += clk-fsl-sai.o diff --git a/drivers/clk/clk-eyeq5.c b/drivers/clk/clk-eyeq5.c new file mode 100644 index 000000000000..475dfcdc8af1 --- /dev/null +++ b/drivers/clk/clk-eyeq5.c @@ -0,0 +1,414 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * PLL clock driver for the Mobileye EyeQ5 platform. + * + * This controller handles 10 read-only PLLs, all derived from the same main + * crystal clock. It also exposes one divider clock, a child of one of the + * PLLs. The parent clock is expected to be constant. This driver's registers + * live in a shared region called OLB. Two PLLs must be initialized by + * of_clk_init(). + * + * We use eq5c_ as prefix, as-in "EyeQ5 Clock", but way shorter. + * + * Copyright (C) 2024 Mobileye Vision Technologies Ltd. + */ + +#define pr_fmt(fmt) "clk-eyeq5: " fmt + +#include <linux/bitfield.h> +#include <linux/bits.h> +#include <linux/clk-provider.h> +#include <linux/mfd/syscon.h> +#include <linux/mod_devicetable.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> + +#include <dt-bindings/clock/mobileye,eyeq5-clk.h> + +/* + * PLL control & status registers, n=0..1 + * 0x02c..0x078 + */ +#define OLB_PCSR_CPU(n) (0x02C + (n) * 4) /* CPU */ +#define OLB_PCSR_VMP(n) (0x034 + (n) * 4) /* VMP */ +#define OLB_PCSR_PMA(n) (0x03C + (n) * 4) /* PMA */ +#define OLB_PCSR_VDI(n) (0x044 + (n) * 4) /* VDI */ +#define OLB_PCSR_DDR0(n) (0x04C + (n) * 4) /* DDR0 */ +#define OLB_PCSR_PCI(n) (0x054 + (n) * 4) /* PCI */ +#define OLB_PCSR_PER(n) (0x05C + (n) * 4) /* PER */ +#define OLB_PCSR_PMAC(n) (0x064 + (n) * 4) /* PMAC */ +#define OLB_PCSR_MPC(n) (0x06c + (n) * 4) /* MPC */ +#define OLB_PCSR_DDR1(n) (0x074 + (n) * 4) /* DDR1 */ + +/* In frac mode, it enables fractional noise canceling DAC. Else, no function. */ +#define OLB_PCSR0_DAC_EN BIT(0) +/* Fractional or integer mode */ +#define OLB_PCSR0_DSM_EN BIT(1) +#define OLB_PCSR0_PLL_EN BIT(2) +/* All clocks output held at 0 */ +#define OLB_PCSR0_FOUTPOSTDIV_EN BIT(3) +#define OLB_PCSR0_POST_DIV1 GENMASK(6, 4) +#define OLB_PCSR0_POST_DIV2 GENMASK(9, 7) +#define OLB_PCSR0_REF_DIV GENMASK(15, 10) +#define OLB_PCSR0_INTIN GENMASK(27, 16) +#define OLB_PCSR0_BYPASS BIT(28) +/* Bits 30..29 are reserved */ +#define OLB_PCSR0_PLL_LOCKED BIT(31) + +#define OLB_PCSR1_RESET BIT(0) +#define OLB_PCSR1_SSGC_DIV GENMASK(4, 1) +/* Spread amplitude (% = 0.1 * SPREAD[4:0]) */ +#define OLB_PCSR1_SPREAD GENMASK(9, 5) +#define OLB_PCSR1_DIS_SSCG BIT(10) +/* Down-spread or center-spread */ +#define OLB_PCSR1_DOWN_SPREAD BIT(11) +#define OLB_PCSR1_FRAC_IN GENMASK(31, 12) + +static struct clk_hw_onecell_data *eq5c_clk_data; +static struct regmap *eq5c_olb; + +struct eq5c_pll { + int index; + const char *name; + u32 reg; +}; + +/* Required early for the GIC timer (pll-cpu) and UARTs (pll-per). */ +static const struct eq5c_pll eq5c_early_plls[] = { + { .index = EQ5C_PLL_CPU, .name = "pll-cpu", .reg = OLB_PCSR_CPU(0), }, + { .index = EQ5C_PLL_PER, .name = "pll-per", .reg = OLB_PCSR_PER(0), }, +}; + +static const struct eq5c_pll eq5c_plls[] = { + { .index = EQ5C_PLL_VMP, .name = "pll-vmp", .reg = OLB_PCSR_VMP(0), }, + { .index = EQ5C_PLL_PMA, .name = "pll-pma", .reg = OLB_PCSR_PMA(0), }, + { .index = EQ5C_PLL_VDI, .name = "pll-vdi", .reg = OLB_PCSR_VDI(0), }, + { .index = EQ5C_PLL_DDR0, .name = "pll-ddr0", .reg = OLB_PCSR_DDR0(0), }, + { .index = EQ5C_PLL_PCI, .name = "pll-pci", .reg = OLB_PCSR_PCI(0), }, + { .index = EQ5C_PLL_PMAC, .name = "pll-pmac", .reg = OLB_PCSR_PMAC(0), }, + { .index = EQ5C_PLL_MPC, .name = "pll-mpc", .reg = OLB_PCSR_MPC(0), }, + { .index = EQ5C_PLL_DDR1, .name = "pll-ddr1", .reg = OLB_PCSR_DDR1(0), }, +}; + +#define EQ5C_OSPI_DIV_CLK_NAME "div-ospi" + +#define EQ5C_NB_CLKS (ARRAY_SIZE(eq5c_early_plls) + ARRAY_SIZE(eq5c_plls) + 1) + +static int eq5c_pll_parse_registers(u32 r0, u32 r1, unsigned long *mult, + unsigned long *div, unsigned long *acc) +{ + if (r0 & OLB_PCSR0_BYPASS) { + *mult = 1; + *div = 1; + *acc = 0; + return 0; + } + + if (!(r0 & OLB_PCSR0_PLL_LOCKED)) + return -EINVAL; + + *mult = FIELD_GET(OLB_PCSR0_INTIN, r0); + *div = FIELD_GET(OLB_PCSR0_REF_DIV, r0); + if (r0 & OLB_PCSR0_FOUTPOSTDIV_EN) + *div *= FIELD_GET(OLB_PCSR0_POST_DIV1, r0) * + FIELD_GET(OLB_PCSR0_POST_DIV2, r0); + + /* Fractional mode, in 2^20 (0x100000) parts. */ + if (r0 & OLB_PCSR0_DSM_EN) { + *div *= 0x100000; + *mult = *mult * 0x100000 + FIELD_GET(OLB_PCSR1_FRAC_IN, r1); + } + + if (!*mult || !*div) + return -EINVAL; + + /* Spread spectrum. */ + if (!(r1 & (OLB_PCSR1_RESET | OLB_PCSR1_DIS_SSCG))) { + /* + * Spread is 1/1000 parts of frequency, accuracy is half of + * that. To get accuracy, convert to ppb (parts per billion). + */ + u32 spread = FIELD_GET(OLB_PCSR1_SPREAD, r1); + *acc = spread * 500000; + if (r1 & OLB_PCSR1_DOWN_SPREAD) { + /* + * Downspreading: the central frequency is half a + * spread lower. + */ + *mult *= 2000 - spread; + *div *= 2000; + } + } else { + *acc = 0; + } + + return 0; +} + +#define OLB_OSPI_REG 0x11C +#define OLB_OSPI_DIV_MASK GENMASK(3, 0) +#define OLB_OSPI_DIV_MASK_WIDTH 4 + +static const struct clk_div_table eq5c_ospi_div_table[] = { + { .val = 0, .div = 2 }, + { .val = 1, .div = 4 }, + { .val = 2, .div = 6 }, + { .val = 3, .div = 8 }, + { .val = 4, .div = 10 }, + { .val = 5, .div = 12 }, + { .val = 6, .div = 14 }, + { .val = 7, .div = 16 }, + {} /* sentinel */ +}; + +struct eq5c_ospi_div { + struct clk_hw hw; + struct device *dev; + struct regmap *olb; +}; + +static struct eq5c_ospi_div *clk_to_priv(struct clk_hw *hw) +{ + return container_of(hw, struct eq5c_ospi_div, hw); +} + +static unsigned long eq5c_ospi_div_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct eq5c_ospi_div *div = clk_to_priv(hw); + unsigned int val; + int ret; + + ret = regmap_read(div->olb, OLB_OSPI_REG, &val); + + if (ret) { + dev_err(div->dev, "regmap_read failed: %d\n", ret); + return 0; + } + + val = FIELD_GET(OLB_OSPI_DIV_MASK, val); + + return divider_recalc_rate(hw, parent_rate, val, + eq5c_ospi_div_table, 0, + OLB_OSPI_DIV_MASK_WIDTH); +} + +static long eq5c_ospi_div_round_rate(struct clk_hw *hw, + unsigned long rate, unsigned long *prate) +{ + return divider_round_rate(hw, rate, prate, eq5c_ospi_div_table, + OLB_OSPI_DIV_MASK_WIDTH, 0); +} + +static int eq5c_ospi_div_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + return divider_determine_rate(hw, req, eq5c_ospi_div_table, + OLB_OSPI_DIV_MASK_WIDTH, 0); +} + +static int eq5c_ospi_div_set_rate(struct clk_hw *hw, + unsigned long rate, unsigned long parent_rate) +{ + struct eq5c_ospi_div *div = clk_to_priv(hw); + unsigned int val; + int value, ret; + + value = divider_get_val(rate, parent_rate, eq5c_ospi_div_table, + OLB_OSPI_DIV_MASK_WIDTH, 0); + if (value < 0) + return value; + + ret = regmap_read(div->olb, OLB_OSPI_REG, &val); + if (ret) { + dev_err(div->dev, "regmap_read failed: %d\n", ret); + return ret; + } + + val &= ~OLB_OSPI_DIV_MASK; + val |= FIELD_PREP(OLB_OSPI_DIV_MASK, value); + + ret = regmap_write(div->olb, OLB_OSPI_REG, val); + if (ret) { + dev_err(div->dev, "regmap_write failed: %d\n", ret); + return ret; + } + + return 0; +} + +static const struct clk_ops eq5c_ospi_div_ops = { + .recalc_rate = eq5c_ospi_div_recalc_rate, + .round_rate = eq5c_ospi_div_round_rate, + .determine_rate = eq5c_ospi_div_determine_rate, + .set_rate = eq5c_ospi_div_set_rate, +}; + +static struct clk_hw *eq5c_init_ospi_div(struct device *dev, const char *name, + struct regmap *olb, + const struct clk_hw *parent) +{ + struct eq5c_ospi_div *div; + int ret; + + div = kzalloc(sizeof(*div), GFP_KERNEL); + if (!div) + return ERR_PTR(-ENOENT); + + div->dev = dev; + div->olb = olb; + div->hw.init = CLK_HW_INIT_HW(name, parent, &eq5c_ospi_div_ops, 0); + + ret = clk_hw_register(dev, &div->hw); + if (ret) { + dev_err(dev, "failed registering %s: %d\n", name, ret); + kfree(div); + return ERR_PTR(ret); + } + + return &div->hw; +} + +static int eq5c_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + struct clk_hw *hw; + int i; + + if (IS_ERR(eq5c_clk_data)) + return PTR_ERR(eq5c_clk_data); + else if (!eq5c_clk_data) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(eq5c_plls); i++) { + const struct eq5c_pll *pll = &eq5c_plls[i]; + unsigned long mult, div, acc; + u32 r0, r1; + int ret; + + regmap_read(eq5c_olb, pll->reg, &r0); + regmap_read(eq5c_olb, pll->reg + sizeof(r0), &r1); + + ret = eq5c_pll_parse_registers(r0, r1, &mult, &div, &acc); + if (ret) { + dev_warn(dev, "failed parsing state of %s\n", pll->name); + continue; + } + + hw = clk_hw_register_fixed_factor_with_accuracy_fwname(dev, np, + pll->name, "ref", 0, mult, div, acc); + eq5c_clk_data->hws[pll->index] = hw; + if (IS_ERR(hw)) { + dev_err(dev, "failed registering %s: %ld\n", + pll->name, PTR_ERR(hw)); + } + } + + /* + * Register the OSPI table-based divider clock manually. This is + * equivalent to drivers/clk/clk-divider.c, but using regmap to access + * register. + */ + hw = eq5c_init_ospi_div(dev, EQ5C_OSPI_DIV_CLK_NAME, eq5c_olb, + eq5c_clk_data->hws[EQ5C_PLL_PER]); + eq5c_clk_data->hws[EQ5C_DIV_OSPI] = hw; + + return 0; +} + +static const struct of_device_id eq5c_match_table[] = { + { .compatible = "mobileye,eyeq5-clk" }, + {} +}; +MODULE_DEVICE_TABLE(of, eq5c_match_table); + +static struct platform_driver eq5c_driver = { + .probe = eq5c_probe, + .driver = { + .name = "clk-eyeq5", + .of_match_table = eq5c_match_table, + }, +}; + +builtin_platform_driver(eq5c_driver); + +static void __init eq5c_init(struct device_node *np) +{ + struct device_node *parent_np = of_get_parent(np); + int i, ret; + + eq5c_clk_data = kzalloc(struct_size(eq5c_clk_data, hws, EQ5C_NB_CLKS), + GFP_KERNEL); + if (!eq5c_clk_data) { + ret = -ENOMEM; + goto err; + } + + eq5c_clk_data->num = EQ5C_NB_CLKS; + + /* + * Mark all clocks as deferred. We register some now and others at + * platform device probe. + */ + for (i = 0; i < EQ5C_NB_CLKS; i++) + eq5c_clk_data->hws[i] = ERR_PTR(-EPROBE_DEFER); + + /* + * Currently, if OLB is not available, we log an error, fail init then + * fail probe. We might want to change this behavior and assume all + * clocks are in bypass mode; this is what is being done in the vendor + * driver. + * + * It is unclear if there are valid situations where the OLB region + * would be inaccessible. + */ + eq5c_olb = ERR_PTR(-ENODEV); + if (parent_np) + eq5c_olb = syscon_node_to_regmap(parent_np); + if (IS_ERR(eq5c_olb)) { + pr_err("failed getting regmap: %ld\n", PTR_ERR(eq5c_olb)); + ret = PTR_ERR(eq5c_olb); + goto err; + } + + for (i = 0; i < ARRAY_SIZE(eq5c_early_plls); i++) { + const struct eq5c_pll *pll = &eq5c_early_plls[i]; + unsigned long mult, div, acc; + struct clk_hw *hw; + u32 r0, r1; + + regmap_read(eq5c_olb, pll->reg, &r0); + regmap_read(eq5c_olb, pll->reg + sizeof(r0), &r1); + + ret = eq5c_pll_parse_registers(r0, r1, &mult, &div, &acc); + if (ret) { + pr_warn("failed parsing state of %s\n", pll->name); + continue; + } + + hw = clk_hw_register_fixed_factor_with_accuracy_fwname(NULL, + np, pll->name, "ref", 0, mult, div, acc); + eq5c_clk_data->hws[pll->index] = hw; + if (IS_ERR(hw)) { + pr_err("failed registering %s: %ld\n", + pll->name, PTR_ERR(hw)); + } + } + + ret = of_clk_add_hw_provider(np, of_clk_hw_onecell_get, eq5c_clk_data); + if (ret) { + pr_err("failed registering clk provider: %d\n", ret); + goto err; + } + + return; + +err: + kfree(eq5c_clk_data); + /* Signal to platform driver probe that we failed init. */ + eq5c_clk_data = ERR_PTR(ret); + eq5c_olb = NULL; +} + +CLK_OF_DECLARE_DRIVER(eq5c, "mobileye,eyeq5-clk", eq5c_init);
Add the Mobileye EyeQ5 clock controller driver. It might grow to add support for other platforms from Mobileye. It handles 10 read-only PLLs derived from the main crystal on board. It exposes a table-based divider clock used for OSPI. Other platform clocks are not configurable and therefore kept as fixed-factor devicetree nodes. Two PLLs are required early on and are therefore registered at of_clk_init(). Those are pll-cpu for the GIC timer and pll-per for the UARTs. Signed-off-by: Théo Lebrun <theo.lebrun@bootlin.com> --- MAINTAINERS | 1 + drivers/clk/Kconfig | 11 ++ drivers/clk/Makefile | 1 + drivers/clk/clk-eyeq5.c | 414 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 427 insertions(+)