diff options
Diffstat (limited to 'drivers/clk/sunxi/clk-sunxi.c')
-rw-r--r-- | drivers/clk/sunxi/clk-sunxi.c | 209 |
1 files changed, 209 insertions, 0 deletions
diff --git a/drivers/clk/sunxi/clk-sunxi.c b/drivers/clk/sunxi/clk-sunxi.c index 838b22aa8b67..8195f543c2fc 100644 --- a/drivers/clk/sunxi/clk-sunxi.c +++ b/drivers/clk/sunxi/clk-sunxi.c @@ -17,12 +17,16 @@ #include <linux/clk.h> #include <linux/clk-provider.h> #include <linux/clkdev.h> +#include <linux/delay.h> +#include <linux/mfd/syscon.h> #include <linux/of.h> #include <linux/of_address.h> +#include <linux/regmap.h> #include <linux/reset-controller.h> #include <linux/slab.h> #include <linux/spinlock.h> #include <linux/log2.h> +#include <asm/io.h> #include "clk-factors.h" @@ -200,6 +204,32 @@ static void sun8i_a23_get_pll1_factors(struct factors_request *req) } /** + * sun9i_a80_get_pllcxcpux_factors() - calculates n and p factors for PLLCxCPUX + * PLLCxCPUX rate is calculated as follows + * rate = parent_rate * n / p; + * parent_rate should always be 24MHz + */ +static void sun9i_a80_get_pllcxcpux_factors(struct factors_request *req) +{ + /* We can operate only on MHz, this will make our life easier. */ + u32 freq_mhz = req->rate / 1000000; + u32 parent_freq_mhz = req->parent_rate / 1000000; + + /* Round down the frequency to the closest multiple of 24. */ + u32 round_freq_24 = 24 * (freq_mhz / 24); + + /* We don't model the external divider (P), which is required for + rates below 288MHz, so clip to 288MHz to avoid violating the + constraints on N. */ + if (round_freq_24 < 288) + round_freq_24 = 288; + + req->rate = round_freq_24 * 1000000; + req->n = freq_mhz / parent_freq_mhz; + req->p = 0; +} + +/** * sun4i_get_pll5_factors() - calculates n, k factors for PLL5 * PLL5 rate is calculated as follows * rate = parent_rate * n * (k + 1) @@ -453,6 +483,13 @@ static const struct clk_factors_config sun8i_a23_pll1_config = { .n_start = 1, }; +static const struct clk_factors_config sun9i_a80_pllcxcpux_config = { + .nshift = 8, + .nwidth = 8, + .pshift = 16, + .pwidth = 1, +}; + static const struct clk_factors_config sun4i_pll5_config = { .nshift = 8, .nwidth = 5, @@ -513,6 +550,12 @@ static const struct factors_data sun8i_a23_pll1_data __initconst = { .getter = sun8i_a23_get_pll1_factors, }; +static const struct factors_data sun9i_a80_pllcxcpux_data __initconst = { + .enable = 31, + .table = &sun9i_a80_pllcxcpux_config, + .getter = sun9i_a80_get_pllcxcpux_factors, +}; + static const struct factors_data sun7i_a20_pll4_data __initconst = { .enable = 31, .table = &sun4i_pll5_config, @@ -597,6 +640,15 @@ static void __init sun8i_pll1_clk_setup(struct device_node *node) CLK_OF_DECLARE(sun8i_pll1, "allwinner,sun8i-a23-pll1-clk", sun8i_pll1_clk_setup); +static void __init sun9i_pllcxcpux_clk_setup(struct device_node *node) +{ + sunxi_factors_clk_setup(node, &sun9i_a80_pllcxcpux_data); +} +CLK_OF_DECLARE(sun9i_pllc0cpux, "allwinner,sun9i-a80-pllc0cpux-clk", + sun9i_pllcxcpux_clk_setup); +CLK_OF_DECLARE(sun9i_pllc1cpux, "allwinner,sun9i-a80-pllc1cpux-clk", + sun9i_pllcxcpux_clk_setup); + static void __init sun7i_pll4_clk_setup(struct device_node *node) { sunxi_factors_clk_setup(node, &sun7i_a20_pll4_data); @@ -655,6 +707,14 @@ static const struct mux_data sun8i_h3_ahb2_mux_data __initconst = { .shift = 0, }; +static const struct mux_data sun9i_c0cpux_mux_data __initconst = { + .shift = 0, +}; + +static const struct mux_data sun9i_c1cpux_mux_data __initconst = { + .shift = 8, +}; + static struct clk * __init sunxi_mux_clk_setup(struct device_node *node, const struct mux_data *data) { @@ -731,6 +791,155 @@ static void __init sun8i_ahb2_clk_setup(struct device_node *node) CLK_OF_DECLARE(sun8i_ahb2, "allwinner,sun8i-h3-ahb2-clk", sun8i_ahb2_clk_setup); +/* + * This notifier function is called for the pre-rate and post-rate change + * notifications of the parent clock of AXI0 (C0CPUX) and AXI1 (C1CPUX) + * to ensure that AXI0 and AXI1 remain as high as possible and below 600MHz + * when using DVFS. + */ + +#define SUN9I_CxCPUXCLKCFG_AXIMASK (0x7) +#define SUN9I_AXI_CLKMAX (600000000) + +struct sun9i_cpuclk_nb { + struct regmap* clkcfg; + struct notifier_block clk_nb; +}; + +static int sun9i_cpuclk_read_axi_rate(struct regmap *clkcfg) +{ + u32 val; + + /* Read the current divider settings */ + regmap_read(clkcfg, 0, &val); + return val & SUN9I_CxCPUXCLKCFG_AXIMASK; +} + +static int sun9i_cpuclk_calc_axi_rate(struct regmap *clkcfg, unsigned long new_rate) +{ + /* Calculate the target divider for the new clock rate */ + return (new_rate - 1) / SUN9I_AXI_CLKMAX; +} + +static int sun9i_cpuclk_pre_rate_change(struct regmap *clkcfg, struct clk_notifier_data *ndata) +{ + u32 old = sun9i_cpuclk_read_axi_rate(clkcfg); + u32 new = sun9i_cpuclk_calc_axi_rate(clkcfg, ndata->new_rate); + + /* If the new_rate requires a larger divider (i.e. lower AXI rate), we must + change the divider before the parent changes its rate. */ + if (new > old) { + pr_debug("%s: increasing AXI div: 0x%x -> 0x%x\n", __func__, old, new); + regmap_update_bits(clkcfg, 0, SUN9I_CxCPUXCLKCFG_AXIMASK, new); + } + + return 0; +} + +static int sun9i_cpuclk_post_rate_change(struct regmap *clkcfg, struct clk_notifier_data *ndata) +{ + u32 old = sun9i_cpuclk_read_axi_rate(clkcfg); + u32 new = sun9i_cpuclk_calc_axi_rate(clkcfg, ndata->new_rate); + + /* If the new_rate supports a smaller divider (i.e. faster AXI rate), we can now + change the divider after the parent has completed its rate change. */ + if (new < old) { + pr_debug("%s: decreasing AXI div: 0x%x -> 0x%x\n", __func__, old, new); + regmap_update_bits(clkcfg, 0, SUN9I_CxCPUXCLKCFG_AXIMASK, new); + } + + return 0; +} + +static int sun9i_cpuclk_notifier_cb(struct notifier_block *nb, + unsigned long event, void *data) +{ + struct clk_notifier_data *ndata = data; + struct sun9i_cpuclk_nb *cpuclk_nb = container_of(nb, struct sun9i_cpuclk_nb, clk_nb); + struct regmap *clkcfg = cpuclk_nb->clkcfg; + int ret; + + pr_debug("%s: event %lu, old_rate %lu, new_rate: %lu\n", + __func__, event, ndata->old_rate, ndata->new_rate); + + if (event == PRE_RATE_CHANGE) + ret = sun9i_cpuclk_pre_rate_change(clkcfg, ndata); + else if (event == POST_RATE_CHANGE) + ret = sun9i_cpuclk_post_rate_change(clkcfg, ndata); + + return notifier_from_errno(ret); +} + +static void __init sun9i_cxcpux_notifier_setup(struct device_node *node, struct clk* clk) +{ + struct sun9i_cpuclk_nb *nb; + struct regmap *clkcfg; + u32 val; + int ret; + + clkcfg = syscon_regmap_lookup_by_phandle(node, "allwinner,sun9i-a80-clkcfg"); + if (!clkcfg) { + pr_warn("%s: could not retrieve CLKCFG via allwinner,sun9i-a80-clkcfg property\n", + of_node_full_name(node)); + return; + } + + if (of_property_read_u32(node, "allwinner,sun9i-a80-clkcfg-default", &val)) + pr_warn("%s: no defaults (allwinner,sun9i-a80-clkcfg-default) for CLKCFG\n", + of_node_full_name(node)); + else + regmap_write(clkcfg, 0, val); + + nb = kzalloc(sizeof(*nb), GFP_KERNEL); + if (!nb) { + pr_err("%s: failed to allocate notifier block for clk rate changes\n", + of_node_full_name(node)); + return; + } + nb->clkcfg = clkcfg; + nb->clk_nb.notifier_call = sun9i_cpuclk_notifier_cb; + ret = clk_notifier_register(clk, &nb->clk_nb); + if (ret) { + pr_err("%s: failed to register clock notifier\n", + of_node_full_name(node)); + kfree(nb); + return; + } +} + +static void __init sun9i_c0cpux_clk_setup(struct device_node *node) +{ + struct clk *clk; + + clk = sunxi_mux_clk_setup(node, &sun9i_c0cpux_mux_data); + if (!clk) + return; + + sun9i_cxcpux_notifier_setup(node, clk); + + /* Protect CPU clock */ + __clk_get(clk); + clk_prepare_enable(clk); +} +CLK_OF_DECLARE(sun9i_c0cpux, "allwinner,sun9i-a80-c0cpux-clk", + sun9i_c0cpux_clk_setup); + +static void __init sun9i_c1cpux_clk_setup(struct device_node *node) +{ + struct clk *clk; + + clk = sunxi_mux_clk_setup(node, &sun9i_c1cpux_mux_data); + if (!clk) + return; + + sun9i_cxcpux_notifier_setup(node, clk); + + /* Protect CPU clock */ + __clk_get(clk); + clk_prepare_enable(clk); +} +CLK_OF_DECLARE(sun9i_c1cpux, "allwinner,sun9i-a80-c1cpux-clk", + sun9i_c1cpux_clk_setup); /** * sunxi_divider_clk_setup() - Setup function for simple divider clocks |