Skip to content

Commit 75c8814

Browse files
claudiubezneabebarino
authored andcommitted
clk: at91: clk-master: add master clock support for SAMA7G5
Add master clock support (MCK1..4) for SAMA7G5. SAMA7G5's PMC has multiple master clocks feeding different subsystems. One of them feeds image subsystem and is changeable based on image subsystem needs. Signed-off-by: Claudiu Beznea <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Stephen Boyd <[email protected]>
1 parent 22a1dfe commit 75c8814

File tree

3 files changed

+313
-5
lines changed

3 files changed

+313
-5
lines changed

drivers/clk/at91/clk-master.c

Lines changed: 305 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,30 +17,49 @@
1717
#define MASTER_DIV_SHIFT 8
1818
#define MASTER_DIV_MASK 0x3
1919

20+
#define PMC_MCR 0x30
21+
#define PMC_MCR_ID_MSK GENMASK(3, 0)
22+
#define PMC_MCR_CMD BIT(7)
23+
#define PMC_MCR_DIV GENMASK(10, 8)
24+
#define PMC_MCR_CSS GENMASK(20, 16)
25+
#define PMC_MCR_CSS_SHIFT (16)
26+
#define PMC_MCR_EN BIT(28)
27+
28+
#define PMC_MCR_ID(x) ((x) & PMC_MCR_ID_MSK)
29+
30+
#define MASTER_MAX_ID 4
31+
2032
#define to_clk_master(hw) container_of(hw, struct clk_master, hw)
2133

2234
struct clk_master {
2335
struct clk_hw hw;
2436
struct regmap *regmap;
37+
spinlock_t *lock;
2538
const struct clk_master_layout *layout;
2639
const struct clk_master_characteristics *characteristics;
40+
u32 *mux_table;
2741
u32 mckr;
42+
int chg_pid;
43+
u8 id;
44+
u8 parent;
45+
u8 div;
2846
};
2947

30-
static inline bool clk_master_ready(struct regmap *regmap)
48+
static inline bool clk_master_ready(struct clk_master *master)
3149
{
50+
unsigned int bit = master->id ? AT91_PMC_MCKXRDY : AT91_PMC_MCKRDY;
3251
unsigned int status;
3352

34-
regmap_read(regmap, AT91_PMC_SR, &status);
53+
regmap_read(master->regmap, AT91_PMC_SR, &status);
3554

36-
return !!(status & AT91_PMC_MCKRDY);
55+
return !!(status & bit);
3756
}
3857

