]>
Commit | Line | Data |
---|---|---|
953cc3e8 ML |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * Driver for Silicon Labs Si544 Programmable Oscillator | |
4 | * Copyright (C) 2018 Topic Embedded Products | |
5 | * Author: Mike Looijmans <[email protected]> | |
6 | */ | |
7 | ||
8 | #include <linux/clk-provider.h> | |
9 | #include <linux/delay.h> | |
10 | #include <linux/module.h> | |
11 | #include <linux/i2c.h> | |
12 | #include <linux/regmap.h> | |
13 | #include <linux/slab.h> | |
14 | ||
15 | /* I2C registers (decimal as in datasheet) */ | |
16 | #define SI544_REG_CONTROL 7 | |
17 | #define SI544_REG_OE_STATE 17 | |
18 | #define SI544_REG_HS_DIV 23 | |
19 | #define SI544_REG_LS_HS_DIV 24 | |
20 | #define SI544_REG_FBDIV0 26 | |
21 | #define SI544_REG_FBDIV8 27 | |
22 | #define SI544_REG_FBDIV16 28 | |
23 | #define SI544_REG_FBDIV24 29 | |
24 | #define SI544_REG_FBDIV32 30 | |
25 | #define SI544_REG_FBDIV40 31 | |
26 | #define SI544_REG_FCAL_OVR 69 | |
27 | #define SI544_REG_ADPLL_DELTA_M0 231 | |
28 | #define SI544_REG_ADPLL_DELTA_M8 232 | |
29 | #define SI544_REG_ADPLL_DELTA_M16 233 | |
30 | #define SI544_REG_PAGE_SELECT 255 | |
31 | ||
32 | /* Register values */ | |
33 | #define SI544_CONTROL_RESET BIT(7) | |
34 | #define SI544_CONTROL_MS_ICAL2 BIT(3) | |
35 | ||
36 | #define SI544_OE_STATE_ODC_OE BIT(0) | |
37 | ||
38 | /* Max freq depends on speed grade */ | |
39 | #define SI544_MIN_FREQ 200000U | |
40 | ||
41 | /* Si544 Internal oscilator runs at 55.05 MHz */ | |
42 | #define FXO 55050000U | |
43 | ||
44 | /* VCO range is 10.8 .. 12.1 GHz, max depends on speed grade */ | |
45 | #define FVCO_MIN 10800000000ULL | |
46 | ||
47 | #define HS_DIV_MAX 2046 | |
48 | #define HS_DIV_MAX_ODD 33 | |
49 | ||
50 | /* Lowest frequency synthesizeable using only the HS divider */ | |
51 | #define MIN_HSDIV_FREQ (FVCO_MIN / HS_DIV_MAX) | |
52 | ||
53 | enum si544_speed_grade { | |
54 | si544a, | |
55 | si544b, | |
56 | si544c, | |
57 | }; | |
58 | ||
59 | struct clk_si544 { | |
60 | struct clk_hw hw; | |
61 | struct regmap *regmap; | |
62 | struct i2c_client *i2c_client; | |
63 | enum si544_speed_grade speed_grade; | |
64 | }; | |
65 | #define to_clk_si544(_hw) container_of(_hw, struct clk_si544, hw) | |
66 | ||
67 | /** | |
68 | * struct clk_si544_muldiv - Multiplier/divider settings | |
69 | * @fb_div_frac: integer part of feedback divider (32 bits) | |
70 | * @fb_div_int: fractional part of feedback divider (11 bits) | |
71 | * @hs_div: 1st divider, 5..2046, must be even when >33 | |
72 | * @ls_div_bits: 2nd divider, as 2^x, range 0..5 | |
73 | * If ls_div_bits is non-zero, hs_div must be even | |
74 | */ | |
75 | struct clk_si544_muldiv { | |
76 | u32 fb_div_frac; | |
77 | u16 fb_div_int; | |
78 | u16 hs_div; | |
79 | u8 ls_div_bits; | |
80 | }; | |
81 | ||
82 | /* Enables or disables the output driver */ | |
83 | static int si544_enable_output(struct clk_si544 *data, bool enable) | |
84 | { | |
85 | return regmap_update_bits(data->regmap, SI544_REG_OE_STATE, | |
86 | SI544_OE_STATE_ODC_OE, enable ? SI544_OE_STATE_ODC_OE : 0); | |
87 | } | |
88 | ||
e8f127ca ML |
89 | static int si544_prepare(struct clk_hw *hw) |
90 | { | |
91 | struct clk_si544 *data = to_clk_si544(hw); | |
92 | ||
93 | return si544_enable_output(data, true); | |
94 | } | |
95 | ||
96 | static void si544_unprepare(struct clk_hw *hw) | |
97 | { | |
98 | struct clk_si544 *data = to_clk_si544(hw); | |
99 | ||
100 | si544_enable_output(data, false); | |
101 | } | |
102 | ||
103 | static int si544_is_prepared(struct clk_hw *hw) | |
104 | { | |
105 | struct clk_si544 *data = to_clk_si544(hw); | |
106 | unsigned int val; | |
107 | int err; | |
108 | ||
109 | err = regmap_read(data->regmap, SI544_REG_OE_STATE, &val); | |
110 | if (err < 0) | |
111 | return err; | |
112 | ||
113 | return !!(val & SI544_OE_STATE_ODC_OE); | |
114 | } | |
115 | ||
953cc3e8 ML |
116 | /* Retrieve clock multiplier and dividers from hardware */ |
117 | static int si544_get_muldiv(struct clk_si544 *data, | |
118 | struct clk_si544_muldiv *settings) | |
119 | { | |
120 | int err; | |
121 | u8 reg[6]; | |
122 | ||
123 | err = regmap_bulk_read(data->regmap, SI544_REG_HS_DIV, reg, 2); | |
124 | if (err) | |
125 | return err; | |
126 | ||
127 | settings->ls_div_bits = (reg[1] >> 4) & 0x07; | |
128 | settings->hs_div = (reg[1] & 0x07) << 8 | reg[0]; | |
129 | ||
130 | err = regmap_bulk_read(data->regmap, SI544_REG_FBDIV0, reg, 6); | |
131 | if (err) | |
132 | return err; | |
133 | ||
134 | settings->fb_div_int = reg[4] | (reg[5] & 0x07) << 8; | |
135 | settings->fb_div_frac = reg[0] | reg[1] << 8 | reg[2] << 16 | | |
136 | reg[3] << 24; | |
137 | return 0; | |
138 | } | |
139 | ||
140 | static int si544_set_muldiv(struct clk_si544 *data, | |
141 | struct clk_si544_muldiv *settings) | |
142 | { | |
143 | int err; | |
144 | u8 reg[6]; | |
145 | ||
146 | reg[0] = settings->hs_div; | |
147 | reg[1] = settings->hs_div >> 8 | settings->ls_div_bits << 4; | |
148 | ||
149 | err = regmap_bulk_write(data->regmap, SI544_REG_HS_DIV, reg, 2); | |
150 | if (err < 0) | |
151 | return err; | |
152 | ||
153 | reg[0] = settings->fb_div_frac; | |
154 | reg[1] = settings->fb_div_frac >> 8; | |
155 | reg[2] = settings->fb_div_frac >> 16; | |
156 | reg[3] = settings->fb_div_frac >> 24; | |
157 | reg[4] = settings->fb_div_int; | |
158 | reg[5] = settings->fb_div_int >> 8; | |
159 | ||
160 | /* | |
161 | * Writing to SI544_REG_FBDIV40 triggers the clock change, so that | |
162 | * must be written last | |
163 | */ | |
164 | return regmap_bulk_write(data->regmap, SI544_REG_FBDIV0, reg, 6); | |
165 | } | |
166 | ||
167 | static bool is_valid_frequency(const struct clk_si544 *data, | |
168 | unsigned long frequency) | |
169 | { | |
170 | unsigned long max_freq = 0; | |
171 | ||
172 | if (frequency < SI544_MIN_FREQ) | |
173 | return false; | |
174 | ||
175 | switch (data->speed_grade) { | |
176 | case si544a: | |
177 | max_freq = 1500000000; | |
178 | break; | |
179 | case si544b: | |
180 | max_freq = 800000000; | |
181 | break; | |
182 | case si544c: | |
183 | max_freq = 350000000; | |
184 | break; | |
185 | } | |
186 | ||
187 | return frequency <= max_freq; | |
188 | } | |
189 | ||
190 | /* Calculate divider settings for a given frequency */ | |
191 | static int si544_calc_muldiv(struct clk_si544_muldiv *settings, | |
192 | unsigned long frequency) | |
193 | { | |
194 | u64 vco; | |
195 | u32 ls_freq; | |
196 | u32 tmp; | |
197 | u8 res; | |
198 | ||
199 | /* Determine the minimum value of LS_DIV and resulting target freq. */ | |
200 | ls_freq = frequency; | |
201 | settings->ls_div_bits = 0; | |
202 | ||
203 | if (frequency >= MIN_HSDIV_FREQ) { | |
204 | settings->ls_div_bits = 0; | |
205 | } else { | |
206 | res = 1; | |
207 | tmp = 2 * HS_DIV_MAX; | |
208 | while (tmp <= (HS_DIV_MAX * 32)) { | |
209 | if (((u64)frequency * tmp) >= FVCO_MIN) | |
210 | break; | |
211 | ++res; | |
212 | tmp <<= 1; | |
213 | } | |
214 | settings->ls_div_bits = res; | |
215 | ls_freq = frequency << res; | |
216 | } | |
217 | ||
218 | /* Determine minimum HS_DIV by rounding up */ | |
219 | vco = FVCO_MIN + ls_freq - 1; | |
220 | do_div(vco, ls_freq); | |
221 | settings->hs_div = vco; | |
222 | ||
223 | /* round up to even number when required */ | |
224 | if ((settings->hs_div & 1) && | |
225 | (settings->hs_div > HS_DIV_MAX_ODD || settings->ls_div_bits)) | |
226 | ++settings->hs_div; | |
227 | ||
228 | /* Calculate VCO frequency (in 10..12GHz range) */ | |
229 | vco = (u64)ls_freq * settings->hs_div; | |
230 | ||
231 | /* Calculate the integer part of the feedback divider */ | |
232 | tmp = do_div(vco, FXO); | |
233 | settings->fb_div_int = vco; | |
234 | ||
235 | /* And the fractional bits using the remainder */ | |
236 | vco = (u64)tmp << 32; | |
4d3f36c5 | 237 | vco += FXO / 2; /* Round to nearest multiple */ |
953cc3e8 ML |
238 | do_div(vco, FXO); |
239 | settings->fb_div_frac = vco; | |
240 | ||
241 | return 0; | |
242 | } | |
243 | ||
244 | /* Calculate resulting frequency given the register settings */ | |
245 | static unsigned long si544_calc_rate(struct clk_si544_muldiv *settings) | |
246 | { | |
247 | u32 d = settings->hs_div * BIT(settings->ls_div_bits); | |
248 | u64 vco; | |
249 | ||
250 | /* Calculate VCO from the fractional part */ | |
251 | vco = (u64)settings->fb_div_frac * FXO; | |
252 | vco += (FXO / 2); | |
253 | vco >>= 32; | |
254 | ||
255 | /* Add the integer part of the VCO frequency */ | |
256 | vco += (u64)settings->fb_div_int * FXO; | |
257 | ||
258 | /* Apply divider to obtain the generated frequency */ | |
259 | do_div(vco, d); | |
260 | ||
261 | return vco; | |
262 | } | |
263 | ||
264 | static unsigned long si544_recalc_rate(struct clk_hw *hw, | |
265 | unsigned long parent_rate) | |
266 | { | |
267 | struct clk_si544 *data = to_clk_si544(hw); | |
268 | struct clk_si544_muldiv settings; | |
269 | int err; | |
270 | ||
271 | err = si544_get_muldiv(data, &settings); | |
272 | if (err) | |
273 | return 0; | |
274 | ||
275 | return si544_calc_rate(&settings); | |
276 | } | |
277 | ||
278 | static long si544_round_rate(struct clk_hw *hw, unsigned long rate, | |
279 | unsigned long *parent_rate) | |
280 | { | |
281 | struct clk_si544 *data = to_clk_si544(hw); | |
282 | struct clk_si544_muldiv settings; | |
283 | int err; | |
284 | ||
285 | if (!is_valid_frequency(data, rate)) | |
286 | return -EINVAL; | |
287 | ||
288 | err = si544_calc_muldiv(&settings, rate); | |
289 | if (err) | |
290 | return err; | |
291 | ||
292 | return si544_calc_rate(&settings); | |
293 | } | |
294 | ||
295 | /* | |
296 | * Update output frequency for "big" frequency changes | |
297 | */ | |
298 | static int si544_set_rate(struct clk_hw *hw, unsigned long rate, | |
299 | unsigned long parent_rate) | |
300 | { | |
301 | struct clk_si544 *data = to_clk_si544(hw); | |
302 | struct clk_si544_muldiv settings; | |
e8f127ca | 303 | unsigned int old_oe_state; |
953cc3e8 ML |
304 | int err; |
305 | ||
306 | if (!is_valid_frequency(data, rate)) | |
307 | return -EINVAL; | |
308 | ||
309 | err = si544_calc_muldiv(&settings, rate); | |
310 | if (err) | |
311 | return err; | |
312 | ||
e8f127ca ML |
313 | err = regmap_read(data->regmap, SI544_REG_OE_STATE, &old_oe_state); |
314 | if (err) | |
315 | return err; | |
316 | ||
953cc3e8 ML |
317 | si544_enable_output(data, false); |
318 | ||
319 | /* Allow FCAL for this frequency update */ | |
320 | err = regmap_write(data->regmap, SI544_REG_FCAL_OVR, 0); | |
321 | if (err < 0) | |
322 | return err; | |
323 | ||
324 | ||
325 | err = si544_set_muldiv(data, &settings); | |
326 | if (err < 0) | |
327 | return err; /* Undefined state now, best to leave disabled */ | |
328 | ||
329 | /* Trigger calibration */ | |
330 | err = regmap_write(data->regmap, SI544_REG_CONTROL, | |
331 | SI544_CONTROL_MS_ICAL2); | |
332 | if (err < 0) | |
333 | return err; | |
334 | ||
335 | /* Applying a new frequency can take up to 10ms */ | |
336 | usleep_range(10000, 12000); | |
337 | ||
e8f127ca ML |
338 | if (old_oe_state & SI544_OE_STATE_ODC_OE) |
339 | si544_enable_output(data, true); | |
953cc3e8 ML |
340 | |
341 | return err; | |
342 | } | |
343 | ||
344 | static const struct clk_ops si544_clk_ops = { | |
e8f127ca ML |
345 | .prepare = si544_prepare, |
346 | .unprepare = si544_unprepare, | |
347 | .is_prepared = si544_is_prepared, | |
953cc3e8 ML |
348 | .recalc_rate = si544_recalc_rate, |
349 | .round_rate = si544_round_rate, | |
350 | .set_rate = si544_set_rate, | |
351 | }; | |
352 | ||
353 | static bool si544_regmap_is_volatile(struct device *dev, unsigned int reg) | |
354 | { | |
355 | switch (reg) { | |
356 | case SI544_REG_CONTROL: | |
357 | case SI544_REG_FCAL_OVR: | |
358 | return true; | |
359 | default: | |
360 | return false; | |
361 | } | |
362 | } | |
363 | ||
364 | static const struct regmap_config si544_regmap_config = { | |
365 | .reg_bits = 8, | |
366 | .val_bits = 8, | |
367 | .cache_type = REGCACHE_RBTREE, | |
368 | .max_register = SI544_REG_PAGE_SELECT, | |
369 | .volatile_reg = si544_regmap_is_volatile, | |
370 | }; | |
371 | ||
372 | static int si544_probe(struct i2c_client *client, | |
373 | const struct i2c_device_id *id) | |
374 | { | |
375 | struct clk_si544 *data; | |
376 | struct clk_init_data init; | |
377 | int err; | |
378 | ||
379 | data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL); | |
380 | if (!data) | |
381 | return -ENOMEM; | |
382 | ||
383 | init.ops = &si544_clk_ops; | |
384 | init.flags = 0; | |
385 | init.num_parents = 0; | |
386 | data->hw.init = &init; | |
387 | data->i2c_client = client; | |
388 | data->speed_grade = id->driver_data; | |
389 | ||
390 | if (of_property_read_string(client->dev.of_node, "clock-output-names", | |
391 | &init.name)) | |
392 | init.name = client->dev.of_node->name; | |
393 | ||
394 | data->regmap = devm_regmap_init_i2c(client, &si544_regmap_config); | |
395 | if (IS_ERR(data->regmap)) | |
396 | return PTR_ERR(data->regmap); | |
397 | ||
398 | i2c_set_clientdata(client, data); | |
399 | ||
400 | /* Select page 0, just to be sure, there appear to be no more */ | |
401 | err = regmap_write(data->regmap, SI544_REG_PAGE_SELECT, 0); | |
402 | if (err < 0) | |
403 | return err; | |
404 | ||
405 | err = devm_clk_hw_register(&client->dev, &data->hw); | |
406 | if (err) { | |
407 | dev_err(&client->dev, "clock registration failed\n"); | |
408 | return err; | |
409 | } | |
410 | err = devm_of_clk_add_hw_provider(&client->dev, of_clk_hw_simple_get, | |
411 | &data->hw); | |
412 | if (err) { | |
413 | dev_err(&client->dev, "unable to add clk provider\n"); | |
414 | return err; | |
415 | } | |
416 | ||
417 | return 0; | |
418 | } | |
419 | ||
420 | static const struct i2c_device_id si544_id[] = { | |
421 | { "si544a", si544a }, | |
422 | { "si544b", si544b }, | |
423 | { "si544c", si544c }, | |
424 | { } | |
425 | }; | |
426 | MODULE_DEVICE_TABLE(i2c, si544_id); | |
427 | ||
428 | static const struct of_device_id clk_si544_of_match[] = { | |
429 | { .compatible = "silabs,si544a" }, | |
430 | { .compatible = "silabs,si544b" }, | |
431 | { .compatible = "silabs,si544c" }, | |
432 | { }, | |
433 | }; | |
434 | MODULE_DEVICE_TABLE(of, clk_si544_of_match); | |
435 | ||
436 | static struct i2c_driver si544_driver = { | |
437 | .driver = { | |
438 | .name = "si544", | |
439 | .of_match_table = clk_si544_of_match, | |
440 | }, | |
441 | .probe = si544_probe, | |
442 | .id_table = si544_id, | |
443 | }; | |
444 | module_i2c_driver(si544_driver); | |
445 | ||
446 | MODULE_AUTHOR("Mike Looijmans <[email protected]>"); | |
447 | MODULE_DESCRIPTION("Si544 driver"); | |
448 | MODULE_LICENSE("GPL"); |