Skip to content

Commit 7029db0

Browse files
claudiubezneabebarino
authored andcommitted
clk: at91: clk-master: add notifier for divider
SAMA7G5 supports DVFS by changing cpuck. On SAMA7G5 mck0 shares the same parent with cpuck as seen in the following clock tree: +----------> cpuck | FRAC PLL ---> DIV PLL -+-> DIV ---> mck0 mck0 could go b/w 32KHz and 200MHz on SAMA7G5. To avoid mck0 overclocking while changing FRAC PLL or DIV PLL the commit implements a notifier for mck0 which applies a safe divider to register (maximum value of the divider which is 5) on PRE_RATE_CHANGE events (such that changes on PLL to not overclock mck0) and sets the maximum allowed rate on POST_RATE_CHANGE events. Signed-off-by: Claudiu Beznea <[email protected]> Link: https://lore.kernel.org/r/[email protected] Acked-by: Nicolas Ferre <[email protected]> Signed-off-by: Stephen Boyd <[email protected]>
1 parent 1e229c2 commit 7029db0

File tree

13 files changed

+186
-82
lines changed

13 files changed

+186
-82
lines changed

drivers/clk/at91/at91rm9200.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ static void __init at91rm9200_pmc_setup(struct device_node *np)
152152
"masterck_pres",
153153
&at91rm9200_master_layout,
154154
&rm9200_mck_characteristics,
155-
&rm9200_mck_lock, CLK_SET_RATE_GATE);
155+
&rm9200_mck_lock, CLK_SET_RATE_GATE, 0);
156156
if (IS_ERR(hw))
157157
goto err_free;
158158

drivers/clk/at91/at91sam9260.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -429,7 +429,7 @@ static void __init at91sam926x_pmc_setup(struct device_node *np,
429429
&at91rm9200_master_layout,
430430
data->mck_characteristics,
431431
&at91sam9260_mck_lock,
432-
CLK_SET_RATE_GATE);
432+
CLK_SET_RATE_GATE, 0);
433433
if (IS_ERR(hw))
434434
goto err_free;
435435

drivers/clk/at91/at91sam9g45.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ static void __init at91sam9g45_pmc_setup(struct device_node *np)
164164
&at91rm9200_master_layout,
165165
&mck_characteristics,
166166
&at91sam9g45_mck_lock,
167-
CLK_SET_RATE_GATE);
167+
CLK_SET_RATE_GATE, 0);
168168
if (IS_ERR(hw))
169169
goto err_free;
170170

drivers/clk/at91/at91sam9n12.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ static void __init at91sam9n12_pmc_setup(struct device_node *np)
191191
&at91sam9x5_master_layout,
192192
&mck_characteristics,
193193
&at91sam9n12_mck_lock,
194-
CLK_SET_RATE_GATE);
194+
CLK_SET_RATE_GATE, 0);
195195
if (IS_ERR(hw))
196196
goto err_free;
197197

drivers/clk/at91/at91sam9rl.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ static void __init at91sam9rl_pmc_setup(struct device_node *np)
132132
"masterck_pres",
133133
&at91rm9200_master_layout,
134134
&sam9rl_mck_characteristics,
135-
&sam9rl_mck_lock, CLK_SET_RATE_GATE);
135+
&sam9rl_mck_lock, CLK_SET_RATE_GATE, 0);
136136
if (IS_ERR(hw))
137137
goto err_free;
138138

drivers/clk/at91/at91sam9x5.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ static void __init at91sam9x5_pmc_setup(struct device_node *np,
210210
"masterck_pres",
211211
&at91sam9x5_master_layout,
212212
&mck_characteristics, &mck_lock,
213-
CLK_SET_RATE_GATE);
213+
CLK_SET_RATE_GATE, 0);
214214
if (IS_ERR(hw))
215215
goto err_free;
216216

drivers/clk/at91/clk-master.c

Lines changed: 174 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
#include <linux/clk-provider.h>
77
#include <linux/clkdev.h>
8+
#include <linux/clk.h>
89
#include <linux/clk/at91_pmc.h>
910
#include <linux/of.h>
1011
#include <linux/mfd/syscon.h>
@@ -36,8 +37,12 @@ struct clk_master {
3637
u8 id;
3738
u8 parent;
3839
u8 div;
40+
u32 safe_div;
3941
};
4042

