aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/clk/sunxi
diff options
context:
space:
mode:
authorMaxime Ripard <maxime.ripard@free-electrons.com>2014-07-11 12:43:18 -0400
committerMaxime Ripard <maxime.ripard@free-electrons.com>2014-09-27 02:58:04 -0400
commit37e1041f04717d726931c8688cbf425071aeb9c1 (patch)
tree3161defb1471bf0c31bc08747bfc3dedeb2ea6a1 /drivers/clk/sunxi
parenteaa18f5d0914b0151cefb52e2977a67ef21dfa64 (diff)
clk: sunxi: mod0: Introduce MMC proper phase handling
The MMC clock we thought we had until now are actually not one but three different clocks. The main one is unchanged, and will have three outputs: - The clock fed into the MMC - a sample and output clocks, to deal with when should we output/sample data to/from the MMC bus The phase control we had are actually controlling the two latter clocks, but the main MMC one is unchanged. We can adjust the phase with a 3 bits value, from 0 to 7, 0 meaning a 180 phase shift, and the other values being the number of periods from the MMC parent clock to outphase the clock of. Signed-off-by: Maxime Ripard <maxime.ripard@free-electrons.com> Acked-by: Hans de Goede <hdegoede@redhat.com>
Diffstat (limited to 'drivers/clk/sunxi')
-rw-r--r--drivers/clk/sunxi/clk-mod0.c189
1 files changed, 189 insertions, 0 deletions
diff --git a/drivers/clk/sunxi/clk-mod0.c b/drivers/clk/sunxi/clk-mod0.c
index 8a7f7036aea3..4a563850ee6e 100644
--- a/drivers/clk/sunxi/clk-mod0.c
+++ b/drivers/clk/sunxi/clk-mod0.c
@@ -16,6 +16,7 @@
16 16
17#include <linux/clk-provider.h> 17#include <linux/clk-provider.h>
18#include <linux/clkdev.h> 18#include <linux/clkdev.h>
19#include <linux/of_address.h>
19 20
20#include "clk-factors.h" 21#include "clk-factors.h"
21 22
@@ -92,3 +93,191 @@ static void __init sun5i_a13_mbus_setup(struct device_node *node)
92 clk_prepare_enable(mbus); 93 clk_prepare_enable(mbus);
93} 94}
94CLK_OF_DECLARE(sun5i_a13_mbus, "allwinner,sun5i-a13-mbus-clk", sun5i_a13_mbus_setup); 95CLK_OF_DECLARE(sun5i_a13_mbus, "allwinner,sun5i-a13-mbus-clk", sun5i_a13_mbus_setup);
96
97struct mmc_phase_data {
98 u8 offset;
99};
100
101struct mmc_phase {
102 struct clk_hw hw;
103 void __iomem *reg;
104 struct mmc_phase_data *data;
105 spinlock_t *lock;
106};
107
108#define to_mmc_phase(_hw) container_of(_hw, struct mmc_phase, hw)
109
110static int mmc_get_phase(struct clk_hw *hw)
111{
112 struct clk *mmc, *mmc_parent, *clk = hw->clk;
113 struct mmc_phase *phase = to_mmc_phase(hw);
114 unsigned int mmc_rate, mmc_parent_rate;
115 u16 step, mmc_div;
116 u32 value;
117 u8 delay;
118
119 value = readl(phase->reg);
120 delay = (value >> phase->data->offset) & 0x3;
121
122 if (!delay)
123 return 180;
124
125 /* Get the main MMC clock */
126 mmc = clk_get_parent(clk);
127 if (!mmc)
128 return -EINVAL;
129
130 /* And its rate */
131 mmc_rate = clk_get_rate(mmc);
132 if (!mmc_rate)
133 return -EINVAL;
134
135 /* Now, get the MMC parent (most likely some PLL) */
136 mmc_parent = clk_get_parent(mmc);
137 if (!mmc_parent)
138 return -EINVAL;
139
140 /* And its rate */
141 mmc_parent_rate = clk_get_rate(mmc_parent);
142 if (!mmc_parent_rate)
143 return -EINVAL;
144
145 /* Get MMC clock divider */
146 mmc_div = mmc_parent_rate / mmc_rate;
147
148 step = DIV_ROUND_CLOSEST(360, mmc_div);
149 return delay * step;
150}
151
152static int mmc_set_phase(struct clk_hw *hw, int degrees)
153{
154 struct clk *mmc, *mmc_parent, *clk = hw->clk;
155 struct mmc_phase *phase = to_mmc_phase(hw);
156 unsigned int mmc_rate, mmc_parent_rate;
157 unsigned long flags;
158 u32 value;
159 u8 delay;
160
161 /* Get the main MMC clock */
162 mmc = clk_get_parent(clk);
163 if (!mmc)
164 return -EINVAL;
165
166 /* And its rate */
167 mmc_rate = clk_get_rate(mmc);
168 if (!mmc_rate)
169 return -EINVAL;
170
171 /* Now, get the MMC parent (most likely some PLL) */
172 mmc_parent = clk_get_parent(mmc);
173 if (!mmc_parent)
174 return -EINVAL;
175
176 /* And its rate */
177 mmc_parent_rate = clk_get_rate(mmc_parent);
178 if (!mmc_parent_rate)
179 return -EINVAL;
180
181 if (degrees != 180) {
182 u16 step, mmc_div;
183
184 /* Get MMC clock divider */
185 mmc_div = mmc_parent_rate / mmc_rate;
186
187 /*
188 * We can only outphase the clocks by multiple of the
189 * PLL's period.
190 *
191 * Since the MMC clock in only a divider, and the
192 * formula to get the outphasing in degrees is deg =
193 * 360 * delta / period
194 *
195 * If we simplify this formula, we can see that the
196 * only thing that we're concerned about is the number
197 * of period we want to outphase our clock from, and
198 * the divider set by the MMC clock.
199 */
200 step = DIV_ROUND_CLOSEST(360, mmc_div);
201 delay = DIV_ROUND_CLOSEST(degrees, step);
202 } else {
203 delay = 0;
204 }
205
206 spin_lock_irqsave(phase->lock, flags);
207 value = readl(phase->reg);
208 value &= ~GENMASK(phase->data->offset + 3, phase->data->offset);
209 value |= delay << phase->data->offset;
210 writel(value, phase->reg);
211 spin_unlock_irqrestore(phase->lock, flags);
212
213 return 0;
214}
215
216static const struct clk_ops mmc_clk_ops = {
217 .get_phase = mmc_get_phase,
218 .set_phase = mmc_set_phase,
219};
220
221static void __init sun4i_a10_mmc_phase_setup(struct device_node *node,
222 struct mmc_phase_data *data)
223{
224 const char *parent_names[1] = { of_clk_get_parent_name(node, 0) };
225 struct clk_init_data init = {
226 .num_parents = 1,
227 .parent_names = parent_names,
228 .ops = &mmc_clk_ops,
229 };
230
231 struct mmc_phase *phase;
232 struct clk *clk;
233
234 phase = kmalloc(sizeof(*phase), GFP_KERNEL);
235 if (!phase)
236 return;
237
238 phase->hw.init = &init;
239
240 phase->reg = of_iomap(node, 0);
241 if (!phase->reg)
242 goto err_free;
243
244 phase->data = data;
245 phase->lock = &sun4i_a10_mod0_lock;
246
247 if (of_property_read_string(node, "clock-output-names", &init.name))
248 init.name = node->name;
249
250 clk = clk_register(NULL, &phase->hw);
251 if (IS_ERR(clk))
252 goto err_unmap;
253
254 of_clk_add_provider(node, of_clk_src_simple_get, clk);
255
256 return;
257
258err_unmap:
259 iounmap(phase->reg);
260err_free:
261 kfree(phase);
262}
263
264
265static struct mmc_phase_data mmc_output_clk = {
266 .offset = 8,
267};
268
269static struct mmc_phase_data mmc_sample_clk = {
270 .offset = 20,
271};
272
273static void __init sun4i_a10_mmc_output_setup(struct device_node *node)
274{
275 sun4i_a10_mmc_phase_setup(node, &mmc_output_clk);
276}
277CLK_OF_DECLARE(sun4i_a10_mmc_output, "allwinner,sun4i-a10-mmc-output-clk", sun4i_a10_mmc_output_setup);
278
279static void __init sun4i_a10_mmc_sample_setup(struct device_node *node)
280{
281 sun4i_a10_mmc_phase_setup(node, &mmc_sample_clk);
282}
283CLK_OF_DECLARE(sun4i_a10_mmc_sample, "allwinner,sun4i-a10-mmc-sample-clk", sun4i_a10_mmc_sample_setup);