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>
1362e59c4eSStephen 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