From mboxrd@z Thu Jan 1 00:00:00 1970 From: Ley Foon Tan Date: Thu, 09 Aug 2018 16:39:49 +0800 Subject: [U-Boot] [PATCH] clk: socfpga: Add initial Arria10 clock driver In-Reply-To: <20180808201256.2135-1-marex@denx.de> References: <20180808201256.2135-1-marex@denx.de> Message-ID: <1533803989.38452.11.camel@intel.com> List-Id: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 8bit To: u-boot@lists.denx.de On Wed, 2018-08-08 at 22:12 +0200, Marek Vasut wrote: > Add clock driver for the Arria10, which allows reading the clock > frequency from all the clock described in the DT. The driver also > allows enabling and disabling the clock. Reconfiguring frequency > is not supported thus far. > > Since the DT bindings for the SoCFPGA clock are massively misdesigned > and the handoff DT adds additional incorrectly described entries to > the DT, the driver contains workarounds which attempt to rectify all > of those problems. > > Signed-off-by: Marek Vasut > Cc: Chin Liang See > Cc: Dinh Nguyen > Cc: Ley Foon Tan > --- >  drivers/clk/Makefile             |   1 + >  drivers/clk/altera/Makefile      |   7 + >  drivers/clk/altera/clk-arria10.c | 363 > +++++++++++++++++++++++++++++++++++++++ >  3 files changed, 371 insertions(+) >  create mode 100644 drivers/clk/altera/Makefile >  create mode 100644 drivers/clk/altera/clk-arria10.c > > diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile > index 146283c723..034bf44078 100644 > --- a/drivers/clk/Makefile > +++ b/drivers/clk/Makefile > @@ -11,6 +11,7 @@ obj-y += tegra/ >  obj-$(CONFIG_ARCH_ASPEED) += aspeed/ >  obj-$(CONFIG_ARCH_MESON) += clk_meson.o >  obj-$(CONFIG_ARCH_ROCKCHIP) += rockchip/ > +obj-$(CONFIG_ARCH_SOCFPGA) += altera/ >  obj-$(CONFIG_CLK_AT91) += at91/ >  obj-$(CONFIG_CLK_MVEBU) += mvebu/ >  obj-$(CONFIG_CLK_BCM6345) += clk_bcm6345.o > diff --git a/drivers/clk/altera/Makefile > b/drivers/clk/altera/Makefile > new file mode 100644 > index 0000000000..2542b7f51b > --- /dev/null > +++ b/drivers/clk/altera/Makefile > @@ -0,0 +1,7 @@ > +# > +# Copyright (C) 2018 Marek Vasut > +# > +# SPDX-License-Identifier:      GPL-2.0+ > +# > + > +obj-$(CONFIG_TARGET_SOCFPGA_ARRIA10) += clk-arria10.o > diff --git a/drivers/clk/altera/clk-arria10.c > b/drivers/clk/altera/clk-arria10.c > new file mode 100644 > index 0000000000..78102c760d > --- /dev/null > +++ b/drivers/clk/altera/clk-arria10.c > @@ -0,0 +1,363 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Copyright (C) 2018 Marek Vasut > + */ > + > +#include > +#include > +#include > +#include > +#include > +#include > + > +#include > + > +enum socfpga_a10_clk_type { > + SOCFPGA_A10_CLK_MAIN_PLL, > + SOCFPGA_A10_CLK_PER_PLL, > + SOCFPGA_A10_CLK_PERIP_CLK, > + SOCFPGA_A10_CLK_GATE_CLK, > + SOCFPGA_A10_CLK_UNKNOWN_CLK, > +}; > + > +struct socfpga_a10_clk_platdata { > + enum socfpga_a10_clk_type type; > + struct clk_bulk clks; > + u32 regs; > + /* Fixed divider */ > + u16 fix_div; > + /* Control register */ > + u16 ctl_reg; > + /* Divider register */ > + u16 div_reg; > + u8 div_len; > + u8 div_off; > + /* Clock gating register */ > + u16 gate_reg; > + u8 gate_bit; > +}; > + > +static int socfpga_a10_clk_get_upstream(struct clk *clk, struct clk > **upclk) > +{ > + struct socfpga_a10_clk_platdata *plat = > dev_get_platdata(clk->dev); > + u32 reg, maxval; > + > + if (plat->clks.count == 0) > + return 0; > + > + if (plat->clks.count == 1) { > + *upclk = &plat->clks.clks[0]; > + return 0; > + } > + > + if (!plat->ctl_reg) { > + dev_err(clk->dev, "Invalid control register\n"); > + return -EINVAL; > + } > + > + reg = readl(plat->regs + plat->ctl_reg); > + > + /* Assume PLLs are ON for now */ > + if (plat->type == SOCFPGA_A10_CLK_MAIN_PLL) { > + reg = (reg >> 8) & 0x3; > + maxval = 2; > + } else if (plat->type == SOCFPGA_A10_CLK_PER_PLL) { > + reg = (reg >> 8) & 0x3; > + maxval = 3; > + } else { > + reg = (reg >> 16) & 0x7; > + maxval = 4; > + } > + > + if (reg > maxval) { > + dev_err(clk->dev, "Invalid clock source\n"); > + return -EINVAL; > + } > + > + *upclk = &plat->clks.clks[reg]; > + return 0; > +} > + > +static int socfpga_a10_clk_endisable(struct clk *clk, bool enable) > +{ > + struct socfpga_a10_clk_platdata *plat = > dev_get_platdata(clk->dev); > + struct clk *upclk = NULL; > + int ret; > + > + if (!enable && plat->gate_reg) > + clrbits_le32(plat->regs + plat->gate_reg, BIT(plat- > >gate_bit)); > + > + ret = socfpga_a10_clk_get_upstream(clk, &upclk); > + if (ret) > + return ret; > + > + if (upclk) { > + if (enable) > + clk_enable(upclk); > + else > + clk_disable(upclk); > + } > + > + if (enable && plat->gate_reg) > + setbits_le32(plat->regs + plat->gate_reg, BIT(plat- > >gate_bit)); > + > + return 0; > +} > + > +static int socfpga_a10_clk_enable(struct clk *clk) > +{ > + return socfpga_a10_clk_endisable(clk, true); > +} > + > +static int socfpga_a10_clk_disable(struct clk *clk) > +{ > + return socfpga_a10_clk_endisable(clk, false); > +} > + > +static ulong socfpga_a10_clk_get_rate(struct clk *clk) > +{ > + struct socfpga_a10_clk_platdata *plat = > dev_get_platdata(clk->dev); > + struct clk *upclk = NULL; > + ulong rate = 0, reg, numer, denom; > + int ret; > + > + ret = socfpga_a10_clk_get_upstream(clk, &upclk); > + if (ret || !upclk) > + return 0; > + > + rate = clk_get_rate(upclk); > + > + if (plat->type == SOCFPGA_A10_CLK_MAIN_PLL) { > + reg = readl(plat->regs + plat->ctl_reg + 4); > /* VCO1 */ > + numer = reg & CLKMGR_MAINPLL_VCO1_NUMER_MSK; > + denom = (reg >> CLKMGR_MAINPLL_VCO1_DENOM_LSB) & > + CLKMGR_MAINPLL_VCO1_DENOM_MSK; > + > + rate /= denom + 1; > + rate *= numer + 1; > + } else if (plat->type == SOCFPGA_A10_CLK_PER_PLL) { > + reg = readl(plat->regs + plat->ctl_reg + 4); > /* VCO1 */ > + numer = reg & CLKMGR_PERPLL_VCO1_NUMER_MSK; > + denom = (reg >> CLKMGR_PERPLL_VCO1_DENOM_LSB) & > + CLKMGR_PERPLL_VCO1_DENOM_MSK; > + > + rate /= denom + 1; > + rate *= numer + 1; > + } else { > + rate /= plat->fix_div; > + > + if (plat->fix_div == 1 && plat->ctl_reg) { > + reg = readl(plat->regs + plat->ctl_reg); > + reg &= 0x7ff; > + rate /= reg + 1; > + } > + > + if (plat->div_reg) { > + reg = readl(plat->regs + plat->div_reg); > + reg >>= plat->div_off; > + reg &= (1 << plat->div_len) - 1; > + if (plat->type == SOCFPGA_A10_CLK_PERIP_CLK) > + rate /= reg + 1; > + if (plat->type == SOCFPGA_A10_CLK_GATE_CLK) > + rate /= 1 << reg; > + } > + } > + > + return rate; > +} > + > +static struct clk_ops socfpga_a10_clk_ops = { > + .enable = socfpga_a10_clk_enable, > + .disable = socfpga_a10_clk_disable, > + .get_rate = socfpga_a10_clk_get_rate, > +}; > + > +/* > + * This workaround tries to fix the massively broken generated > "handoff" DT, > + * which contains duplicate clock nodes without any connection to > the clock > + * manager DT node. Yet, those "handoff" DT nodes contain > configuration of > + * the fixed input clock of the Arria10 which are missing from the > base DT > + * for Arria10. > + * > + * This workaround sets up upstream clock for the fixed input clocks > of the > + * A10 described in the base DT such that they map to the fixed > clock from > + * the "handoff" DT. This does not fully match how the clock look on > the > + * A10, but it is the least intrusive way to fix this mess. > + */ > +static void socfpga_a10_handoff_workaround(struct udevice *dev) > +{ > + struct socfpga_a10_clk_platdata *plat = > dev_get_platdata(dev); > + const void *fdt = gd->fdt_blob; > + struct clk_bulk *bulk = &plat->clks; > + int i, ret, offset = dev_of_offset(dev); > + static const char * const socfpga_a10_fixedclk_map[] = { > + "osc1", "altera_arria10_hps_eosc1", > + "cb_intosc_ls_clk", > "altera_arria10_hps_cb_intosc_ls", > + "f2s_free_clk", "altera_arria10_hps_f2h_free", > + }; > + > + if (fdt_node_check_compatible(fdt, offset, "fixed-clock")) > + return; > + > + for (i = 0; i < ARRAY_SIZE(socfpga_a10_fixedclk_map); i += > 2) > + if (!strcmp(dev->name, socfpga_a10_fixedclk_map[i])) > + break; > + > + if (i == ARRAY_SIZE(socfpga_a10_fixedclk_map)) > + return; > + > + ret = uclass_get_device_by_name(UCLASS_CLK, > + socfpga_a10_fixedclk_map[i + > 1], &dev); > + if (ret) > + return; > + > + bulk->count = 1; > + bulk->clks = devm_kcalloc(dev, bulk->count, > +   sizeof(struct clk), GFP_KERNEL); > + if (!bulk->clks) > + return; > + > + ret = clk_request(dev, &bulk->clks[0]); > + if (ret) > + free(bulk->clks); > +} > + > +static int socfpga_a10_clk_bind(struct udevice *dev) > +{ > + const void *fdt = gd->fdt_blob; > + int offset = dev_of_offset(dev); > + bool pre_reloc_only = !(gd->flags & GD_FLG_RELOC); > + const char *name; > + int ret; > + > + for (offset = fdt_first_subnode(fdt, offset); > +      offset > 0; > +      offset = fdt_next_subnode(fdt, offset)) { > + name = fdt_get_name(fdt, offset, NULL); > + if (!name) > + return -EINVAL; > + > + if (!strcmp(name, "clocks")) { > + offset = fdt_first_subnode(fdt, offset); > + name = fdt_get_name(fdt, offset, NULL); > + if (!name) > + return -EINVAL; > + } > + > + /* Filter out supported sub-clock */ > + if (fdt_node_check_compatible(fdt, offset, > +       "altr,socfpga-a10-pll- > clock") && > +     fdt_node_check_compatible(fdt, offset, > +       "altr,socfpga-a10- > perip-clk") && > +     fdt_node_check_compatible(fdt, offset, > +       "altr,socfpga-a10- > gate-clk") && > +     fdt_node_check_compatible(fdt, offset, "fixed- > clock")) > + continue; > + > + if (pre_reloc_only && !dm_fdt_pre_reloc(fdt, > offset)) > + continue; > + > + ret = device_bind_driver_to_node(dev, "clk-a10", > name, > +  offset_to_ofnode(of > fset), > +  NULL); > + if (ret) > + return ret; > + } > + > + return 0; > +} > + > +static int socfpga_a10_clk_probe(struct udevice *dev) > +{ > + struct socfpga_a10_clk_platdata *plat = > dev_get_platdata(dev); > + const void *fdt = gd->fdt_blob; > + int offset = dev_of_offset(dev); > + > + clk_get_bulk(dev, &plat->clks); > + > + socfpga_a10_handoff_workaround(dev); > + > + if (!fdt_node_check_compatible(fdt, offset, > +        "altr,socfpga-a10-pll- > clock")) { > + /* Main PLL has 3 upstream clock */ > + if (plat->clks.count == 3) > + plat->type = SOCFPGA_A10_CLK_MAIN_PLL; > + else > + plat->type = SOCFPGA_A10_CLK_PER_PLL; > + } else if (!fdt_node_check_compatible(fdt, offset, > +       "altr,socfpga-a10- > perip-clk")) { > + plat->type = SOCFPGA_A10_CLK_PERIP_CLK; > + } else if (!fdt_node_check_compatible(fdt, offset, > +       "altr,socfpga-a10- > gate-clk")) { > + plat->type = SOCFPGA_A10_CLK_GATE_CLK; > + } else { > + plat->type = SOCFPGA_A10_CLK_UNKNOWN_CLK; > + } > + > + return 0; > +} > + > +static int socfpga_a10_ofdata_to_platdata(struct udevice *dev) > +{ > + struct socfpga_a10_clk_platdata *plat = > dev_get_platdata(dev); > + struct socfpga_a10_clk_platdata *pplat; > + struct udevice *pdev; > + const void *fdt = gd->fdt_blob; > + unsigned int divreg[3], gatereg[2]; > + int ret, offset = dev_of_offset(dev); > + u32 regs; > + > + regs = dev_read_u32_default(dev, "reg", 0x0); > + > + if (!fdt_node_check_compatible(fdt, offset, "altr,clk-mgr")) > { > + plat->regs = devfdt_get_addr(dev); > + } else { > + pdev = dev_get_parent(dev); > + if (!pdev) > + return -ENODEV; > + > + pplat = dev_get_platdata(pdev); > + if (!pplat) > + return -EINVAL; > + > + plat->ctl_reg = regs; > + plat->regs = pplat->regs; > + } > + > + plat->type = SOCFPGA_A10_CLK_UNKNOWN_CLK; > + > + plat->fix_div = dev_read_u32_default(dev, "fixed-divider", > 1); > + > + ret = dev_read_u32_array(dev, "div-reg", divreg, > ARRAY_SIZE(divreg)); > + if (!ret) { > + plat->div_reg = divreg[0]; > + plat->div_len = divreg[2]; > + plat->div_off = divreg[1]; > + } > + > + ret = dev_read_u32_array(dev, "clk-gate", gatereg, > ARRAY_SIZE(gatereg)); > + if (!ret) { > + plat->gate_reg = gatereg[0]; > + plat->gate_bit = gatereg[1]; > + } > + > + return 0; > +} > + > +static const struct udevice_id socfpga_a10_clk_match[] = { > + { .compatible = "altr,clk-mgr" }, > + {} > +}; > + > +U_BOOT_DRIVER(socfpga_a10_clk) = { > + .name = "clk-a10", > + .id = UCLASS_CLK, > + .flags = DM_FLAG_PRE_RELOC, > + .of_match = socfpga_a10_clk_match, > + .ops = &socfpga_a10_clk_ops, > + .bind = socfpga_a10_clk_bind, > + .probe = socfpga_a10_clk_probe, > + .ofdata_to_platdata = socfpga_a10_ofdata_to_platdata, > + > + .platdata_auto_alloc_size = sizeof(struct > socfpga_a10_clk_platdata), > +}; Reviewed-by: Ley Foon Tan