// SPDX-License-Identifier: GPL-2.0-only /* * ARTPEC-6 clock initialization * * Copyright 2015-2016 Axis Communications AB. */ #include <linux/clk-provider.h> #include <linux/device.h> #include <linux/io.h> #include <linux/of.h> #include <linux/of_address.h> #include <linux/platform_device.h> #include <linux/slab.h> #include <dt-bindings/clock/axis,artpec6-clkctrl.h> #define NUM_I2S_CLOCKS 2 struct artpec6_clkctrl_drvdata { struct clk *clk_table[ARTPEC6_CLK_NUMCLOCKS]; void __iomem *syscon_base; struct clk_onecell_data clk_data; spinlock_t i2scfg_lock; }; static struct artpec6_clkctrl_drvdata *clkdata; static const char *const i2s_clk_names[NUM_I2S_CLOCKS] = { "i2s0", "i2s1", }; static const int i2s_clk_indexes[NUM_I2S_CLOCKS] = { ARTPEC6_CLK_I2S0_CLK, ARTPEC6_CLK_I2S1_CLK, }; static void of_artpec6_clkctrl_setup(struct device_node *np) { int i; const char *sys_refclk_name; u32 pll_mode, pll_m, pll_n; struct clk **clks; /* Mandatory parent clock. */ i = of_property_match_string(np, "clock-names", "sys_refclk"); if (i < 0) return; sys_refclk_name = of_clk_get_parent_name(np, i); clkdata = kzalloc(sizeof(*clkdata), GFP_KERNEL); if (!clkdata) return; clks = clkdata->clk_table; for (i = 0; i < ARTPEC6_CLK_NUMCLOCKS; ++i) clks[i] = ERR_PTR(-EPROBE_DEFER); clkdata->syscon_base = of_iomap(np, 0); BUG_ON(clkdata->syscon_base == NULL); /* Read PLL1 factors configured by boot strap pins. */ pll_mode = (readl(clkdata->syscon_base) >> 6) & 3; switch (pll_mode) { case 0: /* DDR3-2133 mode */ pll_m = 4; pll_n = 85; break; case 1: /* DDR3-1866 mode */ pll_m = 6; pll_n = 112; break; case 2: /* DDR3-1600 mode */ pll_m = 4; pll_n = 64; break; case 3: /* DDR3-1333 mode */ pll_m = 8; pll_n = 106; break; } clks[ARTPEC6_CLK_CPU] = clk_register_fixed_factor(NULL, "cpu", sys_refclk_name, 0, pll_n, pll_m); clks[ARTPEC6_CLK_CPU_PERIPH] = clk_register_fixed_factor(NULL, "cpu_periph", "cpu", 0, 1, 2); /* EPROBE_DEFER on the apb_clock is not handled in amba devices. */ clks[ARTPEC6_CLK_UART_PCLK] = clk_register_fixed_factor(NULL, "uart_pclk", "cpu", 0, 1, 8); clks[ARTPEC6_CLK_UART_REFCLK] = clk_register_fixed_rate(NULL, "uart_ref", sys_refclk_name, 0, 50000000); clks[ARTPEC6_CLK_SPI_PCLK] = clk_register_fixed_factor(NULL, "spi_pclk", "cpu", 0, 1, 8); clks[ARTPEC6_CLK_SPI_SSPCLK] = clk_register_fixed_rate(NULL, "spi_sspclk", sys_refclk_name, 0, 50000000); clks[ARTPEC6_CLK_DBG_PCLK] = clk_register_fixed_factor(NULL, "dbg_pclk", "cpu", 0, 1, 8); clkdata->clk_data.clks = clkdata->clk_table; clkdata->clk_data.clk_num = ARTPEC6_CLK_NUMCLOCKS; of_clk_add_provider(np, of_clk_src_onecell_get, &clkdata->clk_data); } CLK_OF_DECLARE_DRIVER(artpec6_clkctrl, "axis,artpec6-clkctrl", of_artpec6_clkctrl_setup); static int artpec6_clkctrl_probe(struct platform_device *pdev) { int propidx; struct device_node *np = pdev->dev.of_node; struct device *dev = &pdev->dev; struct clk **clks = clkdata->clk_table; const char *sys_refclk_name; const char *i2s_refclk_name = NULL; const char *frac_clk_name[2] = { NULL, NULL }; const char *i2s_mux_parents[2]; u32 muxreg; int i; int err = 0; /* Mandatory parent clock. */ propidx = of_property_match_string(np, "clock-names", "sys_refclk"); if (propidx < 0) return -EINVAL; sys_refclk_name = of_clk_get_parent_name(np, propidx); /* Find clock names of optional parent clocks. */ propidx = of_property_match_string(np, "clock-names", "i2s_refclk"); if (propidx >= 0) i2s_refclk_name = of_clk_get_parent_name(np, propidx); propidx = of_property_match_string(np, "clock-names", "frac_clk0"); if (propidx >= 0) frac_clk_name[0] = of_clk_get_parent_name(np, propidx); propidx = of_property_match_string(np, "clock-names", "frac_clk1"); if (propidx >= 0) frac_clk_name[1] = of_clk_get_parent_name(np, propidx); spin_lock_init(&clkdata->i2scfg_lock); clks[ARTPEC6_CLK_NAND_CLKA] = clk_register_fixed_factor(dev, "nand_clka", "cpu", 0, 1, 8); clks[ARTPEC6_CLK_NAND_CLKB] = clk_register_fixed_rate(dev, "nand_clkb", sys_refclk_name, 0, 100000000); clks[ARTPEC6_CLK_ETH_ACLK] = clk_register_fixed_factor(dev, "eth_aclk", "cpu", 0, 1, 4); clks[ARTPEC6_CLK_DMA_ACLK] = clk_register_fixed_factor(dev, "dma_aclk", "cpu", 0, 1, 4); clks[ARTPEC6_CLK_PTP_REF] = clk_register_fixed_rate(dev, "ptp_ref", sys_refclk_name, 0, 100000000); clks[ARTPEC6_CLK_SD_PCLK] = clk_register_fixed_rate(dev, "sd_pclk", sys_refclk_name, 0, 100000000); clks[ARTPEC6_CLK_SD_IMCLK] = clk_register_fixed_rate(dev, "sd_imclk", sys_refclk_name, 0, 100000000); clks[ARTPEC6_CLK_I2S_HST] = clk_register_fixed_factor(dev, "i2s_hst", "cpu", 0, 1, 8); for (i = 0; i < NUM_I2S_CLOCKS; ++i) { if (i2s_refclk_name && frac_clk_name[i]) { i2s_mux_parents[0] = frac_clk_name[i]; i2s_mux_parents[1] = i2s_refclk_name; clks[i2s_clk_indexes[i]] = clk_register_mux(dev, i2s_clk_names[i], i2s_mux_parents, 2, CLK_SET_RATE_NO_REPARENT | CLK_SET_RATE_PARENT, clkdata->syscon_base + 0x14, i, 1, 0, &clkdata->i2scfg_lock); } else if (frac_clk_name[i]) { /* Lock the mux for internal clock reference. */ muxreg = readl(clkdata->syscon_base + 0x14); muxreg &= ~BIT(i); writel(muxreg, clkdata->syscon_base + 0x14); clks[i2s_clk_indexes[i]] = clk_register_fixed_factor(dev, i2s_clk_names[i], frac_clk_name[i], 0, 1, 1); } else if (i2s_refclk_name) { /* Lock the mux for external clock reference. */ muxreg = readl(clkdata->syscon_base + 0x14); muxreg |= BIT(i); writel(muxreg, clkdata->syscon_base + 0x14); clks[i2s_clk_indexes[i]] = clk_register_fixed_factor(dev, i2s_clk_names[i], i2s_refclk_name, 0, 1, 1); } } clks[ARTPEC6_CLK_I2C] = clk_register_fixed_rate(dev, "i2c", sys_refclk_name, 0, 100000000); clks[ARTPEC6_CLK_SYS_TIMER] = clk_register_fixed_rate(dev, "timer", sys_refclk_name, 0, 100000000); clks[ARTPEC6_CLK_FRACDIV_IN] = clk_register_fixed_rate(dev, "fracdiv_in", sys_refclk_name, 0, 600000000); for (i = 0; i < ARTPEC6_CLK_NUMCLOCKS; ++i) { if (IS_ERR(clks[i]) && PTR_ERR(clks[i]) != -EPROBE_DEFER) { dev_err(dev, "Failed to register clock at index %d err=%ld\n", i, PTR_ERR(clks[i])); err = PTR_ERR(clks[i]); } } return err; } static const struct of_device_id artpec_clkctrl_of_match[] = { { .compatible = "axis,artpec6-clkctrl" }, {} }; static struct platform_driver artpec6_clkctrl_driver = { .probe = artpec6_clkctrl_probe, .driver = { .name = "artpec6_clkctrl", .of_match_table = artpec_clkctrl_of_match, }, }; builtin_platform_driver(artpec6_clkctrl_driver);