xref: /openbmc/linux/drivers/clk/clk-divider.c (revision 9d9f78ed9af0e465d2fd15550471956e7f559b9f)
1*9d9f78edSMike Turquette /*
2*9d9f78edSMike Turquette  * Copyright (C) 2011 Sascha Hauer, Pengutronix <s.hauer@pengutronix.de>
3*9d9f78edSMike Turquette  * Copyright (C) 2011 Richard Zhao, Linaro <richard.zhao@linaro.org>
4*9d9f78edSMike Turquette  * Copyright (C) 2011-2012 Mike Turquette, Linaro Ltd <mturquette@linaro.org>
5*9d9f78edSMike Turquette  *
6*9d9f78edSMike Turquette  * This program is free software; you can redistribute it and/or modify
7*9d9f78edSMike Turquette  * it under the terms of the GNU General Public License version 2 as
8*9d9f78edSMike Turquette  * published by the Free Software Foundation.
9*9d9f78edSMike Turquette  *
10*9d9f78edSMike Turquette  * Adjustable divider clock implementation
11*9d9f78edSMike Turquette  */
12*9d9f78edSMike Turquette 
13*9d9f78edSMike Turquette #include <linux/clk-provider.h>
14*9d9f78edSMike Turquette #include <linux/module.h>
15*9d9f78edSMike Turquette #include <linux/slab.h>
16*9d9f78edSMike Turquette #include <linux/io.h>
17*9d9f78edSMike Turquette #include <linux/err.h>
18*9d9f78edSMike Turquette #include <linux/string.h>
19*9d9f78edSMike Turquette 
20*9d9f78edSMike Turquette /*
21*9d9f78edSMike Turquette  * DOC: basic adjustable divider clock that cannot gate
22*9d9f78edSMike Turquette  *
23*9d9f78edSMike Turquette  * Traits of this clock:
24*9d9f78edSMike Turquette  * prepare - clk_prepare only ensures that parents are prepared
25*9d9f78edSMike Turquette  * enable - clk_enable only ensures that parents are enabled
26*9d9f78edSMike Turquette  * rate - rate is adjustable.  clk->rate = parent->rate / divisor
27*9d9f78edSMike Turquette  * parent - fixed parent.  No clk_set_parent support
28*9d9f78edSMike Turquette  */
29*9d9f78edSMike Turquette 
30*9d9f78edSMike Turquette #define to_clk_divider(_hw) container_of(_hw, struct clk_divider, hw)
31*9d9f78edSMike Turquette 
32*9d9f78edSMike Turquette #define div_mask(d)	((1 << (d->width)) - 1)
33*9d9f78edSMike Turquette 
34*9d9f78edSMike Turquette static unsigned long clk_divider_recalc_rate(struct clk_hw *hw,
35*9d9f78edSMike Turquette 		unsigned long parent_rate)
36*9d9f78edSMike Turquette {
37*9d9f78edSMike Turquette 	struct clk_divider *divider = to_clk_divider(hw);
38*9d9f78edSMike Turquette 	unsigned int div;
39*9d9f78edSMike Turquette 
40*9d9f78edSMike Turquette 	div = readl(divider->reg) >> divider->shift;
41*9d9f78edSMike Turquette 	div &= div_mask(divider);
42*9d9f78edSMike Turquette 
43*9d9f78edSMike Turquette 	if (!(divider->flags & CLK_DIVIDER_ONE_BASED))
44*9d9f78edSMike Turquette 		div++;
45*9d9f78edSMike Turquette 
46*9d9f78edSMike Turquette 	return parent_rate / div;
47*9d9f78edSMike Turquette }
48*9d9f78edSMike Turquette EXPORT_SYMBOL_GPL(clk_divider_recalc_rate);
49*9d9f78edSMike Turquette 
50*9d9f78edSMike Turquette /*
51*9d9f78edSMike Turquette  * The reverse of DIV_ROUND_UP: The maximum number which
52*9d9f78edSMike Turquette  * divided by m is r
53*9d9f78edSMike Turquette  */
54*9d9f78edSMike Turquette #define MULT_ROUND_UP(r, m) ((r) * (m) + (m) - 1)
55*9d9f78edSMike Turquette 
56*9d9f78edSMike Turquette static int clk_divider_bestdiv(struct clk_hw *hw, unsigned long rate,
57*9d9f78edSMike Turquette 		unsigned long *best_parent_rate)
58*9d9f78edSMike Turquette {
59*9d9f78edSMike Turquette 	struct clk_divider *divider = to_clk_divider(hw);
60*9d9f78edSMike Turquette 	int i, bestdiv = 0;
61*9d9f78edSMike Turquette 	unsigned long parent_rate, best = 0, now, maxdiv;
62*9d9f78edSMike Turquette 
63*9d9f78edSMike Turquette 	if (!rate)
64*9d9f78edSMike Turquette 		rate = 1;
65*9d9f78edSMike Turquette 
66*9d9f78edSMike Turquette 	maxdiv = (1 << divider->width);
67*9d9f78edSMike Turquette 
68*9d9f78edSMike Turquette 	if (divider->flags & CLK_DIVIDER_ONE_BASED)
69*9d9f78edSMike Turquette 		maxdiv--;
70*9d9f78edSMike Turquette 
71*9d9f78edSMike Turquette 	if (!best_parent_rate) {
72*9d9f78edSMike Turquette 		parent_rate = __clk_get_rate(__clk_get_parent(hw->clk));
73*9d9f78edSMike Turquette 		bestdiv = DIV_ROUND_UP(parent_rate, rate);
74*9d9f78edSMike Turquette 		bestdiv = bestdiv == 0 ? 1 : bestdiv;
75*9d9f78edSMike Turquette 		bestdiv = bestdiv > maxdiv ? maxdiv : bestdiv;
76*9d9f78edSMike Turquette 		return bestdiv;
77*9d9f78edSMike Turquette 	}
78*9d9f78edSMike Turquette 
79*9d9f78edSMike Turquette 	/*
80*9d9f78edSMike Turquette 	 * The maximum divider we can use without overflowing
81*9d9f78edSMike Turquette 	 * unsigned long in rate * i below
82*9d9f78edSMike Turquette 	 */
83*9d9f78edSMike Turquette 	maxdiv = min(ULONG_MAX / rate, maxdiv);
84*9d9f78edSMike Turquette 
85*9d9f78edSMike Turquette 	for (i = 1; i <= maxdiv; i++) {
86*9d9f78edSMike Turquette 		parent_rate = __clk_round_rate(__clk_get_parent(hw->clk),
87*9d9f78edSMike Turquette 				MULT_ROUND_UP(rate, i));
88*9d9f78edSMike Turquette 		now = parent_rate / i;
89*9d9f78edSMike Turquette 		if (now <= rate && now > best) {
90*9d9f78edSMike Turquette 			bestdiv = i;
91*9d9f78edSMike Turquette 			best = now;
92*9d9f78edSMike Turquette 			*best_parent_rate = parent_rate;
93*9d9f78edSMike Turquette 		}
94*9d9f78edSMike Turquette 	}
95*9d9f78edSMike Turquette 
96*9d9f78edSMike Turquette 	if (!bestdiv) {
97*9d9f78edSMike Turquette 		bestdiv = (1 << divider->width);
98*9d9f78edSMike Turquette 		if (divider->flags & CLK_DIVIDER_ONE_BASED)
99*9d9f78edSMike Turquette 			bestdiv--;
100*9d9f78edSMike Turquette 		*best_parent_rate = __clk_round_rate(__clk_get_parent(hw->clk), 1);
101*9d9f78edSMike Turquette 	}
102*9d9f78edSMike Turquette 
103*9d9f78edSMike Turquette 	return bestdiv;
104*9d9f78edSMike Turquette }
105*9d9f78edSMike Turquette 
106*9d9f78edSMike Turquette static long clk_divider_round_rate(struct clk_hw *hw, unsigned long rate,
107*9d9f78edSMike Turquette 				unsigned long *prate)
108*9d9f78edSMike Turquette {
109*9d9f78edSMike Turquette 	int div;
110*9d9f78edSMike Turquette 	div = clk_divider_bestdiv(hw, rate, prate);
111*9d9f78edSMike Turquette 
112*9d9f78edSMike Turquette 	if (prate)
113*9d9f78edSMike Turquette 		return *prate / div;
114*9d9f78edSMike Turquette 	else {
115*9d9f78edSMike Turquette 		unsigned long r;
116*9d9f78edSMike Turquette 		r = __clk_get_rate(__clk_get_parent(hw->clk));
117*9d9f78edSMike Turquette 		return r / div;
118*9d9f78edSMike Turquette 	}
119*9d9f78edSMike Turquette }
120*9d9f78edSMike Turquette EXPORT_SYMBOL_GPL(clk_divider_round_rate);
121*9d9f78edSMike Turquette 
122*9d9f78edSMike Turquette static int clk_divider_set_rate(struct clk_hw *hw, unsigned long rate)
123*9d9f78edSMike Turquette {
124*9d9f78edSMike Turquette 	struct clk_divider *divider = to_clk_divider(hw);
125*9d9f78edSMike Turquette 	unsigned int div;
126*9d9f78edSMike Turquette 	unsigned long flags = 0;
127*9d9f78edSMike Turquette 	u32 val;
128*9d9f78edSMike Turquette 
129*9d9f78edSMike Turquette 	div = __clk_get_rate(__clk_get_parent(hw->clk)) / rate;
130*9d9f78edSMike Turquette 
131*9d9f78edSMike Turquette 	if (!(divider->flags & CLK_DIVIDER_ONE_BASED))
132*9d9f78edSMike Turquette 		div--;
133*9d9f78edSMike Turquette 
134*9d9f78edSMike Turquette 	if (div > div_mask(divider))
135*9d9f78edSMike Turquette 		div = div_mask(divider);
136*9d9f78edSMike Turquette 
137*9d9f78edSMike Turquette 	if (divider->lock)
138*9d9f78edSMike Turquette 		spin_lock_irqsave(divider->lock, flags);
139*9d9f78edSMike Turquette 
140*9d9f78edSMike Turquette 	val = readl(divider->reg);
141*9d9f78edSMike Turquette 	val &= ~(div_mask(divider) << divider->shift);
142*9d9f78edSMike Turquette 	val |= div << divider->shift;
143*9d9f78edSMike Turquette 	writel(val, divider->reg);
144*9d9f78edSMike Turquette 
145*9d9f78edSMike Turquette 	if (divider->lock)
146*9d9f78edSMike Turquette 		spin_unlock_irqrestore(divider->lock, flags);
147*9d9f78edSMike Turquette 
148*9d9f78edSMike Turquette 	return 0;
149*9d9f78edSMike Turquette }
150*9d9f78edSMike Turquette EXPORT_SYMBOL_GPL(clk_divider_set_rate);
151*9d9f78edSMike Turquette 
152*9d9f78edSMike Turquette struct clk_ops clk_divider_ops = {
153*9d9f78edSMike Turquette 	.recalc_rate = clk_divider_recalc_rate,
154*9d9f78edSMike Turquette 	.round_rate = clk_divider_round_rate,
155*9d9f78edSMike Turquette 	.set_rate = clk_divider_set_rate,
156*9d9f78edSMike Turquette };
157*9d9f78edSMike Turquette EXPORT_SYMBOL_GPL(clk_divider_ops);
158*9d9f78edSMike Turquette 
159*9d9f78edSMike Turquette struct clk *clk_register_divider(struct device *dev, const char *name,
160*9d9f78edSMike Turquette 		const char *parent_name, unsigned long flags,
161*9d9f78edSMike Turquette 		void __iomem *reg, u8 shift, u8 width,
162*9d9f78edSMike Turquette 		u8 clk_divider_flags, spinlock_t *lock)
163*9d9f78edSMike Turquette {
164*9d9f78edSMike Turquette 	struct clk_divider *div;
165*9d9f78edSMike Turquette 	struct clk *clk;
166*9d9f78edSMike Turquette 
167*9d9f78edSMike Turquette 	div = kzalloc(sizeof(struct clk_divider), GFP_KERNEL);
168*9d9f78edSMike Turquette 
169*9d9f78edSMike Turquette 	if (!div) {
170*9d9f78edSMike Turquette 		pr_err("%s: could not allocate divider clk\n", __func__);
171*9d9f78edSMike Turquette 		return NULL;
172*9d9f78edSMike Turquette 	}
173*9d9f78edSMike Turquette 
174*9d9f78edSMike Turquette 	/* struct clk_divider assignments */
175*9d9f78edSMike Turquette 	div->reg = reg;
176*9d9f78edSMike Turquette 	div->shift = shift;
177*9d9f78edSMike Turquette 	div->width = width;
178*9d9f78edSMike Turquette 	div->flags = clk_divider_flags;
179*9d9f78edSMike Turquette 	div->lock = lock;
180*9d9f78edSMike Turquette 
181*9d9f78edSMike Turquette 	if (parent_name) {
182*9d9f78edSMike Turquette 		div->parent[0] = kstrdup(parent_name, GFP_KERNEL);
183*9d9f78edSMike Turquette 		if (!div->parent[0])
184*9d9f78edSMike Turquette 			goto out;
185*9d9f78edSMike Turquette 	}
186*9d9f78edSMike Turquette 
187*9d9f78edSMike Turquette 	clk = clk_register(dev, name,
188*9d9f78edSMike Turquette 			&clk_divider_ops, &div->hw,
189*9d9f78edSMike Turquette 			div->parent,
190*9d9f78edSMike Turquette 			(parent_name ? 1 : 0),
191*9d9f78edSMike Turquette 			flags);
192*9d9f78edSMike Turquette 	if (clk)
193*9d9f78edSMike Turquette 		return clk;
194*9d9f78edSMike Turquette 
195*9d9f78edSMike Turquette out:
196*9d9f78edSMike Turquette 	kfree(div->parent[0]);
197*9d9f78edSMike Turquette 	kfree(div);
198*9d9f78edSMike Turquette 
199*9d9f78edSMike Turquette 	return NULL;
200*9d9f78edSMike Turquette }
201