1*71ffeafbSRoman Beranek // SPDX-License-Identifier: GPL-2.0-or-later
2*71ffeafbSRoman Beranek /*
3*71ffeafbSRoman Beranek  * Copyright (C) 2016 Free Electrons
4*71ffeafbSRoman Beranek  * Copyright (C) 2016 NextThing Co
5*71ffeafbSRoman Beranek  *
6*71ffeafbSRoman Beranek  * Maxime Ripard <maxime.ripard@free-electrons.com>
7*71ffeafbSRoman Beranek  */
8*71ffeafbSRoman Beranek 
9*71ffeafbSRoman Beranek #include <linux/clk-provider.h>
10*71ffeafbSRoman Beranek #include <linux/regmap.h>
11*71ffeafbSRoman Beranek 
12*71ffeafbSRoman Beranek #include "sun4i_tcon.h"
13*71ffeafbSRoman Beranek #include "sun4i_tcon_dclk.h"
14*71ffeafbSRoman Beranek 
15*71ffeafbSRoman Beranek struct sun4i_dclk {
16*71ffeafbSRoman Beranek 	struct clk_hw		hw;
17*71ffeafbSRoman Beranek 	struct regmap		*regmap;
18*71ffeafbSRoman Beranek 	struct sun4i_tcon	*tcon;
19*71ffeafbSRoman Beranek };
20*71ffeafbSRoman Beranek 
hw_to_dclk(struct clk_hw * hw)21*71ffeafbSRoman Beranek static inline struct sun4i_dclk *hw_to_dclk(struct clk_hw *hw)
22*71ffeafbSRoman Beranek {
23*71ffeafbSRoman Beranek 	return container_of(hw, struct sun4i_dclk, hw);
24*71ffeafbSRoman Beranek }
25*71ffeafbSRoman Beranek 
sun4i_dclk_disable(struct clk_hw * hw)26*71ffeafbSRoman Beranek static void sun4i_dclk_disable(struct clk_hw *hw)
27*71ffeafbSRoman Beranek {
28*71ffeafbSRoman Beranek 	struct sun4i_dclk *dclk = hw_to_dclk(hw);
29*71ffeafbSRoman Beranek 
30*71ffeafbSRoman Beranek 	regmap_update_bits(dclk->regmap, SUN4I_TCON0_DCLK_REG,
31*71ffeafbSRoman Beranek 			   BIT(SUN4I_TCON0_DCLK_GATE_BIT), 0);
32*71ffeafbSRoman Beranek }
33*71ffeafbSRoman Beranek 
sun4i_dclk_enable(struct clk_hw * hw)34*71ffeafbSRoman Beranek static int sun4i_dclk_enable(struct clk_hw *hw)
35*71ffeafbSRoman Beranek {
36*71ffeafbSRoman Beranek 	struct sun4i_dclk *dclk = hw_to_dclk(hw);
37*71ffeafbSRoman Beranek 
38*71ffeafbSRoman Beranek 	return regmap_update_bits(dclk->regmap, SUN4I_TCON0_DCLK_REG,
39*71ffeafbSRoman Beranek 				  BIT(SUN4I_TCON0_DCLK_GATE_BIT),
40*71ffeafbSRoman Beranek 				  BIT(SUN4I_TCON0_DCLK_GATE_BIT));
41*71ffeafbSRoman Beranek }
42*71ffeafbSRoman Beranek 
sun4i_dclk_is_enabled(struct clk_hw * hw)43*71ffeafbSRoman Beranek static int sun4i_dclk_is_enabled(struct clk_hw *hw)
44*71ffeafbSRoman Beranek {
45*71ffeafbSRoman Beranek 	struct sun4i_dclk *dclk = hw_to_dclk(hw);
46*71ffeafbSRoman Beranek 	u32 val;
47*71ffeafbSRoman Beranek 
48*71ffeafbSRoman Beranek 	regmap_read(dclk->regmap, SUN4I_TCON0_DCLK_REG, &val);
49*71ffeafbSRoman Beranek 
50*71ffeafbSRoman Beranek 	return val & BIT(SUN4I_TCON0_DCLK_GATE_BIT);
51*71ffeafbSRoman Beranek }
52*71ffeafbSRoman Beranek 
sun4i_dclk_recalc_rate(struct clk_hw * hw,unsigned long parent_rate)53*71ffeafbSRoman Beranek static unsigned long sun4i_dclk_recalc_rate(struct clk_hw *hw,
54*71ffeafbSRoman Beranek 					    unsigned long parent_rate)
55*71ffeafbSRoman Beranek {
56*71ffeafbSRoman Beranek 	struct sun4i_dclk *dclk = hw_to_dclk(hw);
57*71ffeafbSRoman Beranek 	u32 val;
58*71ffeafbSRoman Beranek 
59*71ffeafbSRoman Beranek 	regmap_read(dclk->regmap, SUN4I_TCON0_DCLK_REG, &val);
60*71ffeafbSRoman Beranek 
61*71ffeafbSRoman Beranek 	val >>= SUN4I_TCON0_DCLK_DIV_SHIFT;
62*71ffeafbSRoman Beranek 	val &= (1 << SUN4I_TCON0_DCLK_DIV_WIDTH) - 1;
63*71ffeafbSRoman Beranek 
64*71ffeafbSRoman Beranek 	if (!val)
65*71ffeafbSRoman Beranek 		val = 1;
66*71ffeafbSRoman Beranek 
67*71ffeafbSRoman Beranek 	return parent_rate / val;
68*71ffeafbSRoman Beranek }
69*71ffeafbSRoman Beranek 
sun4i_dclk_round_rate(struct clk_hw * hw,unsigned long rate,unsigned long * parent_rate)70*71ffeafbSRoman Beranek static long sun4i_dclk_round_rate(struct clk_hw *hw, unsigned long rate,
71*71ffeafbSRoman Beranek 				  unsigned long *parent_rate)
72*71ffeafbSRoman Beranek {
73*71ffeafbSRoman Beranek 	struct sun4i_dclk *dclk = hw_to_dclk(hw);
74*71ffeafbSRoman Beranek 	struct sun4i_tcon *tcon = dclk->tcon;
75*71ffeafbSRoman Beranek 	unsigned long best_parent = 0;
76*71ffeafbSRoman Beranek 	u8 best_div = 1;
77*71ffeafbSRoman Beranek 	int i;
78*71ffeafbSRoman Beranek 
79*71ffeafbSRoman Beranek 	for (i = tcon->dclk_min_div; i <= tcon->dclk_max_div; i++) {
80*71ffeafbSRoman Beranek 		u64 ideal = (u64)rate * i;
81*71ffeafbSRoman Beranek 		unsigned long rounded;
82*71ffeafbSRoman Beranek 
83*71ffeafbSRoman Beranek 		/*
84*71ffeafbSRoman Beranek 		 * ideal has overflowed the max value that can be stored in an
85*71ffeafbSRoman Beranek 		 * unsigned long, and every clk operation we might do on a
86*71ffeafbSRoman Beranek 		 * truncated u64 value will give us incorrect results.
87*71ffeafbSRoman Beranek 		 * Let's just stop there since bigger dividers will result in
88*71ffeafbSRoman Beranek 		 * the same overflow issue.
89*71ffeafbSRoman Beranek 		 */
90*71ffeafbSRoman Beranek 		if (ideal > ULONG_MAX)
91*71ffeafbSRoman Beranek 			goto out;
92*71ffeafbSRoman Beranek 
93*71ffeafbSRoman Beranek 		rounded = clk_hw_round_rate(clk_hw_get_parent(hw),
94*71ffeafbSRoman Beranek 					    ideal);
95*71ffeafbSRoman Beranek 
96*71ffeafbSRoman Beranek 		if (rounded == ideal) {
97*71ffeafbSRoman Beranek 			best_parent = rounded;
98*71ffeafbSRoman Beranek 			best_div = i;
99*71ffeafbSRoman Beranek 			goto out;
100*71ffeafbSRoman Beranek 		}
101*71ffeafbSRoman Beranek 
102*71ffeafbSRoman Beranek 		if (abs(rate - rounded / i) <
103*71ffeafbSRoman Beranek 		    abs(rate - best_parent / best_div)) {
104*71ffeafbSRoman Beranek 			best_parent = rounded;
105*71ffeafbSRoman Beranek 			best_div = i;
106*71ffeafbSRoman Beranek 		}
107*71ffeafbSRoman Beranek 	}
108*71ffeafbSRoman Beranek 
109*71ffeafbSRoman Beranek out:
110*71ffeafbSRoman Beranek 	*parent_rate = best_parent;
111*71ffeafbSRoman Beranek 
112*71ffeafbSRoman Beranek 	return best_parent / best_div;
113*71ffeafbSRoman Beranek }
114*71ffeafbSRoman Beranek 
sun4i_dclk_set_rate(struct clk_hw * hw,unsigned long rate,unsigned long parent_rate)115*71ffeafbSRoman Beranek static int sun4i_dclk_set_rate(struct clk_hw *hw, unsigned long rate,
116*71ffeafbSRoman Beranek 			       unsigned long parent_rate)
117*71ffeafbSRoman Beranek {
118*71ffeafbSRoman Beranek 	struct sun4i_dclk *dclk = hw_to_dclk(hw);
119*71ffeafbSRoman Beranek 	u8 div = parent_rate / rate;
120*71ffeafbSRoman Beranek 
121*71ffeafbSRoman Beranek 	return regmap_update_bits(dclk->regmap, SUN4I_TCON0_DCLK_REG,
122*71ffeafbSRoman Beranek 				  GENMASK(6, 0), div);
123*71ffeafbSRoman Beranek }
124*71ffeafbSRoman Beranek 
sun4i_dclk_get_phase(struct clk_hw * hw)125*71ffeafbSRoman Beranek static int sun4i_dclk_get_phase(struct clk_hw *hw)
126*71ffeafbSRoman Beranek {
127*71ffeafbSRoman Beranek 	struct sun4i_dclk *dclk = hw_to_dclk(hw);
128*71ffeafbSRoman Beranek 	u32 val;
129*71ffeafbSRoman Beranek 
130*71ffeafbSRoman Beranek 	regmap_read(dclk->regmap, SUN4I_TCON0_IO_POL_REG, &val);
131*71ffeafbSRoman Beranek 
132*71ffeafbSRoman Beranek 	val >>= 28;
133*71ffeafbSRoman Beranek 	val &= 3;
134*71ffeafbSRoman Beranek 
135*71ffeafbSRoman Beranek 	return val * 120;
136*71ffeafbSRoman Beranek }
137*71ffeafbSRoman Beranek 
sun4i_dclk_set_phase(struct clk_hw * hw,int degrees)138*71ffeafbSRoman Beranek static int sun4i_dclk_set_phase(struct clk_hw *hw, int degrees)
139*71ffeafbSRoman Beranek {
140*71ffeafbSRoman Beranek 	struct sun4i_dclk *dclk = hw_to_dclk(hw);
141*71ffeafbSRoman Beranek 	u32 val = degrees / 120;
142*71ffeafbSRoman Beranek 
143*71ffeafbSRoman Beranek 	val <<= 28;
144*71ffeafbSRoman Beranek 
145*71ffeafbSRoman Beranek 	regmap_update_bits(dclk->regmap, SUN4I_TCON0_IO_POL_REG,
146*71ffeafbSRoman Beranek 			   GENMASK(29, 28),
147*71ffeafbSRoman Beranek 			   val);
148*71ffeafbSRoman Beranek 
149*71ffeafbSRoman Beranek 	return 0;
150*71ffeafbSRoman Beranek }
151*71ffeafbSRoman Beranek 
152*71ffeafbSRoman Beranek static const struct clk_ops sun4i_dclk_ops = {
153*71ffeafbSRoman Beranek 	.disable	= sun4i_dclk_disable,
154*71ffeafbSRoman Beranek 	.enable		= sun4i_dclk_enable,
155*71ffeafbSRoman Beranek 	.is_enabled	= sun4i_dclk_is_enabled,
156*71ffeafbSRoman Beranek 
157*71ffeafbSRoman Beranek 	.recalc_rate	= sun4i_dclk_recalc_rate,
158*71ffeafbSRoman Beranek 	.round_rate	= sun4i_dclk_round_rate,
159*71ffeafbSRoman Beranek 	.set_rate	= sun4i_dclk_set_rate,
160*71ffeafbSRoman Beranek 
161*71ffeafbSRoman Beranek 	.get_phase	= sun4i_dclk_get_phase,
162*71ffeafbSRoman Beranek 	.set_phase	= sun4i_dclk_set_phase,
163*71ffeafbSRoman Beranek };
164*71ffeafbSRoman Beranek 
sun4i_dclk_create(struct device * dev,struct sun4i_tcon * tcon)165*71ffeafbSRoman Beranek int sun4i_dclk_create(struct device *dev, struct sun4i_tcon *tcon)
166*71ffeafbSRoman Beranek {
167*71ffeafbSRoman Beranek 	const char *clk_name, *parent_name;
168*71ffeafbSRoman Beranek 	struct clk_init_data init;
169*71ffeafbSRoman Beranek 	struct sun4i_dclk *dclk;
170*71ffeafbSRoman Beranek 	int ret;
171*71ffeafbSRoman Beranek 
172*71ffeafbSRoman Beranek 	parent_name = __clk_get_name(tcon->sclk0);
173*71ffeafbSRoman Beranek 	ret = of_property_read_string_index(dev->of_node,
174*71ffeafbSRoman Beranek 					    "clock-output-names", 0,
175*71ffeafbSRoman Beranek 					    &clk_name);
176*71ffeafbSRoman Beranek 	if (ret)
177*71ffeafbSRoman Beranek 		return ret;
178*71ffeafbSRoman Beranek 
179*71ffeafbSRoman Beranek 	dclk = devm_kzalloc(dev, sizeof(*dclk), GFP_KERNEL);
180*71ffeafbSRoman Beranek 	if (!dclk)
181*71ffeafbSRoman Beranek 		return -ENOMEM;
182*71ffeafbSRoman Beranek 	dclk->tcon = tcon;
183*71ffeafbSRoman Beranek 
184*71ffeafbSRoman Beranek 	init.name = clk_name;
185*71ffeafbSRoman Beranek 	init.ops = &sun4i_dclk_ops;
186*71ffeafbSRoman Beranek 	init.parent_names = &parent_name;
187*71ffeafbSRoman Beranek 	init.num_parents = 1;
188*71ffeafbSRoman Beranek 	init.flags = CLK_SET_RATE_PARENT;
189*71ffeafbSRoman Beranek 
190*71ffeafbSRoman Beranek 	dclk->regmap = tcon->regs;
191*71ffeafbSRoman Beranek 	dclk->hw.init = &init;
192*71ffeafbSRoman Beranek 
193*71ffeafbSRoman Beranek 	tcon->dclk = clk_register(dev, &dclk->hw);
194*71ffeafbSRoman Beranek 	if (IS_ERR(tcon->dclk))
195*71ffeafbSRoman Beranek 		return PTR_ERR(tcon->dclk);
196*71ffeafbSRoman Beranek 
197*71ffeafbSRoman Beranek 	return 0;
198*71ffeafbSRoman Beranek }
199*71ffeafbSRoman Beranek EXPORT_SYMBOL(sun4i_dclk_create);
200*71ffeafbSRoman Beranek 
sun4i_dclk_free(struct sun4i_tcon * tcon)201*71ffeafbSRoman Beranek int sun4i_dclk_free(struct sun4i_tcon *tcon)
202*71ffeafbSRoman Beranek {
203*71ffeafbSRoman Beranek 	clk_unregister(tcon->dclk);
204*71ffeafbSRoman Beranek 	return 0;
205*71ffeafbSRoman Beranek }
206*71ffeafbSRoman Beranek EXPORT_SYMBOL(sun4i_dclk_free);
207