xref: /openbmc/linux/drivers/clk/at91/clk-master.c (revision 20e2fc42)
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  *  Copyright (C) 2013 Boris BREZILLON <b.brezillon@overkiz.com>
4  */
5 
6 #include <linux/clk-provider.h>
7 #include <linux/clkdev.h>
8 #include <linux/clk/at91_pmc.h>
9 #include <linux/of.h>
10 #include <linux/mfd/syscon.h>
11 #include <linux/regmap.h>
12 
13 #include "pmc.h"
14 
15 #define MASTER_PRES_MASK	0x7
16 #define MASTER_PRES_MAX		MASTER_PRES_MASK
17 #define MASTER_DIV_SHIFT	8
18 #define MASTER_DIV_MASK		0x3
19 
20 #define to_clk_master(hw) container_of(hw, struct clk_master, hw)
21 
22 struct clk_master {
23 	struct clk_hw hw;
24 	struct regmap *regmap;
25 	const struct clk_master_layout *layout;
26 	const struct clk_master_characteristics *characteristics;
27 	u32 mckr;
28 };
29 
30 static inline bool clk_master_ready(struct regmap *regmap)
31 {
32 	unsigned int status;
33 
34 	regmap_read(regmap, AT91_PMC_SR, &status);
35 
36 	return status & AT91_PMC_MCKRDY ? 1 : 0;
37 }
38 
39 static int clk_master_prepare(struct clk_hw *hw)
40 {
41 	struct clk_master *master = to_clk_master(hw);
42 
43 	while (!clk_master_ready(master->regmap))
44 		cpu_relax();
45 
46 	return 0;
47 }
48 
49 static int clk_master_is_prepared(struct clk_hw *hw)
50 {
51 	struct clk_master *master = to_clk_master(hw);
52 
53 	return clk_master_ready(master->regmap);
54 }
55 
56 static unsigned long clk_master_recalc_rate(struct clk_hw *hw,
57 					    unsigned long parent_rate)
58 {
59 	u8 pres;
60 	u8 div;
61 	unsigned long rate = parent_rate;
62 	struct clk_master *master = to_clk_master(hw);
63 	const struct clk_master_layout *layout = master->layout;
64 	const struct clk_master_characteristics *characteristics =
65 						master->characteristics;
66 	unsigned int mckr;
67 
68 	regmap_read(master->regmap, master->layout->offset, &mckr);
69 	mckr &= layout->mask;
70 
71 	pres = (mckr >> layout->pres_shift) & MASTER_PRES_MASK;
72 	div = (mckr >> MASTER_DIV_SHIFT) & MASTER_DIV_MASK;
73 
74 	if (characteristics->have_div3_pres && pres == MASTER_PRES_MAX)
75 		rate /= 3;
76 	else
77 		rate >>= pres;
78 
79 	rate /= characteristics->divisors[div];
80 
81 	if (rate < characteristics->output.min)
82 		pr_warn("master clk is underclocked");
83 	else if (rate > characteristics->output.max)
84 		pr_warn("master clk is overclocked");
85 
86 	return rate;
87 }
88 
89 static u8 clk_master_get_parent(struct clk_hw *hw)
90 {
91 	struct clk_master *master = to_clk_master(hw);
92 	unsigned int mckr;
93 
94 	regmap_read(master->regmap, master->layout->offset, &mckr);
95 
96 	return mckr & AT91_PMC_CSS;
97 }
98 
99 static const struct clk_ops master_ops = {
100 	.prepare = clk_master_prepare,
101 	.is_prepared = clk_master_is_prepared,
102 	.recalc_rate = clk_master_recalc_rate,
103 	.get_parent = clk_master_get_parent,
104 };
105 
106 struct clk_hw * __init
107 at91_clk_register_master(struct regmap *regmap,
108 		const char *name, int num_parents,
109 		const char **parent_names,
110 		const struct clk_master_layout *layout,
111 		const struct clk_master_characteristics *characteristics)
112 {
113 	struct clk_master *master;
114 	struct clk_init_data init;
115 	struct clk_hw *hw;
116 	int ret;
117 
118 	if (!name || !num_parents || !parent_names)
119 		return ERR_PTR(-EINVAL);
120 
121 	master = kzalloc(sizeof(*master), GFP_KERNEL);
122 	if (!master)
123 		return ERR_PTR(-ENOMEM);
124 
125 	init.name = name;
126 	init.ops = &master_ops;
127 	init.parent_names = parent_names;
128 	init.num_parents = num_parents;
129 	init.flags = 0;
130 
131 	master->hw.init = &init;
132 	master->layout = layout;
133 	master->characteristics = characteristics;
134 	master->regmap = regmap;
135 
136 	hw = &master->hw;
137 	ret = clk_hw_register(NULL, &master->hw);
138 	if (ret) {
139 		kfree(master);
140 		hw = ERR_PTR(ret);
141 	}
142 
143 	return hw;
144 }
145 
146 const struct clk_master_layout at91rm9200_master_layout = {
147 	.mask = 0x31F,
148 	.pres_shift = 2,
149 	.offset = AT91_PMC_MCKR,
150 };
151 
152 const struct clk_master_layout at91sam9x5_master_layout = {
153 	.mask = 0x373,
154 	.pres_shift = 4,
155 	.offset = AT91_PMC_MCKR,
156 };
157