3958
static int clk_master_prepare(struct clk_hw *hw)
4059
{
4160
struct clk_master *master = to_clk_master(hw);
4261

43-
while (!clk_master_ready(master->regmap))
62+
while (!clk_master_ready(master))
4463
cpu_relax();
4564

4665
return 0;
@@ -50,7 +69,7 @@ static int clk_master_is_prepared(struct clk_hw *hw)
5069
{
5170
struct clk_master *master = to_clk_master(hw);
5271

53-
return clk_master_ready(master->regmap);
72+
return clk_master_ready(master);
5473
}
5574

5675
static unsigned long clk_master_recalc_rate(struct clk_hw *hw,
@@ -143,6 +162,287 @@ at91_clk_register_master(struct regmap *regmap,
143162
return hw;
144163
}
145164

165+
static unsigned long
166+
clk_sama7g5_master_recalc_rate(struct clk_hw *hw,
167+
unsigned long parent_rate)
168+
{
169+
struct clk_master *master = to_clk_master(hw);
170+
171+
return DIV_ROUND_CLOSEST_ULL(parent_rate, (1 << master->div));
172+
}
173+
174+
static void clk_sama7g5_master_best_diff(struct clk_rate_request *req,
175+
struct clk_hw *parent,
176+
unsigned long parent_rate,
177+
long *best_rate,
178+
long *best_diff,
179+
u32 div)
180+
{
181+
unsigned long tmp_rate, tmp_diff;
182+
183+
if (div == MASTER_PRES_MAX)
184+
tmp_rate = parent_rate / 3;
185+
else
186+
tmp_rate = parent_rate >> div;
187+
188+
tmp_diff = abs(req->rate - tmp_rate);
189+
190+
if (*best_diff < 0 || *best_diff >= tmp_diff) {
191+
*best_rate = tmp_rate;
192+
*best_diff = tmp_diff;
193+
req->best_parent_rate = parent_rate;
194+
req->best_parent_hw = parent;
195+
}
196+
}
197+
198+
static int clk_sama7g5_master_determine_rate(struct clk_hw *hw,
199+
struct clk_rate_request *req)
200+
{
201+
struct clk_master *master = to_clk_master(hw);
202+
struct clk_rate_request req_parent = *req;
203+
struct clk_hw *parent;
204+
long best_rate = LONG_MIN, best_diff = LONG_MIN;
205+
unsigned long parent_rate;
206+
unsigned int div, i;
207+
208+
/* First: check the dividers of MCR. */
209+
for (i = 0; i < clk_hw_get_num_parents(hw); i++) {
210+
parent = clk_hw_get_parent_by_index(hw, i);
211+
if (!parent)
212+
continue;
213+
214+
parent_rate = clk_hw_get_rate(parent);
215+
if (!parent_rate)
216+
continue;
217+
218+
for (div = 0; div < MASTER_PRES_MAX + 1; div++) {
219+
clk_sama7g5_master_best_diff(req, parent, parent_rate,
220+
&best_rate, &best_diff,
221+
div);
222+
if (!best_diff)
223+
break;
224+
}
225+
226+
if (!best_diff)
227+
break;
228+
}
229+
230+
/* Second: try to request rate form changeable parent. */
231+
if (master->chg_pid < 0)
232+
goto end;
233+
234+
parent = clk_hw_get_parent_by_index(hw, master->chg_pid);
235+
if (!parent)
236+
goto end;
237+
238+
for (div = 0; div < MASTER_PRES_MAX + 1; div++) {
239+
if (div == MASTER_PRES_MAX)
240+
req_parent.rate = req->rate * 3;
241+
else
242+
req_parent.rate = req->rate << div;
243+
244+
if (__clk_determine_rate(parent, &req_parent))
245+
continue;
246+
247+
clk_sama7g5_master_best_diff(req, parent, req_parent.rate,
248+
&best_rate, &best_diff, div);
249+
250+
if (!best_diff)
251+
break;
252+
}
253+
254+
end:
255+
pr_debug("MCK: %s, best_rate = %ld, parent clk: %s @ %ld\n",
256+
__func__, best_rate,
257+
__clk_get_name((req->best_parent_hw)->clk),
258+
req->best_parent_rate);
259+
260+
if (best_rate < 0)
261+
return -EINVAL;
262+
263+
req->rate = best_rate;
264+
265+
return 0;
266+
}
267+
268+
static u8 clk_sama7g5_master_get_parent(struct clk_hw *hw)
269+
{
270+
struct clk_master *master = to_clk_master(hw);
271+
unsigned long flags;
272+
u8 index;
273+
274+
spin_lock_irqsave(master->lock, flags);
275+
index = clk_mux_val_to_index(&master->hw, master->mux_table, 0,
276+
master->parent);
277+
spin_unlock_irqrestore(master->lock, flags);
278+
279+
return index;
280+
}
281+
282+
static int clk_sama7g5_master_set_parent(struct clk_hw *hw, u8 index)
283+
{
284+
struct clk_master *master = to_clk_master(hw);
285+
unsigned long flags;
286+
287+
if (index >= clk_hw_get_num_parents(hw))
288+
return -EINVAL;
289+
290+
spin_lock_irqsave(master->lock, flags);
291+
master->parent = clk_mux_index_to_val(master->mux_table, 0, index);
292+
spin_unlock_irqrestore(master->lock, flags);
293+
294+
return 0;
295+
}
296+
297+
static int clk_sama7g5_master_enable(struct clk_hw *hw)
298+
{
299+
struct clk_master *master = to_clk_master(hw);
300+
unsigned long flags;
301+
unsigned int val, cparent;
302+
303+
spin_lock_irqsave(master->lock, flags);
304+
305+
regmap_write(master->regmap, PMC_MCR, PMC_MCR_ID(master->id));
306+
regmap_read(master->regmap, PMC_MCR, &val);
307+
regmap_update_bits(master->regmap, PMC_MCR,
308+
PMC_MCR_EN | PMC_MCR_CSS | PMC_MCR_DIV |
309+
PMC_MCR_CMD | PMC_MCR_ID_MSK,
310+
PMC_MCR_EN | (master->parent << PMC_MCR_CSS_SHIFT) |
311+
(master->div << MASTER_DIV_SHIFT) |
312+
PMC_MCR_CMD | PMC_MCR_ID(master->id));
313+
314+
cparent = (val & PMC_MCR_CSS) >> PMC_MCR_CSS_SHIFT;
315+
316+
/* Wait here only if parent is being changed. */
317+
while ((cparent != master->parent) && !clk_master_ready(master))
318+
cpu_relax();
319+
320+
spin_unlock_irqrestore(master->lock, flags);
321+
322+
return 0;
323+
}
324+
325+
static void clk_sama7g5_master_disable(struct clk_hw *hw)
326+
{
327+
struct clk_master *master = to_clk_master(hw);
328+
unsigned long flags;
329+
330+
spin_lock_irqsave(master->lock, flags);
331+
332+
regmap_write(master->regmap, PMC_MCR, master->id);
333+
regmap_update_bits(master->regmap, PMC_MCR,
334+
PMC_MCR_EN | PMC_MCR_CMD | PMC_MCR_ID_MSK,
335+
PMC_MCR_CMD | PMC_MCR_ID(master->id));
336+
337+
spin_unlock_irqrestore(master->lock, flags);
338+
}
339+
340+
static int clk_sama7g5_master_is_enabled(struct clk_hw *hw)
341+
{
342+
struct clk_master *master = to_clk_master(hw);
343+
unsigned long flags;
344+
unsigned int val;
345+
346+
spin_lock_irqsave(master->lock, flags);
347+
348+
regmap_write(master->regmap, PMC_MCR, master->id);
349+
regmap_read(master->regmap, PMC_MCR, &val);
350+
351+
spin_unlock_irqrestore(master->lock, flags);
352+
353+
return !!(val & PMC_MCR_EN);
354+
}
355+
356+
static int clk_sama7g5_master_set_rate(struct clk_hw *hw, unsigned long rate,
357+
unsigned long parent_rate)
358+
{
359+
struct clk_master *master = to_clk_master(hw);
360+
unsigned long div, flags;
361+
362+
div = DIV_ROUND_CLOSEST(parent_rate, rate);
363+
if ((div > (1 << (MASTER_PRES_MAX - 1))) || (div & (div - 1)))
364+
return -EINVAL;
365+
366+
if (div == 3)
367+
div = MASTER_PRES_MAX;
368+
else
369+
div = ffs(div) - 1;
370+
371+
spin_lock_irqsave(master->lock, flags);
372+
master->div = div;
373+
spin_unlock_irqrestore(master->lock, flags);
374+
375+
return 0;
376+
}
377+
378+
static const struct clk_ops sama7g5_master_ops = {
379+
.enable = clk_sama7g5_master_enable,
380+
.disable = clk_sama7g5_master_disable,
381+
.is_enabled = clk_sama7g5_master_is_enabled,
382+
.recalc_rate = clk_sama7g5_master_recalc_rate,
383+
.determine_rate = clk_sama7g5_master_determine_rate,
384+
.set_rate = clk_sama7g5_master_set_rate,
385+
.get_parent = clk_sama7g5_master_get_parent,
386+
.set_parent = clk_sama7g5_master_set_parent,
387+
};
388+
389+
struct clk_hw * __init
390+
at91_clk_sama7g5_register_master(struct regmap *regmap,
391+
const char *name, int num_parents,
392+
const char **parent_names,
393+
u32 *mux_table,
394+
spinlock_t *lock, u8 id,
395+
bool critical, int chg_pid)
396+
{
397+
struct clk_master *master;
398+
struct clk_hw *hw;
399+
struct clk_init_data init;
400+
unsigned long flags;
401+
unsigned int val;
402+
int ret;
403+
404+
if (!name || !num_parents || !parent_names || !mux_table ||
405+
!lock || id > MASTER_MAX_ID)
406+
return ERR_PTR(-EINVAL);
407+
408+
master = kzalloc(sizeof(*master), GFP_KERNEL);
409+
if (!master)
410+
return ERR_PTR(-ENOMEM);
411+
412+
init.name = name;
413+
init.ops = &sama7g5_master_ops;
414+
init.parent_names = parent_names;
415+
init.num_parents = num_parents;
416+
init.flags = CLK_SET_RATE_GATE | CLK_SET_PARENT_GATE;
417+
if (chg_pid >= 0)
418+
init.flags |= CLK_SET_RATE_PARENT;
419+
if (critical)
420+
init.flags |= CLK_IS_CRITICAL;
421+
422+
master->hw.init = &init;
423+
master->regmap = regmap;
424+
master->id = id;
425+
master->chg_pid = chg_pid;
426+
master->lock = lock;
427+
master->mux_table = mux_table;
428+
429+
spin_lock_irqsave(master->lock, flags);
430+
regmap_write(master->regmap, PMC_MCR, master->id);
431+
regmap_read(master->regmap, PMC_MCR, &val);
432+
master->parent = (val & PMC_MCR_CSS) >> PMC_MCR_CSS_SHIFT;
433+
master->div = (val & PMC_MCR_DIV) >> MASTER_DIV_SHIFT;
434+
spin_unlock_irqrestore(master->lock, flags);
435+
436+
hw = &master->hw;
437+
ret = clk_hw_register(NULL, &master->hw);
438+
if (ret) {
439+
kfree(master);
440+
hw = ERR_PTR(ret);
441+
}
442+
443+
return hw;
444+
}
445+
146446
const struct clk_master_layout at91rm9200_master_layout = {
147447
.mask = 0x31F,
148448
.pres_shift = 2,

drivers/clk/at91/pmc.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,13 @@ at91_clk_register_master(struct regmap *regmap, const char *name,
154154
const struct clk_master_layout *layout,
155155
const struct clk_master_characteristics *characteristics);
156156

157+
struct clk_hw * __init
158+
at91_clk_sama7g5_register_master(struct regmap *regmap,
159+
const char *name, int num_parents,
160+
const char **parent_names, u32 *mux_table,
161+
spinlock_t *lock, u8 id, bool critical,
162+
int chg_pid);
163+
157164
struct clk_hw * __init
158165
at91_clk_register_peripheral(struct regmap *regmap, const char *name,
159166
const char *parent_name, u32 id);

include/linux/clk/at91_pmc.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@
174174
#define AT91_PMC_MOSCRCS (1 << 17) /* Main On-Chip RC [some SAM9] */
175175
#define AT91_PMC_CFDEV (1 << 18) /* Clock Failure Detector Event [some SAM9] */
176176
#define AT91_PMC_GCKRDY (1 << 24) /* Generated Clocks */
177+
#define AT91_PMC_MCKXRDY (1 << 26) /* Master Clock x [x=1..4] Ready Status */
177178
#define AT91_PMC_IMR 0x6c /* Interrupt Mask Register */
178179

179180
#define AT91_PMC_FSMR 0x70 /* Fast Startup Mode Register */

0 commit comments

Comments
 (0)