43+
/* MCK div reference to be used by notifier. */
44+
static struct clk_master *master_div;
45+
4146
static inline bool clk_master_ready(struct clk_master *master)
4247
{
4348
unsigned int bit = master->id ? AT91_PMC_MCKXRDY : AT91_PMC_MCKRDY;
@@ -153,121 +158,198 @@ static const struct clk_ops master_div_ops = {
153158
.restore_context = clk_master_div_restore_context,
154159
};
155160

156-
static int clk_master_div_set_rate(struct clk_hw *hw, unsigned long rate,
157-
unsigned long parent_rate)
161+
/* This function must be called with lock acquired. */
162+
static int clk_master_div_set(struct clk_master *master,
163+
unsigned long parent_rate, int div)
158164
{
159-
struct clk_master *master = to_clk_master(hw);
160165
const struct clk_master_characteristics *characteristics =
161166
master->characteristics;
162-
unsigned long flags;
163-
unsigned int mckr, tmp;
164-
int div, i;
167+
unsigned long rate = parent_rate;
168+
unsigned int max_div = 0, div_index = 0, max_div_index = 0;
169+
unsigned int i, mckr, tmp;
165170
int ret;
166171

167-
div = DIV_ROUND_CLOSEST(parent_rate, rate);
168-
if (div > ARRAY_SIZE(characteristics->divisors))
169-
return -EINVAL;
170-
171172
for (i = 0; i < ARRAY_SIZE(characteristics->divisors); i++) {
172173
if (!characteristics->divisors[i])
173174
break;
174175

175-
if (div == characteristics->divisors[i]) {
176-
div = i;
177-
break;
176+
if (div == characteristics->divisors[i])
177+
div_index = i;
178+
179+
if (max_div < characteristics->divisors[i]) {
180+
max_div = characteristics->divisors[i];
181+
max_div_index = i;
178182
}
179183
}
180184

181-
if (i == ARRAY_SIZE(characteristics->divisors))
182-
return -EINVAL;
185+
if (div > max_div)
186+
div_index = max_div_index;
183187

184-
spin_lock_irqsave(master->lock, flags);
185188
ret = regmap_read(master->regmap, master->layout->offset, &mckr);
186189
if (ret)
187-
goto unlock;
190+
return ret;
188191

189192
mckr &= master->layout->mask;
190193
tmp = (mckr >> MASTER_DIV_SHIFT) & MASTER_DIV_MASK;
191-
if (tmp == div)
192-
goto unlock;
194+
if (tmp == div_index)
195+
return 0;
196+
197+
rate /= characteristics->divisors[div_index];
198+
if (rate < characteristics->output.min)
199+
pr_warn("master clk div is underclocked");
200+
else if (rate > characteristics->output.max)
201+
pr_warn("master clk div is overclocked");
193202

194203
mckr &= ~(MASTER_DIV_MASK << MASTER_DIV_SHIFT);
195-
mckr |= (div << MASTER_DIV_SHIFT);
204+
mckr |= (div_index << MASTER_DIV_SHIFT);
196205
ret = regmap_write(master->regmap, master->layout->offset, mckr);
197206
if (ret)
198-
goto unlock;
207+
return ret;
199208

200209
while (!clk_master_ready(master))
201210
cpu_relax();
202-
unlock:
203-
spin_unlock_irqrestore(master->lock, flags);
211+
212+
master->div = characteristics->divisors[div_index];
204213

205214
return 0;
206215
}
207216

208-
static int clk_master_div_determine_rate(struct clk_hw *hw,
209-
struct clk_rate_request *req)
217+
static unsigned long clk_master_div_recalc_rate_chg(struct clk_hw *hw,
218+
unsigned long parent_rate)
210219
{
211220
struct clk_master *master = to_clk_master(hw);
212-
const struct clk_master_characteristics *characteristics =
213-
master->characteristics;
214-
struct clk_hw *parent;
215-
unsigned long parent_rate, tmp_rate, best_rate = 0;
216-
int i, best_diff = INT_MIN, tmp_diff;
217-
218-
parent = clk_hw_get_parent(hw);
219-
if (!parent)
220-
return -EINVAL;
221-
222-
parent_rate = clk_hw_get_rate(parent);
223-
if (!parent_rate)
224-
return -EINVAL;
225-
226-
for (i = 0; i < ARRAY_SIZE(characteristics->divisors); i++) {
227-
if (!characteristics->divisors[i])
228-
break;
229221

230-
tmp_rate = DIV_ROUND_CLOSEST_ULL(parent_rate,
231-
characteristics->divisors[i]);
232-
tmp_diff = abs(tmp_rate - req->rate);
233-
234-
if (!best_rate || best_diff > tmp_diff) {
235-
best_diff = tmp_diff;
236-
best_rate = tmp_rate;
237-
}
238-
239-
if (!best_diff)
240-
break;
241-
}
242-
243-
req->best_parent_rate = best_rate;
244-
req->best_parent_hw = parent;
245-
req->rate = best_rate;
246-
247-
return 0;
222+
return DIV_ROUND_CLOSEST_ULL(parent_rate, master->div);
248223
}
249224

250225
static void clk_master_div_restore_context_chg(struct clk_hw *hw)
251226
{
252227
struct clk_master *master = to_clk_master(hw);
228+
unsigned long flags;
253229
int ret;
254230

255-
ret = clk_master_div_set_rate(hw, master->pms.rate,
256-
master->pms.parent_rate);
231+
spin_lock_irqsave(master->lock, flags);
232+
ret = clk_master_div_set(master, master->pms.parent_rate,
233+
DIV_ROUND_CLOSEST(master->pms.parent_rate,
234+
master->pms.rate));
235+
spin_unlock_irqrestore(master->lock, flags);
257236
if (ret)
258237
pr_warn("Failed to restore MCK DIV clock\n");
259238
}
260239

261240
static const struct clk_ops master_div_ops_chg = {
262241
.prepare = clk_master_prepare,
263242
.is_prepared = clk_master_is_prepared,
264-
.recalc_rate = clk_master_div_recalc_rate,
265-
.determine_rate = clk_master_div_determine_rate,
266-
.set_rate = clk_master_div_set_rate,
243+
.recalc_rate = clk_master_div_recalc_rate_chg,
267244
.save_context = clk_master_div_save_context,
268245
.restore_context = clk_master_div_restore_context_chg,
269246
};
270247

248+
static int clk_master_div_notifier_fn(struct notifier_block *notifier,
249+
unsigned long code, void *data)
250+
{
251+
const struct clk_master_characteristics *characteristics =
252+
master_div->characteristics;
253+
struct clk_notifier_data *cnd = data;
254+
unsigned long flags, new_parent_rate, new_rate;
255+
unsigned int mckr, div, new_div = 0;
256+
int ret, i;
257+
long tmp_diff;
258+
long best_diff = -1;
259+
260+
spin_lock_irqsave(master_div->lock, flags);
261+
switch (code) {
262+
case PRE_RATE_CHANGE:
263+
/*
264+
* We want to avoid any overclocking of MCK DIV domain. To do
265+
* this we set a safe divider (the underclocking is not of
266+
* interest as we can go as low as 32KHz). The relation
267+
* b/w this clock and its parents are as follows:
268+
*
269+
* FRAC PLL -> DIV PLL -> MCK DIV
270+
*
271+
* With the proper safe divider we should be good even with FRAC
272+
* PLL at its maximum value.
273+
*/
274+
ret = regmap_read(master_div->regmap, master_div->layout->offset,
275+
&mckr);
276+
if (ret) {
277+
ret = NOTIFY_STOP_MASK;
278+
goto unlock;
279+
}
280+
281+
mckr &= master_div->layout->mask;
282+
div = (mckr >> MASTER_DIV_SHIFT) & MASTER_DIV_MASK;
283+
284+
/* Switch to safe divider. */
285+
clk_master_div_set(master_div,
286+
cnd->old_rate * characteristics->divisors[div],
287+
master_div->safe_div);
288+
break;
289+
290+
case POST_RATE_CHANGE:
291+
/*
292+
* At this point we want to restore MCK DIV domain to its maximum
293+
* allowed rate.
294+
*/
295+
ret = regmap_read(master_div->regmap, master_div->layout->offset,
296+
&mckr);
297+
if (ret) {
298+
ret = NOTIFY_STOP_MASK;
299+
goto unlock;
300+
}
301+
302+
mckr &= master_div->layout->mask;
303+
div = (mckr >> MASTER_DIV_SHIFT) & MASTER_DIV_MASK;
304+
new_parent_rate = cnd->new_rate * characteristics->divisors[div];
305+
306+
for (i = 0; i < ARRAY_SIZE(characteristics->divisors); i++) {
307+
if (!characteristics->divisors[i])
308+
break;
309+
310+
new_rate = DIV_ROUND_CLOSEST_ULL(new_parent_rate,
311+
characteristics->divisors[i]);
312+
313+
tmp_diff = characteristics->output.max - new_rate;
314+
if (tmp_diff < 0)
315+
continue;
316+
317+
if (best_diff < 0 || best_diff > tmp_diff) {
318+
new_div = characteristics->divisors[i];
319+
best_diff = tmp_diff;
320+
}
321+
322+
if (!tmp_diff)
323+
break;
324+
}
325+
326+
if (!new_div) {
327+
ret = NOTIFY_STOP_MASK;
328+
goto unlock;
329+
}
330+
331+
/* Update the div to preserve MCK DIV clock rate. */
332+
clk_master_div_set(master_div, new_parent_rate,
333+
new_div);
334+
335+
ret = NOTIFY_OK;
336+
break;
337+
338+
default:
339+
ret = NOTIFY_DONE;
340+
break;
341+
}
342+
343+
unlock:
344+
spin_unlock_irqrestore(master_div->lock, flags);
345+
346+
return ret;
347+
}
348+
349+
static struct notifier_block clk_master_div_notifier = {
350+
.notifier_call = clk_master_div_notifier_fn,
351+
};
352+
271353
static void clk_sama7g5_master_best_diff(struct clk_rate_request *req,
272354
struct clk_hw *parent,
273355
unsigned long parent_rate,
@@ -496,6 +578,8 @@ at91_clk_register_master_internal(struct regmap *regmap,
496578
struct clk_master *master;
497579
struct clk_init_data init;
498580
struct clk_hw *hw;
581+
unsigned int mckr;
582+
unsigned long irqflags;
499583
int ret;
500584

501585
if (!name || !num_parents || !parent_names || !lock)
@@ -518,6 +602,16 @@ at91_clk_register_master_internal(struct regmap *regmap,
518602
master->chg_pid = chg_pid;
519603
master->lock = lock;
520604

605+
if (ops == &master_div_ops_chg) {
606+
spin_lock_irqsave(master->lock, irqflags);
607+
regmap_read(master->regmap, master->layout->offset, &mckr);
608+
spin_unlock_irqrestore(master->lock, irqflags);
609+
610+
mckr &= layout->mask;
611+
mckr = (mckr >> MASTER_DIV_SHIFT) & MASTER_DIV_MASK;
612+
master->div = characteristics->divisors[mckr];
613+
}
614+
521615
hw = &master->hw;
522616
ret = clk_hw_register(NULL, &master->hw);
523617
if (ret) {
@@ -554,19 +648,29 @@ at91_clk_register_master_div(struct regmap *regmap,
554648
const char *name, const char *parent_name,
555649
const struct clk_master_layout *layout,
556650
const struct clk_master_characteristics *characteristics,
557-
spinlock_t *lock, u32 flags)
651+
spinlock_t *lock, u32 flags, u32 safe_div)
558652
{
559653
const struct clk_ops *ops;
654+
struct clk_hw *hw;
560655

561656
if (flags & CLK_SET_RATE_GATE)
562657
ops = &master_div_ops;
563658
else
564659
ops = &master_div_ops_chg;
565660

566-
return at91_clk_register_master_internal(regmap, name, 1,
567-
&parent_name, layout,
568-
characteristics, ops,
569-
lock, flags, -EINVAL);
661+
hw = at91_clk_register_master_internal(regmap, name, 1,
662+
&parent_name, layout,
663+
characteristics, ops,
664+
lock, flags, -EINVAL);
665+
666+
if (!IS_ERR(hw) && safe_div) {
667+
master_div = to_clk_master(hw);
668+
master_div->safe_div = safe_div;
669+
clk_notifier_register(hw->clk,
670+
&clk_master_div_notifier);
671+
}
672+
673+
return hw;
570674
}
571675

572676
static unsigned long

0 commit comments

Comments
 (0)