On Mon, Dec 05, 2016 at 06:54:34AM -0800, Andrey Smirnov wrote: > Mask ROM leaves the CPU running at 264Mhz, so configure the clock tree > to such that CPU runs at maximum supported frequency, based on speed > grading burned into OCOTP fusebox. > > Signed-off-by: Andrey Smirnov <andrew.smirnov@xxxxxxxxx> > --- > arch/arm/mach-imx/Kconfig | 13 +++ > drivers/clk/imx/clk-vf610.c | 187 +++++++++++++++++++++++++++++++++++++++++++- > 2 files changed, 198 insertions(+), 2 deletions(-) > > diff --git a/arch/arm/mach-imx/Kconfig b/arch/arm/mach-imx/Kconfig > index af533ea..1752335 100644 > --- a/arch/arm/mach-imx/Kconfig > +++ b/arch/arm/mach-imx/Kconfig > @@ -750,6 +750,19 @@ config HABV3_IMG_CRT_DER > > endif > > +config ADJUST_CPU_CLOCK > + bool "Adjust CPU clock based on its speed grading" > + select IMX_OCOTP > + depends on ARCH_VF610 > + default y > + help > + Some i.MX SoCs (e. g. Vybrid) are manufactured to have > + several variants of the same chip different only in maxumum s/maxumum/maximum/ > + CPU frequency supported. MaskROM on such chips plays it safe > + and uses the lowest possible frequency. This option > + configures Barebox to read chip's speed grade information > + and increase CPU clock to it's highest possible value. Why do we need this configurable? > + > endmenu > > endif > diff --git a/drivers/clk/imx/clk-vf610.c b/drivers/clk/imx/clk-vf610.c > index 04cb02f..1cf2b65 100644 > --- a/drivers/clk/imx/clk-vf610.c > +++ b/drivers/clk/imx/clk-vf610.c > @@ -16,7 +16,9 @@ > #include <of_address.h> > #include <linux/clkdev.h> > #include <linux/clk.h> > +#include <notifier.h> > #include <dt-bindings/clock/vf610-clock.h> > +#include <mach/vf610-regs.h> > > #include "clk.h" > > @@ -76,6 +78,7 @@ > #define PLL6_CTRL (anatop_base + 0xa0) > #define PLL7_CTRL (anatop_base + 0x20) > #define ANA_MISC1 (anatop_base + 0x160) > +#define PLL_LOCK (anatop_base + 0x2c0) > > static void __iomem *anatop_base; > static void __iomem *ccm_base; > @@ -188,8 +191,9 @@ static void __init vf610_clocks_init(struct device_node *ccm_node) > clk[VF610_CLK_PLL6_BYPASS_SRC] = imx_clk_mux("pll6_bypass_src", PLL6_CTRL, 14, 1, pll_bypass_src_sels, ARRAY_SIZE(pll_bypass_src_sels)); > clk[VF610_CLK_PLL7_BYPASS_SRC] = imx_clk_mux("pll7_bypass_src", PLL7_CTRL, 14, 1, pll_bypass_src_sels, ARRAY_SIZE(pll_bypass_src_sels)); > > - clk[VF610_CLK_PLL1] = imx_clk_pllv3(IMX_PLLV3_GENERIC, "pll1", "pll1_bypass_src", PLL1_CTRL, 0x1); > - clk[VF610_CLK_PLL2] = imx_clk_pllv3(IMX_PLLV3_GENERIC, "pll2", "pll2_bypass_src", PLL2_CTRL, 0x1); > + clk[VF610_CLK_PLL1] = imx_clk_pllv3_locked(IMX_PLLV3_SYS_VF610, "pll1", "pll1_bypass_src", PLL1_CTRL, 0x1, PLL_LOCK, BIT(6)); > + clk[VF610_CLK_PLL2] = imx_clk_pllv3_locked(IMX_PLLV3_SYS_VF610, "pll2", "pll2_bypass_src", PLL2_CTRL, 0x1, PLL_LOCK, BIT(5)); > + > clk[VF610_CLK_PLL3] = imx_clk_pllv3(IMX_PLLV3_USB_VF610, "pll3", "pll3_bypass_src", PLL3_CTRL, 0x2); > clk[VF610_CLK_PLL4] = imx_clk_pllv3(IMX_PLLV3_AV, "pll4", "pll4_bypass_src", PLL4_CTRL, 0x7f); > clk[VF610_CLK_PLL5] = imx_clk_pllv3(IMX_PLLV3_ENET, "pll5", "pll5_bypass_src", PLL5_CTRL, 0x3); > @@ -441,3 +445,182 @@ static void __init vf610_clocks_init(struct device_node *ccm_node) > of_clk_add_provider(np, of_clk_src_onecell_get, &clk_data); > } > CLK_OF_DECLARE(vf610, "fsl,vf610-ccm", vf610_clocks_init); > + > +#ifdef CONFIG_ADJUST_CPU_CLOCK > + > +enum { > + OCOTP_SPEED_GRADING_OFFSET = (0x4 * sizeof(uint32_t)), > + OCOTP_SPEED_GRADING_SHIFT = 18, > + OCOTP_SPEED_GRADING_MASK = 0b1111, > + > + VF610_SPEED_500 = 0b1110, > + VF610_SPEED_400 = 0b1001, > + VF610_SPEED_266 = 0b0001, > + > + DDRMC_CR117 = 0x01d4, > + DDRMC_CR117_AXI0_FITYPEREG_SYNC = 0b01 << 16, > +}; > + > +static int vf610_switch_cpu_clock_to_500mhz(void) > +{ > + int ret; > + > + /* > + * When switching A5 CPU to 500Mhz we expect DDRC to be > + * clocked by PLL2_PFD2 and the system to be configured in > + * asynchronous mode. > + * > + * We also can't just use default PFD1 output of PLL1 due to > + * Errata e6235, so we have to re-clock the PLL itself and use > + * its output to clock the CPU directly. > + */ > + > + if (clk_get_parent(clk[VF610_CLK_DDR_SEL]) != clk[VF610_CLK_PLL2_PFD2]) { > + pr_warn("DDRC is clocked by PLL1, can't switch CPU clock"); > + return -EINVAL; > + } > + > + ret = clk_set_parent(clk[VF610_CLK_SYS_SEL], clk[VF610_CLK_PLL2_BUS]); > + if (ret < 0) { > + pr_crit("Unable to re-parent '%s'\n", > + clk[VF610_CLK_SYS_SEL]->name); > + return ret; > + } > + > + ret = clk_set_rate(clk[VF610_CLK_PLL1], 500000000); > + if (ret < 0) { > + pr_crit("Unable to set %s to 500Mhz %d\n", > + clk[VF610_CLK_PLL1]->name, ret); > + return ret; > + } > + > + ret = clk_set_parent(clk[VF610_CLK_PLL1_PFD_SEL], clk[VF610_CLK_PLL1_SYS]); > + if (ret < 0) { > + pr_crit("Unable to re-parent '%s'\n", > + clk[VF610_CLK_PLL1_PFD_SEL]->name); > + return ret; > + } > + > + ret = clk_set_parent(clk[VF610_CLK_SYS_SEL], clk[VF610_CLK_PLL1_PFD_SEL]); > + if (ret < 0) { > + pr_crit("Unable to re-parent '%s'\n", > + clk[VF610_CLK_SYS_SEL]->name); > + return ret; > + } > + > + /* > + * imx_clk_divider has no error path in its set_rate hook > + */ > + clk_set_rate(clk[VF610_CLK_SYS_BUS], clk_get_rate(clk[VF610_CLK_SYS_SEL])); > + clk_set_rate(clk[VF610_CLK_PLATFORM_BUS], clk_get_rate(clk[VF610_CLK_SYS_BUS]) / 3); > + > + return ret; > +} > + > +static int vf610_switch_cpu_clock_to_400mhz(void) > +{ > + int ret; > + uint32_t cr117; > + void * __iomem ddrmc = IOMEM(VF610_DDR_BASE_ADDR); > + > + if (clk_get_parent(clk[VF610_CLK_DDR_SEL]) != clk[VF610_CLK_PLL2_PFD2]) { > + pr_warn("DDRC is clocked by PLL1, can't switch CPU clock"); > + return -EINVAL; > + } > + > + ret = clk_set_parent(clk[VF610_CLK_PLL2_PFD_SEL], clk[VF610_CLK_PLL2_PFD2]); > + if (ret < 0) { > + pr_crit("Unable to re-parent '%s'\n", > + clk[VF610_CLK_PLL2_PFD_SEL]->name); > + return ret; > + } > + > + ret = clk_set_parent(clk[VF610_CLK_SYS_SEL], clk[VF610_CLK_PLL2_PFD_SEL]); > + if (ret < 0) { > + pr_crit("Unable to re-parent '%s'\n", > + clk[VF610_CLK_SYS_SEL]->name); > + return ret; > + } > + > + /* > + * imx_clk_divider has no error path in its set_rate hook > + */ > + clk_set_rate(clk[VF610_CLK_SYS_BUS], clk_get_rate(clk[VF610_CLK_SYS_SEL])); > + clk_set_rate(clk[VF610_CLK_PLATFORM_BUS], clk_get_rate(clk[VF610_CLK_SYS_BUS]) / 3); > + > + /* > + * Now that we are running off of the same clock as DDRMC we > + * shouldn't need to use clock domain corssing FIFO and s/corssing/crossing/? > + * asynchronous mode and instead can swithch to sychronous > + * mode for AXI0 accesses > + */ > + cr117 = readl(ddrmc + DDRMC_CR117); > + cr117 |= DDRMC_CR117_AXI0_FITYPEREG_SYNC; > + writel(cr117, ddrmc + DDRMC_CR117); > + > + return 0; > +} > + > +static int vf610_switch_cpu_clock(void) > +{ > + int ret; > + struct device_node *ocotp_node; > + struct cdev *ocotp; > + uint32_t speed_grading; > + > + if (!of_machine_is_compatible("fsl,vf610")) > + return 0; > + > + ocotp_node = of_find_compatible_node(NULL, NULL, "fsl,vf610-ocotp"); > + if (!ocotp_node) { > + pr_err("Unable to find OCOTP DT node\n"); > + return -ENODEV; > + } > + > + ocotp = cdev_by_device_node(ocotp_node); > + if (!ocotp) { > + pr_err("No OCOTP character device\n"); > + return -ENODEV; > + } > + > + ret = cdev_read(ocotp, &speed_grading, sizeof(speed_grading), > + OCOTP_SPEED_GRADING_OFFSET, 0); > + if (ret != sizeof(speed_grading)) { > + pr_err("Failed to read speed grading data\n"); > + return ret < 0 ? ret : -EIO; > + } There's a better way to access ocotp registers. see imx_ocotp_read_field(). > + > + speed_grading >>= OCOTP_SPEED_GRADING_SHIFT; > + speed_grading &= OCOTP_SPEED_GRADING_MASK; > + > + switch (speed_grading) { > + default: > + pr_err("Unknown CPU speed grading %x\n", speed_grading); > + return -EINVAL; > + > + case VF610_SPEED_266: > + return 0; > + > + case VF610_SPEED_500: > + ret = vf610_switch_cpu_clock_to_500mhz(); > + break; > + > + case VF610_SPEED_400: > + ret = vf610_switch_cpu_clock_to_400mhz(); > + break; > + } > + > + clock_notifier_call_chain(); > + return ret; > +} > +/* > + * We can probably gain a bit of a boot speed if we switch CPU clock > + * earlier, but if we do this we'd need to figure out a way how to > + * re-adjust the baud rate settings of the UART for DEBUG_LL > + * functionality, or, accept the fact that it will be unavailbe after s/unavailbe/unavailable/ Sascha -- Pengutronix e.K. | | Industrial Linux Solutions | http://www.pengutronix.de/ | Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0 | Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 | _______________________________________________ barebox mailing list barebox@xxxxxxxxxxxxxxxxxxx http://lists.infradead.org/mailman/listinfo/barebox