1956060a5SElaine Zhang // SPDX-License-Identifier: GPL-2.0
2956060a5SElaine Zhang /*
3956060a5SElaine Zhang  * Copyright (c) 2018 Fuzhou Rockchip Electronics Co., Ltd
4956060a5SElaine Zhang  */
5956060a5SElaine Zhang 
6956060a5SElaine Zhang #include <linux/clk-provider.h>
762e59c4eSStephen Boyd #include <linux/io.h>
862e59c4eSStephen Boyd #include <linux/slab.h>
9956060a5SElaine Zhang #include "clk.h"
10956060a5SElaine Zhang 
11956060a5SElaine Zhang #define div_mask(width)	((1 << (width)) - 1)
12956060a5SElaine Zhang 
_is_best_half_div(unsigned long rate,unsigned long now,unsigned long best,unsigned long flags)13956060a5SElaine Zhang static bool _is_best_half_div(unsigned long rate, unsigned long now,
14956060a5SElaine Zhang 			      unsigned long best, unsigned long flags)
15956060a5SElaine Zhang {
16956060a5SElaine Zhang 	if (flags & CLK_DIVIDER_ROUND_CLOSEST)
17956060a5SElaine Zhang 		return abs(rate - now) < abs(rate - best);
18956060a5SElaine Zhang 
19956060a5SElaine Zhang 	return now <= rate && now > best;
20956060a5SElaine Zhang }
21956060a5SElaine Zhang 
clk_half_divider_recalc_rate(struct clk_hw * hw,unsigned long parent_rate)22956060a5SElaine Zhang static unsigned long clk_half_divider_recalc_rate(struct clk_hw *hw,
23956060a5SElaine Zhang 						  unsigned long parent_rate)
24956060a5SElaine Zhang {
25956060a5SElaine Zhang 	struct clk_divider *divider = to_clk_divider(hw);
26956060a5SElaine Zhang 	unsigned int val;
27956060a5SElaine Zhang 
285834fd75SJonas Gorski 	val = readl(divider->reg) >> divider->shift;
29956060a5SElaine Zhang 	val &= div_mask(divider->width);
30956060a5SElaine Zhang 	val = val * 2 + 3;
31956060a5SElaine Zhang 
32956060a5SElaine Zhang 	return DIV_ROUND_UP_ULL(((u64)parent_rate * 2), val);
33956060a5SElaine Zhang }
34956060a5SElaine Zhang 
clk_half_divider_bestdiv(struct clk_hw * hw,unsigned long rate,unsigned long * best_parent_rate,u8 width,unsigned long flags)35956060a5SElaine Zhang static int clk_half_divider_bestdiv(struct clk_hw *hw, unsigned long rate,
36956060a5SElaine Zhang 				    unsigned long *best_parent_rate, u8 width,
37956060a5SElaine Zhang 				    unsigned long flags)
38956060a5SElaine Zhang {
39956060a5SElaine Zhang 	unsigned int i, bestdiv = 0;
40956060a5SElaine Zhang 	unsigned long parent_rate, best = 0, now, maxdiv;
41956060a5SElaine Zhang 	unsigned long parent_rate_saved = *best_parent_rate;
42956060a5SElaine Zhang 
43956060a5SElaine Zhang 	if (!rate)
44956060a5SElaine Zhang 		rate = 1;
45956060a5SElaine Zhang 
46956060a5SElaine Zhang 	maxdiv = div_mask(width);
47956060a5SElaine Zhang 
48956060a5SElaine Zhang 	if (!(clk_hw_get_flags(hw) & CLK_SET_RATE_PARENT)) {
49956060a5SElaine Zhang 		parent_rate = *best_parent_rate;
50956060a5SElaine Zhang 		bestdiv = DIV_ROUND_UP_ULL(((u64)parent_rate * 2), rate);
51956060a5SElaine Zhang 		if (bestdiv < 3)
52956060a5SElaine Zhang 			bestdiv = 0;
53956060a5SElaine Zhang 		else
54956060a5SElaine Zhang 			bestdiv = (bestdiv - 3) / 2;
55956060a5SElaine Zhang 		bestdiv = bestdiv > maxdiv ? maxdiv : bestdiv;
56956060a5SElaine Zhang 		return bestdiv;
57956060a5SElaine Zhang 	}
58956060a5SElaine Zhang 
59956060a5SElaine Zhang 	/*
60956060a5SElaine Zhang 	 * The maximum divider we can use without overflowing
61956060a5SElaine Zhang 	 * unsigned long in rate * i below
62956060a5SElaine Zhang 	 */
63956060a5SElaine Zhang 	maxdiv = min(ULONG_MAX / rate, maxdiv);
64956060a5SElaine Zhang 
65956060a5SElaine Zhang 	for (i = 0; i <= maxdiv; i++) {
66956060a5SElaine Zhang 		if (((u64)rate * (i * 2 + 3)) == ((u64)parent_rate_saved * 2)) {
67956060a5SElaine Zhang 			/*
68956060a5SElaine Zhang 			 * It's the most ideal case if the requested rate can be
69956060a5SElaine Zhang 			 * divided from parent clock without needing to change
70956060a5SElaine Zhang 			 * parent rate, so return the divider immediately.
71956060a5SElaine Zhang 			 */
72956060a5SElaine Zhang 			*best_parent_rate = parent_rate_saved;
73956060a5SElaine Zhang 			return i;
74956060a5SElaine Zhang 		}
75956060a5SElaine Zhang 		parent_rate = clk_hw_round_rate(clk_hw_get_parent(hw),
76956060a5SElaine Zhang 						((u64)rate * (i * 2 + 3)) / 2);
77956060a5SElaine Zhang 		now = DIV_ROUND_UP_ULL(((u64)parent_rate * 2),
78956060a5SElaine Zhang 				       (i * 2 + 3));
79956060a5SElaine Zhang 
80956060a5SElaine Zhang 		if (_is_best_half_div(rate, now, best, flags)) {
81956060a5SElaine Zhang 			bestdiv = i;
82956060a5SElaine Zhang 			best = now;
83956060a5SElaine Zhang 			*best_parent_rate = parent_rate;
84956060a5SElaine Zhang 		}
85956060a5SElaine Zhang 	}
86956060a5SElaine Zhang 
87956060a5SElaine Zhang 	if (!bestdiv) {
88956060a5SElaine Zhang 		bestdiv = div_mask(width);
89956060a5SElaine Zhang 		*best_parent_rate = clk_hw_round_rate(clk_hw_get_parent(hw), 1);
90956060a5SElaine Zhang 	}
91956060a5SElaine Zhang 
92956060a5SElaine Zhang 	return bestdiv;
93956060a5SElaine Zhang }
94956060a5SElaine Zhang 
clk_half_divider_round_rate(struct clk_hw * hw,unsigned long rate,unsigned long * prate)95956060a5SElaine Zhang static long clk_half_divider_round_rate(struct clk_hw *hw, unsigned long rate,
96956060a5SElaine Zhang 					unsigned long *prate)
97956060a5SElaine Zhang {
98956060a5SElaine Zhang 	struct clk_divider *divider = to_clk_divider(hw);
99956060a5SElaine Zhang 	int div;
100956060a5SElaine Zhang 
101956060a5SElaine Zhang 	div = clk_half_divider_bestdiv(hw, rate, prate,
102956060a5SElaine Zhang 				       divider->width,
103956060a5SElaine Zhang 				       divider->flags);
104956060a5SElaine Zhang 
105956060a5SElaine Zhang 	return DIV_ROUND_UP_ULL(((u64)*prate * 2), div * 2 + 3);
106956060a5SElaine Zhang }
107956060a5SElaine Zhang 
clk_half_divider_set_rate(struct clk_hw * hw,unsigned long rate,unsigned long parent_rate)108956060a5SElaine Zhang static int clk_half_divider_set_rate(struct clk_hw *hw, unsigned long rate,
109956060a5SElaine Zhang 				     unsigned long parent_rate)
110956060a5SElaine Zhang {
111956060a5SElaine Zhang 	struct clk_divider *divider = to_clk_divider(hw);
112956060a5SElaine Zhang 	unsigned int value;
113956060a5SElaine Zhang 	unsigned long flags = 0;
114956060a5SElaine Zhang 	u32 val;
115956060a5SElaine Zhang 
116956060a5SElaine Zhang 	value = DIV_ROUND_UP_ULL(((u64)parent_rate * 2), rate);
117956060a5SElaine Zhang 	value = (value - 3) / 2;
118956060a5SElaine Zhang 	value =  min_t(unsigned int, value, div_mask(divider->width));
119956060a5SElaine Zhang 
120956060a5SElaine Zhang 	if (divider->lock)
121956060a5SElaine Zhang 		spin_lock_irqsave(divider->lock, flags);
122956060a5SElaine Zhang 	else
123956060a5SElaine Zhang 		__acquire(divider->lock);
124956060a5SElaine Zhang 
125956060a5SElaine Zhang 	if (divider->flags & CLK_DIVIDER_HIWORD_MASK) {
126956060a5SElaine Zhang 		val = div_mask(divider->width) << (divider->shift + 16);
127956060a5SElaine Zhang 	} else {
1285834fd75SJonas Gorski 		val = readl(divider->reg);
129956060a5SElaine Zhang 		val &= ~(div_mask(divider->width) << divider->shift);
130956060a5SElaine Zhang 	}
131956060a5SElaine Zhang 	val |= value << divider->shift;
1325834fd75SJonas Gorski 	writel(val, divider->reg);
133956060a5SElaine Zhang 
134956060a5SElaine Zhang 	if (divider->lock)
135956060a5SElaine Zhang 		spin_unlock_irqrestore(divider->lock, flags);
136956060a5SElaine Zhang 	else
137956060a5SElaine Zhang 		__release(divider->lock);
138956060a5SElaine Zhang 
139956060a5SElaine Zhang 	return 0;
140956060a5SElaine Zhang }
141956060a5SElaine Zhang 
142bbbbd246SBen Dooks (Codethink) static const struct clk_ops clk_half_divider_ops = {
143956060a5SElaine Zhang 	.recalc_rate = clk_half_divider_recalc_rate,
144956060a5SElaine Zhang 	.round_rate = clk_half_divider_round_rate,
145956060a5SElaine Zhang 	.set_rate = clk_half_divider_set_rate,
146956060a5SElaine Zhang };
147956060a5SElaine Zhang 
148*d48fbef9SLee Jones /*
149956060a5SElaine Zhang  * Register a clock branch.
150956060a5SElaine Zhang  * Most clock branches have a form like
151956060a5SElaine Zhang  *
152956060a5SElaine Zhang  * src1 --|--\
153956060a5SElaine Zhang  *        |M |--[GATE]-[DIV]-
154956060a5SElaine Zhang  * src2 --|--/
155956060a5SElaine Zhang  *
156956060a5SElaine Zhang  * sometimes without one of those components.
157956060a5SElaine Zhang  */
rockchip_clk_register_halfdiv(const char * name,const char * const * parent_names,u8 num_parents,void __iomem * base,int muxdiv_offset,u8 mux_shift,u8 mux_width,u8 mux_flags,u8 div_shift,u8 div_width,u8 div_flags,int gate_offset,u8 gate_shift,u8 gate_flags,unsigned long flags,spinlock_t * lock)158956060a5SElaine Zhang struct clk *rockchip_clk_register_halfdiv(const char *name,
159956060a5SElaine Zhang 					  const char *const *parent_names,
160956060a5SElaine Zhang 					  u8 num_parents, void __iomem *base,
161956060a5SElaine Zhang 					  int muxdiv_offset, u8 mux_shift,
162956060a5SElaine Zhang 					  u8 mux_width, u8 mux_flags,
163956060a5SElaine Zhang 					  u8 div_shift, u8 div_width,
164956060a5SElaine Zhang 					  u8 div_flags, int gate_offset,
165956060a5SElaine Zhang 					  u8 gate_shift, u8 gate_flags,
166956060a5SElaine Zhang 					  unsigned long flags,
167956060a5SElaine Zhang 					  spinlock_t *lock)
168956060a5SElaine Zhang {
169b608f11dSStephen Boyd 	struct clk_hw *hw = ERR_PTR(-ENOMEM);
170956060a5SElaine Zhang 	struct clk_mux *mux = NULL;
171956060a5SElaine Zhang 	struct clk_gate *gate = NULL;
172956060a5SElaine Zhang 	struct clk_divider *div = NULL;
173956060a5SElaine Zhang 	const struct clk_ops *mux_ops = NULL, *div_ops = NULL,
174956060a5SElaine Zhang 			     *gate_ops = NULL;
175956060a5SElaine Zhang 
176956060a5SElaine Zhang 	if (num_parents > 1) {
177956060a5SElaine Zhang 		mux = kzalloc(sizeof(*mux), GFP_KERNEL);
178956060a5SElaine Zhang 		if (!mux)
179956060a5SElaine Zhang 			return ERR_PTR(-ENOMEM);
180956060a5SElaine Zhang 
181956060a5SElaine Zhang 		mux->reg = base + muxdiv_offset;
182956060a5SElaine Zhang 		mux->shift = mux_shift;
183956060a5SElaine Zhang 		mux->mask = BIT(mux_width) - 1;
184956060a5SElaine Zhang 		mux->flags = mux_flags;
185956060a5SElaine Zhang 		mux->lock = lock;
186956060a5SElaine Zhang 		mux_ops = (mux_flags & CLK_MUX_READ_ONLY) ? &clk_mux_ro_ops
187956060a5SElaine Zhang 							: &clk_mux_ops;
188956060a5SElaine Zhang 	}
189956060a5SElaine Zhang 
190956060a5SElaine Zhang 	if (gate_offset >= 0) {
191956060a5SElaine Zhang 		gate = kzalloc(sizeof(*gate), GFP_KERNEL);
192956060a5SElaine Zhang 		if (!gate)
193956060a5SElaine Zhang 			goto err_gate;
194956060a5SElaine Zhang 
195956060a5SElaine Zhang 		gate->flags = gate_flags;
196956060a5SElaine Zhang 		gate->reg = base + gate_offset;
197956060a5SElaine Zhang 		gate->bit_idx = gate_shift;
198956060a5SElaine Zhang 		gate->lock = lock;
199956060a5SElaine Zhang 		gate_ops = &clk_gate_ops;
200956060a5SElaine Zhang 	}
201956060a5SElaine Zhang 
202956060a5SElaine Zhang 	if (div_width > 0) {
203956060a5SElaine Zhang 		div = kzalloc(sizeof(*div), GFP_KERNEL);
204956060a5SElaine Zhang 		if (!div)
205956060a5SElaine Zhang 			goto err_div;
206956060a5SElaine Zhang 
207956060a5SElaine Zhang 		div->flags = div_flags;
208956060a5SElaine Zhang 		div->reg = base + muxdiv_offset;
209956060a5SElaine Zhang 		div->shift = div_shift;
210956060a5SElaine Zhang 		div->width = div_width;
211956060a5SElaine Zhang 		div->lock = lock;
212956060a5SElaine Zhang 		div_ops = &clk_half_divider_ops;
213956060a5SElaine Zhang 	}
214956060a5SElaine Zhang 
21563207c37SElaine Zhang 	hw = clk_hw_register_composite(NULL, name, parent_names, num_parents,
216956060a5SElaine Zhang 				       mux ? &mux->hw : NULL, mux_ops,
217956060a5SElaine Zhang 				       div ? &div->hw : NULL, div_ops,
218956060a5SElaine Zhang 				       gate ? &gate->hw : NULL, gate_ops,
219956060a5SElaine Zhang 				       flags);
22063207c37SElaine Zhang 	if (IS_ERR(hw))
22163207c37SElaine Zhang 		goto err_div;
222956060a5SElaine Zhang 
22363207c37SElaine Zhang 	return hw->clk;
224956060a5SElaine Zhang err_div:
225956060a5SElaine Zhang 	kfree(gate);
226956060a5SElaine Zhang err_gate:
227956060a5SElaine Zhang 	kfree(mux);
22863207c37SElaine Zhang 	return ERR_CAST(hw);
229956060a5SElaine Zhang }
230