1b2441318SGreg Kroah-Hartman // SPDX-License-Identifier: GPL-2.0
277d16e2cSChen-Yu Tsai /*
377d16e2cSChen-Yu Tsai * Copyright (C) 2015 Chen-Yu Tsai
477d16e2cSChen-Yu Tsai *
577d16e2cSChen-Yu Tsai * Chen-Yu Tsai <wens@csie.org>
677d16e2cSChen-Yu Tsai *
777d16e2cSChen-Yu Tsai * Allwinner A80 CPUS clock driver
877d16e2cSChen-Yu Tsai *
977d16e2cSChen-Yu Tsai */
1077d16e2cSChen-Yu Tsai
1177d16e2cSChen-Yu Tsai #include <linux/clk.h>
1277d16e2cSChen-Yu Tsai #include <linux/clk-provider.h>
13*62e59c4eSStephen Boyd #include <linux/io.h>
1477d16e2cSChen-Yu Tsai #include <linux/slab.h>
1577d16e2cSChen-Yu Tsai #include <linux/spinlock.h>
1677d16e2cSChen-Yu Tsai #include <linux/of.h>
1777d16e2cSChen-Yu Tsai #include <linux/of_address.h>
1877d16e2cSChen-Yu Tsai
1977d16e2cSChen-Yu Tsai static DEFINE_SPINLOCK(sun9i_a80_cpus_lock);
2077d16e2cSChen-Yu Tsai
2177d16e2cSChen-Yu Tsai /**
2277d16e2cSChen-Yu Tsai * sun9i_a80_cpus_clk_setup() - Setup function for a80 cpus composite clk
2377d16e2cSChen-Yu Tsai */
2477d16e2cSChen-Yu Tsai
2577d16e2cSChen-Yu Tsai #define SUN9I_CPUS_MAX_PARENTS 4
2677d16e2cSChen-Yu Tsai #define SUN9I_CPUS_MUX_PARENT_PLL4 3
2777d16e2cSChen-Yu Tsai #define SUN9I_CPUS_MUX_SHIFT 16
2877d16e2cSChen-Yu Tsai #define SUN9I_CPUS_MUX_MASK GENMASK(17, 16)
2977d16e2cSChen-Yu Tsai #define SUN9I_CPUS_MUX_GET_PARENT(reg) ((reg & SUN9I_CPUS_MUX_MASK) >> \
3077d16e2cSChen-Yu Tsai SUN9I_CPUS_MUX_SHIFT)
3177d16e2cSChen-Yu Tsai
3277d16e2cSChen-Yu Tsai #define SUN9I_CPUS_DIV_SHIFT 4
3377d16e2cSChen-Yu Tsai #define SUN9I_CPUS_DIV_MASK GENMASK(5, 4)
3477d16e2cSChen-Yu Tsai #define SUN9I_CPUS_DIV_GET(reg) ((reg & SUN9I_CPUS_DIV_MASK) >> \
3577d16e2cSChen-Yu Tsai SUN9I_CPUS_DIV_SHIFT)
3677d16e2cSChen-Yu Tsai #define SUN9I_CPUS_DIV_SET(reg, div) ((reg & ~SUN9I_CPUS_DIV_MASK) | \
3777d16e2cSChen-Yu Tsai (div << SUN9I_CPUS_DIV_SHIFT))
3877d16e2cSChen-Yu Tsai #define SUN9I_CPUS_PLL4_DIV_SHIFT 8
3977d16e2cSChen-Yu Tsai #define SUN9I_CPUS_PLL4_DIV_MASK GENMASK(12, 8)
4077d16e2cSChen-Yu Tsai #define SUN9I_CPUS_PLL4_DIV_GET(reg) ((reg & SUN9I_CPUS_PLL4_DIV_MASK) >> \
4177d16e2cSChen-Yu Tsai SUN9I_CPUS_PLL4_DIV_SHIFT)
4277d16e2cSChen-Yu Tsai #define SUN9I_CPUS_PLL4_DIV_SET(reg, div) ((reg & ~SUN9I_CPUS_PLL4_DIV_MASK) | \
4377d16e2cSChen-Yu Tsai (div << SUN9I_CPUS_PLL4_DIV_SHIFT))
4477d16e2cSChen-Yu Tsai
4577d16e2cSChen-Yu Tsai struct sun9i_a80_cpus_clk {
4677d16e2cSChen-Yu Tsai struct clk_hw hw;
4777d16e2cSChen-Yu Tsai void __iomem *reg;
4877d16e2cSChen-Yu Tsai };
4977d16e2cSChen-Yu Tsai
5077d16e2cSChen-Yu Tsai #define to_sun9i_a80_cpus_clk(_hw) container_of(_hw, struct sun9i_a80_cpus_clk, hw)
5177d16e2cSChen-Yu Tsai
sun9i_a80_cpus_clk_recalc_rate(struct clk_hw * hw,unsigned long parent_rate)5277d16e2cSChen-Yu Tsai static unsigned long sun9i_a80_cpus_clk_recalc_rate(struct clk_hw *hw,
5377d16e2cSChen-Yu Tsai unsigned long parent_rate)
5477d16e2cSChen-Yu Tsai {
5577d16e2cSChen-Yu Tsai struct sun9i_a80_cpus_clk *cpus = to_sun9i_a80_cpus_clk(hw);
5677d16e2cSChen-Yu Tsai unsigned long rate;
5777d16e2cSChen-Yu Tsai u32 reg;
5877d16e2cSChen-Yu Tsai
5977d16e2cSChen-Yu Tsai /* Fetch the register value */
6077d16e2cSChen-Yu Tsai reg = readl(cpus->reg);
6177d16e2cSChen-Yu Tsai
6277d16e2cSChen-Yu Tsai /* apply pre-divider first if parent is pll4 */
6377d16e2cSChen-Yu Tsai if (SUN9I_CPUS_MUX_GET_PARENT(reg) == SUN9I_CPUS_MUX_PARENT_PLL4)
6477d16e2cSChen-Yu Tsai parent_rate /= SUN9I_CPUS_PLL4_DIV_GET(reg) + 1;
6577d16e2cSChen-Yu Tsai
6677d16e2cSChen-Yu Tsai /* clk divider */
6777d16e2cSChen-Yu Tsai rate = parent_rate / (SUN9I_CPUS_DIV_GET(reg) + 1);
6877d16e2cSChen-Yu Tsai
6977d16e2cSChen-Yu Tsai return rate;
7077d16e2cSChen-Yu Tsai }
7177d16e2cSChen-Yu Tsai
sun9i_a80_cpus_clk_round(unsigned long rate,u8 * divp,u8 * pre_divp,u8 parent,unsigned long parent_rate)7277d16e2cSChen-Yu Tsai static long sun9i_a80_cpus_clk_round(unsigned long rate, u8 *divp, u8 *pre_divp,
7377d16e2cSChen-Yu Tsai u8 parent, unsigned long parent_rate)
7477d16e2cSChen-Yu Tsai {
7577d16e2cSChen-Yu Tsai u8 div, pre_div = 1;
7677d16e2cSChen-Yu Tsai
7777d16e2cSChen-Yu Tsai /*
7877d16e2cSChen-Yu Tsai * clock can only divide, so we will never be able to achieve
7977d16e2cSChen-Yu Tsai * frequencies higher than the parent frequency
8077d16e2cSChen-Yu Tsai */
8177d16e2cSChen-Yu Tsai if (parent_rate && rate > parent_rate)
8277d16e2cSChen-Yu Tsai rate = parent_rate;
8377d16e2cSChen-Yu Tsai
8477d16e2cSChen-Yu Tsai div = DIV_ROUND_UP(parent_rate, rate);
8577d16e2cSChen-Yu Tsai
8677d16e2cSChen-Yu Tsai /* calculate pre-divider if parent is pll4 */
8777d16e2cSChen-Yu Tsai if (parent == SUN9I_CPUS_MUX_PARENT_PLL4 && div > 4) {
8877d16e2cSChen-Yu Tsai /* pre-divider is 1 ~ 32 */
8977d16e2cSChen-Yu Tsai if (div < 32) {
9077d16e2cSChen-Yu Tsai pre_div = div;
9177d16e2cSChen-Yu Tsai div = 1;
9277d16e2cSChen-Yu Tsai } else if (div < 64) {
9377d16e2cSChen-Yu Tsai pre_div = DIV_ROUND_UP(div, 2);
9477d16e2cSChen-Yu Tsai div = 2;
9577d16e2cSChen-Yu Tsai } else if (div < 96) {
9677d16e2cSChen-Yu Tsai pre_div = DIV_ROUND_UP(div, 3);
9777d16e2cSChen-Yu Tsai div = 3;
9877d16e2cSChen-Yu Tsai } else {
9977d16e2cSChen-Yu Tsai pre_div = DIV_ROUND_UP(div, 4);
10077d16e2cSChen-Yu Tsai div = 4;
10177d16e2cSChen-Yu Tsai }
10277d16e2cSChen-Yu Tsai }
10377d16e2cSChen-Yu Tsai
10477d16e2cSChen-Yu Tsai /* we were asked to pass back divider values */
10577d16e2cSChen-Yu Tsai if (divp) {
10677d16e2cSChen-Yu Tsai *divp = div - 1;
10777d16e2cSChen-Yu Tsai *pre_divp = pre_div - 1;
10877d16e2cSChen-Yu Tsai }
10977d16e2cSChen-Yu Tsai
11077d16e2cSChen-Yu Tsai return parent_rate / pre_div / div;
11177d16e2cSChen-Yu Tsai }
11277d16e2cSChen-Yu Tsai
sun9i_a80_cpus_clk_determine_rate(struct clk_hw * clk,struct clk_rate_request * req)11377d16e2cSChen-Yu Tsai static int sun9i_a80_cpus_clk_determine_rate(struct clk_hw *clk,
11477d16e2cSChen-Yu Tsai struct clk_rate_request *req)
11577d16e2cSChen-Yu Tsai {
11677d16e2cSChen-Yu Tsai struct clk_hw *parent, *best_parent = NULL;
11777d16e2cSChen-Yu Tsai int i, num_parents;
11877d16e2cSChen-Yu Tsai unsigned long parent_rate, best = 0, child_rate, best_child_rate = 0;
11977d16e2cSChen-Yu Tsai unsigned long rate = req->rate;
12077d16e2cSChen-Yu Tsai
12177d16e2cSChen-Yu Tsai /* find the parent that can help provide the fastest rate <= rate */
12277d16e2cSChen-Yu Tsai num_parents = clk_hw_get_num_parents(clk);
12377d16e2cSChen-Yu Tsai for (i = 0; i < num_parents; i++) {
12477d16e2cSChen-Yu Tsai parent = clk_hw_get_parent_by_index(clk, i);
12577d16e2cSChen-Yu Tsai if (!parent)
12677d16e2cSChen-Yu Tsai continue;
12777d16e2cSChen-Yu Tsai if (clk_hw_get_flags(clk) & CLK_SET_RATE_PARENT)
12877d16e2cSChen-Yu Tsai parent_rate = clk_hw_round_rate(parent, rate);
12977d16e2cSChen-Yu Tsai else
13077d16e2cSChen-Yu Tsai parent_rate = clk_hw_get_rate(parent);
13177d16e2cSChen-Yu Tsai
13277d16e2cSChen-Yu Tsai child_rate = sun9i_a80_cpus_clk_round(rate, NULL, NULL, i,
13377d16e2cSChen-Yu Tsai parent_rate);
13477d16e2cSChen-Yu Tsai
13577d16e2cSChen-Yu Tsai if (child_rate <= rate && child_rate > best_child_rate) {
13677d16e2cSChen-Yu Tsai best_parent = parent;
13777d16e2cSChen-Yu Tsai best = parent_rate;
13877d16e2cSChen-Yu Tsai best_child_rate = child_rate;
13977d16e2cSChen-Yu Tsai }
14077d16e2cSChen-Yu Tsai }
14177d16e2cSChen-Yu Tsai
14277d16e2cSChen-Yu Tsai if (!best_parent)
14377d16e2cSChen-Yu Tsai return -EINVAL;
14477d16e2cSChen-Yu Tsai
14577d16e2cSChen-Yu Tsai req->best_parent_hw = best_parent;
14677d16e2cSChen-Yu Tsai req->best_parent_rate = best;
14777d16e2cSChen-Yu Tsai req->rate = best_child_rate;
14877d16e2cSChen-Yu Tsai
14977d16e2cSChen-Yu Tsai return 0;
15077d16e2cSChen-Yu Tsai }
15177d16e2cSChen-Yu Tsai
sun9i_a80_cpus_clk_set_rate(struct clk_hw * hw,unsigned long rate,unsigned long parent_rate)15277d16e2cSChen-Yu Tsai static int sun9i_a80_cpus_clk_set_rate(struct clk_hw *hw, unsigned long rate,
15377d16e2cSChen-Yu Tsai unsigned long parent_rate)
15477d16e2cSChen-Yu Tsai {
15577d16e2cSChen-Yu Tsai struct sun9i_a80_cpus_clk *cpus = to_sun9i_a80_cpus_clk(hw);
15677d16e2cSChen-Yu Tsai unsigned long flags;
15777d16e2cSChen-Yu Tsai u8 div, pre_div, parent;
15877d16e2cSChen-Yu Tsai u32 reg;
15977d16e2cSChen-Yu Tsai
16077d16e2cSChen-Yu Tsai spin_lock_irqsave(&sun9i_a80_cpus_lock, flags);
16177d16e2cSChen-Yu Tsai
16277d16e2cSChen-Yu Tsai reg = readl(cpus->reg);
16377d16e2cSChen-Yu Tsai
16477d16e2cSChen-Yu Tsai /* need to know which parent is used to apply pre-divider */
16577d16e2cSChen-Yu Tsai parent = SUN9I_CPUS_MUX_GET_PARENT(reg);
16677d16e2cSChen-Yu Tsai sun9i_a80_cpus_clk_round(rate, &div, &pre_div, parent, parent_rate);
16777d16e2cSChen-Yu Tsai
16877d16e2cSChen-Yu Tsai reg = SUN9I_CPUS_DIV_SET(reg, div);
16977d16e2cSChen-Yu Tsai reg = SUN9I_CPUS_PLL4_DIV_SET(reg, pre_div);
17077d16e2cSChen-Yu Tsai writel(reg, cpus->reg);
17177d16e2cSChen-Yu Tsai
17277d16e2cSChen-Yu Tsai spin_unlock_irqrestore(&sun9i_a80_cpus_lock, flags);
17377d16e2cSChen-Yu Tsai
17477d16e2cSChen-Yu Tsai return 0;
17577d16e2cSChen-Yu Tsai }
17677d16e2cSChen-Yu Tsai
17777d16e2cSChen-Yu Tsai static const struct clk_ops sun9i_a80_cpus_clk_ops = {
17877d16e2cSChen-Yu Tsai .determine_rate = sun9i_a80_cpus_clk_determine_rate,
17977d16e2cSChen-Yu Tsai .recalc_rate = sun9i_a80_cpus_clk_recalc_rate,
18077d16e2cSChen-Yu Tsai .set_rate = sun9i_a80_cpus_clk_set_rate,
18177d16e2cSChen-Yu Tsai };
18277d16e2cSChen-Yu Tsai
sun9i_a80_cpus_setup(struct device_node * node)18377d16e2cSChen-Yu Tsai static void sun9i_a80_cpus_setup(struct device_node *node)
18477d16e2cSChen-Yu Tsai {
18577d16e2cSChen-Yu Tsai const char *clk_name = node->name;
18677d16e2cSChen-Yu Tsai const char *parents[SUN9I_CPUS_MAX_PARENTS];
18777d16e2cSChen-Yu Tsai struct resource res;
18877d16e2cSChen-Yu Tsai struct sun9i_a80_cpus_clk *cpus;
18977d16e2cSChen-Yu Tsai struct clk_mux *mux;
19077d16e2cSChen-Yu Tsai struct clk *clk;
19177d16e2cSChen-Yu Tsai int ret;
19277d16e2cSChen-Yu Tsai
19377d16e2cSChen-Yu Tsai cpus = kzalloc(sizeof(*cpus), GFP_KERNEL);
19477d16e2cSChen-Yu Tsai if (!cpus)
19577d16e2cSChen-Yu Tsai return;
19677d16e2cSChen-Yu Tsai
19777d16e2cSChen-Yu Tsai cpus->reg = of_io_request_and_map(node, 0, of_node_full_name(node));
19877d16e2cSChen-Yu Tsai if (IS_ERR(cpus->reg))
19977d16e2cSChen-Yu Tsai goto err_free_cpus;
20077d16e2cSChen-Yu Tsai
20177d16e2cSChen-Yu Tsai of_property_read_string(node, "clock-output-names", &clk_name);
20277d16e2cSChen-Yu Tsai
20377d16e2cSChen-Yu Tsai /* we have a mux, we will have >1 parents */
20477d16e2cSChen-Yu Tsai ret = of_clk_parent_fill(node, parents, SUN9I_CPUS_MAX_PARENTS);
20577d16e2cSChen-Yu Tsai
20677d16e2cSChen-Yu Tsai mux = kzalloc(sizeof(*mux), GFP_KERNEL);
20777d16e2cSChen-Yu Tsai if (!mux)
20877d16e2cSChen-Yu Tsai goto err_unmap;
20977d16e2cSChen-Yu Tsai
21077d16e2cSChen-Yu Tsai /* set up clock properties */
21177d16e2cSChen-Yu Tsai mux->reg = cpus->reg;
21277d16e2cSChen-Yu Tsai mux->shift = SUN9I_CPUS_MUX_SHIFT;
21377d16e2cSChen-Yu Tsai /* un-shifted mask is what mux_clk expects */
21477d16e2cSChen-Yu Tsai mux->mask = SUN9I_CPUS_MUX_MASK >> SUN9I_CPUS_MUX_SHIFT;
21577d16e2cSChen-Yu Tsai mux->lock = &sun9i_a80_cpus_lock;
21677d16e2cSChen-Yu Tsai
21777d16e2cSChen-Yu Tsai clk = clk_register_composite(NULL, clk_name, parents, ret,
21877d16e2cSChen-Yu Tsai &mux->hw, &clk_mux_ops,
21977d16e2cSChen-Yu Tsai &cpus->hw, &sun9i_a80_cpus_clk_ops,
22077d16e2cSChen-Yu Tsai NULL, NULL, 0);
22177d16e2cSChen-Yu Tsai if (IS_ERR(clk))
22277d16e2cSChen-Yu Tsai goto err_free_mux;
22377d16e2cSChen-Yu Tsai
22477d16e2cSChen-Yu Tsai ret = of_clk_add_provider(node, of_clk_src_simple_get, clk);
22577d16e2cSChen-Yu Tsai if (ret)
22677d16e2cSChen-Yu Tsai goto err_unregister;
22777d16e2cSChen-Yu Tsai
22877d16e2cSChen-Yu Tsai return;
22977d16e2cSChen-Yu Tsai
23077d16e2cSChen-Yu Tsai err_unregister:
23177d16e2cSChen-Yu Tsai clk_unregister(clk);
23277d16e2cSChen-Yu Tsai err_free_mux:
23377d16e2cSChen-Yu Tsai kfree(mux);
23477d16e2cSChen-Yu Tsai err_unmap:
23577d16e2cSChen-Yu Tsai iounmap(cpus->reg);
23677d16e2cSChen-Yu Tsai of_address_to_resource(node, 0, &res);
23777d16e2cSChen-Yu Tsai release_mem_region(res.start, resource_size(&res));
23877d16e2cSChen-Yu Tsai err_free_cpus:
23977d16e2cSChen-Yu Tsai kfree(cpus);
24077d16e2cSChen-Yu Tsai }
24177d16e2cSChen-Yu Tsai CLK_OF_DECLARE(sun9i_a80_cpus, "allwinner,sun9i-a80-cpus-clk",
24277d16e2cSChen-Yu Tsai sun9i_a80_cpus_setup);
243