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 <mike.looijmans@topic.nl> |
6 | */ |
7 | |
8 | #include <linux/clk-provider.h> |
9 | #include <linux/delay.h> |
10 | #include <linux/math64.h> |
11 | #include <linux/module.h> |
12 | #include <linux/i2c.h> |
13 | #include <linux/regmap.h> |
14 | #include <linux/slab.h> |
15 | |
16 | /* I2C registers (decimal as in datasheet) */ |
17 | #define SI544_REG_CONTROL 7 |
18 | #define SI544_REG_OE_STATE 17 |
19 | #define SI544_REG_HS_DIV 23 |
20 | #define SI544_REG_LS_HS_DIV 24 |
21 | #define SI544_REG_FBDIV0 26 |
22 | #define SI544_REG_FBDIV8 27 |
23 | #define SI544_REG_FBDIV16 28 |
24 | #define SI544_REG_FBDIV24 29 |
25 | #define SI544_REG_FBDIV32 30 |
26 | #define SI544_REG_FBDIV40 31 |
27 | #define SI544_REG_FCAL_OVR 69 |
28 | #define SI544_REG_ADPLL_DELTA_M0 231 |
29 | #define SI544_REG_ADPLL_DELTA_M8 232 |
30 | #define SI544_REG_ADPLL_DELTA_M16 233 |
31 | #define SI544_REG_PAGE_SELECT 255 |
32 | |
33 | /* Register values */ |
34 | #define SI544_CONTROL_RESET BIT(7) |
35 | #define SI544_CONTROL_MS_ICAL2 BIT(3) |
36 | |
37 | #define SI544_OE_STATE_ODC_OE BIT(0) |
38 | |
39 | /* Max freq depends on speed grade */ |
40 | #define SI544_MIN_FREQ 200000U |
41 | |
42 | /* Si544 Internal oscilator runs at 55.05 MHz */ |
43 | #define FXO 55050000U |
44 | |
45 | /* VCO range is 10.8 .. 12.1 GHz, max depends on speed grade */ |
46 | #define FVCO_MIN 10800000000ULL |
47 | |
48 | #define HS_DIV_MAX 2046 |
49 | #define HS_DIV_MAX_ODD 33 |
50 | |
51 | /* Lowest frequency synthesizeable using only the HS divider */ |
52 | #define MIN_HSDIV_FREQ (FVCO_MIN / HS_DIV_MAX) |
53 | |
54 | /* Range and interpretation of the adjustment value */ |
55 | #define DELTA_M_MAX 8161512 |
56 | #define DELTA_M_FRAC_NUM 19 |
57 | #define DELTA_M_FRAC_DEN 20000 |
58 | |
59 | struct clk_si544 { |
60 | struct clk_hw hw; |
61 | struct regmap *regmap; |
62 | struct i2c_client *i2c_client; |
63 | unsigned long max_freq; |
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 | * @delta_m: Frequency shift for small -950..+950 ppm changes, 24 bit |
75 | */ |
76 | struct clk_si544_muldiv { |
77 | u32 fb_div_frac; |
78 | u16 fb_div_int; |
79 | u16 hs_div; |
80 | u8 ls_div_bits; |
81 | s32 delta_m; |
82 | }; |
83 | |
84 | /* Enables or disables the output driver */ |
85 | static int si544_enable_output(struct clk_si544 *data, bool enable) |
86 | { |
87 | return regmap_update_bits(map: data->regmap, SI544_REG_OE_STATE, |
88 | SI544_OE_STATE_ODC_OE, val: enable ? SI544_OE_STATE_ODC_OE : 0); |
89 | } |
90 | |
91 | static int si544_prepare(struct clk_hw *hw) |
92 | { |
93 | struct clk_si544 *data = to_clk_si544(hw); |
94 | |
95 | return si544_enable_output(data, enable: true); |
96 | } |
97 | |
98 | static void si544_unprepare(struct clk_hw *hw) |
99 | { |
100 | struct clk_si544 *data = to_clk_si544(hw); |
101 | |
102 | si544_enable_output(data, enable: false); |
103 | } |
104 | |
105 | static int si544_is_prepared(struct clk_hw *hw) |
106 | { |
107 | struct clk_si544 *data = to_clk_si544(hw); |
108 | unsigned int val; |
109 | int err; |
110 | |
111 | err = regmap_read(map: data->regmap, SI544_REG_OE_STATE, val: &val); |
112 | if (err < 0) |
113 | return err; |
114 | |
115 | return !!(val & SI544_OE_STATE_ODC_OE); |
116 | } |
117 | |
118 | /* Retrieve clock multiplier and dividers from hardware */ |
119 | static int si544_get_muldiv(struct clk_si544 *data, |
120 | struct clk_si544_muldiv *settings) |
121 | { |
122 | int err; |
123 | u8 reg[6]; |
124 | |
125 | err = regmap_bulk_read(map: data->regmap, SI544_REG_HS_DIV, val: reg, val_count: 2); |
126 | if (err) |
127 | return err; |
128 | |
129 | settings->ls_div_bits = (reg[1] >> 4) & 0x07; |
130 | settings->hs_div = (reg[1] & 0x07) << 8 | reg[0]; |
131 | |
132 | err = regmap_bulk_read(map: data->regmap, SI544_REG_FBDIV0, val: reg, val_count: 6); |
133 | if (err) |
134 | return err; |
135 | |
136 | settings->fb_div_int = reg[4] | (reg[5] & 0x07) << 8; |
137 | settings->fb_div_frac = reg[0] | reg[1] << 8 | reg[2] << 16 | |
138 | reg[3] << 24; |
139 | |
140 | err = regmap_bulk_read(map: data->regmap, SI544_REG_ADPLL_DELTA_M0, val: reg, val_count: 3); |
141 | if (err) |
142 | return err; |
143 | |
144 | /* Interpret as 24-bit signed number */ |
145 | settings->delta_m = reg[0] << 8 | reg[1] << 16 | reg[2] << 24; |
146 | settings->delta_m >>= 8; |
147 | |
148 | return 0; |
149 | } |
150 | |
151 | static int si544_set_delta_m(struct clk_si544 *data, s32 delta_m) |
152 | { |
153 | u8 reg[3]; |
154 | |
155 | reg[0] = delta_m; |
156 | reg[1] = delta_m >> 8; |
157 | reg[2] = delta_m >> 16; |
158 | |
159 | return regmap_bulk_write(map: data->regmap, SI544_REG_ADPLL_DELTA_M0, |
160 | val: reg, val_count: 3); |
161 | } |
162 | |
163 | static int si544_set_muldiv(struct clk_si544 *data, |
164 | struct clk_si544_muldiv *settings) |
165 | { |
166 | int err; |
167 | u8 reg[6]; |
168 | |
169 | reg[0] = settings->hs_div; |
170 | reg[1] = settings->hs_div >> 8 | settings->ls_div_bits << 4; |
171 | |
172 | err = regmap_bulk_write(map: data->regmap, SI544_REG_HS_DIV, val: reg, val_count: 2); |
173 | if (err < 0) |
174 | return err; |
175 | |
176 | reg[0] = settings->fb_div_frac; |
177 | reg[1] = settings->fb_div_frac >> 8; |
178 | reg[2] = settings->fb_div_frac >> 16; |
179 | reg[3] = settings->fb_div_frac >> 24; |
180 | reg[4] = settings->fb_div_int; |
181 | reg[5] = settings->fb_div_int >> 8; |
182 | |
183 | /* |
184 | * Writing to SI544_REG_FBDIV40 triggers the clock change, so that |
185 | * must be written last |
186 | */ |
187 | return regmap_bulk_write(map: data->regmap, SI544_REG_FBDIV0, val: reg, val_count: 6); |
188 | } |
189 | |
190 | static bool is_valid_frequency(const struct clk_si544 *data, |
191 | unsigned long frequency) |
192 | { |
193 | if (frequency < SI544_MIN_FREQ) |
194 | return false; |
195 | |
196 | return frequency <= data->max_freq; |
197 | } |
198 | |
199 | /* Calculate divider settings for a given frequency */ |
200 | static int si544_calc_muldiv(struct clk_si544_muldiv *settings, |
201 | unsigned long frequency) |
202 | { |
203 | u64 vco; |
204 | u32 ls_freq; |
205 | u32 tmp; |
206 | u8 res; |
207 | |
208 | /* Determine the minimum value of LS_DIV and resulting target freq. */ |
209 | ls_freq = frequency; |
210 | settings->ls_div_bits = 0; |
211 | |
212 | if (frequency >= MIN_HSDIV_FREQ) { |
213 | settings->ls_div_bits = 0; |
214 | } else { |
215 | res = 1; |
216 | tmp = 2 * HS_DIV_MAX; |
217 | while (tmp <= (HS_DIV_MAX * 32)) { |
218 | if (((u64)frequency * tmp) >= FVCO_MIN) |
219 | break; |
220 | ++res; |
221 | tmp <<= 1; |
222 | } |
223 | settings->ls_div_bits = res; |
224 | ls_freq = frequency << res; |
225 | } |
226 | |
227 | /* Determine minimum HS_DIV by rounding up */ |
228 | vco = FVCO_MIN + ls_freq - 1; |
229 | do_div(vco, ls_freq); |
230 | settings->hs_div = vco; |
231 | |
232 | /* round up to even number when required */ |
233 | if ((settings->hs_div & 1) && |
234 | (settings->hs_div > HS_DIV_MAX_ODD || settings->ls_div_bits)) |
235 | ++settings->hs_div; |
236 | |
237 | /* Calculate VCO frequency (in 10..12GHz range) */ |
238 | vco = (u64)ls_freq * settings->hs_div; |
239 | |
240 | /* Calculate the integer part of the feedback divider */ |
241 | tmp = do_div(vco, FXO); |
242 | settings->fb_div_int = vco; |
243 | |
244 | /* And the fractional bits using the remainder */ |
245 | vco = (u64)tmp << 32; |
246 | vco += FXO / 2; /* Round to nearest multiple */ |
247 | do_div(vco, FXO); |
248 | settings->fb_div_frac = vco; |
249 | |
250 | /* Reset the frequency adjustment */ |
251 | settings->delta_m = 0; |
252 | |
253 | return 0; |
254 | } |
255 | |
256 | /* Calculate resulting frequency given the register settings */ |
257 | static unsigned long si544_calc_center_rate( |
258 | const struct clk_si544_muldiv *settings) |
259 | { |
260 | u32 d = settings->hs_div * BIT(settings->ls_div_bits); |
261 | u64 vco; |
262 | |
263 | /* Calculate VCO from the fractional part */ |
264 | vco = (u64)settings->fb_div_frac * FXO; |
265 | vco += (FXO / 2); |
266 | vco >>= 32; |
267 | |
268 | /* Add the integer part of the VCO frequency */ |
269 | vco += (u64)settings->fb_div_int * FXO; |
270 | |
271 | /* Apply divider to obtain the generated frequency */ |
272 | do_div(vco, d); |
273 | |
274 | return vco; |
275 | } |
276 | |
277 | static unsigned long si544_calc_rate(const struct clk_si544_muldiv *settings) |
278 | { |
279 | unsigned long rate = si544_calc_center_rate(settings); |
280 | s64 delta = (s64)rate * (DELTA_M_FRAC_NUM * settings->delta_m); |
281 | |
282 | /* |
283 | * The clock adjustment is much smaller than 1 Hz, round to the |
284 | * nearest multiple. Apparently div64_s64 rounds towards zero, hence |
285 | * check the sign and adjust into the proper direction. |
286 | */ |
287 | if (settings->delta_m < 0) |
288 | delta -= ((s64)DELTA_M_MAX * DELTA_M_FRAC_DEN) / 2; |
289 | else |
290 | delta += ((s64)DELTA_M_MAX * DELTA_M_FRAC_DEN) / 2; |
291 | delta = div64_s64(dividend: delta, divisor: ((s64)DELTA_M_MAX * DELTA_M_FRAC_DEN)); |
292 | |
293 | return rate + delta; |
294 | } |
295 | |
296 | static unsigned long si544_recalc_rate(struct clk_hw *hw, |
297 | unsigned long parent_rate) |
298 | { |
299 | struct clk_si544 *data = to_clk_si544(hw); |
300 | struct clk_si544_muldiv settings; |
301 | int err; |
302 | |
303 | err = si544_get_muldiv(data, settings: &settings); |
304 | if (err) |
305 | return 0; |
306 | |
307 | return si544_calc_rate(settings: &settings); |
308 | } |
309 | |
310 | static long si544_round_rate(struct clk_hw *hw, unsigned long rate, |
311 | unsigned long *parent_rate) |
312 | { |
313 | struct clk_si544 *data = to_clk_si544(hw); |
314 | |
315 | if (!is_valid_frequency(data, frequency: rate)) |
316 | return -EINVAL; |
317 | |
318 | /* The accuracy is less than 1 Hz, so any rate is possible */ |
319 | return rate; |
320 | } |
321 | |
322 | /* Calculates the maximum "small" change, 950 * rate / 1000000 */ |
323 | static unsigned long si544_max_delta(unsigned long rate) |
324 | { |
325 | u64 num = rate; |
326 | |
327 | num *= DELTA_M_FRAC_NUM; |
328 | do_div(num, DELTA_M_FRAC_DEN); |
329 | |
330 | return num; |
331 | } |
332 | |
333 | static s32 si544_calc_delta(s32 delta, s32 max_delta) |
334 | { |
335 | s64 n = (s64)delta * DELTA_M_MAX; |
336 | |
337 | return div_s64(dividend: n, divisor: max_delta); |
338 | } |
339 | |
340 | static int si544_set_rate(struct clk_hw *hw, unsigned long rate, |
341 | unsigned long parent_rate) |
342 | { |
343 | struct clk_si544 *data = to_clk_si544(hw); |
344 | struct clk_si544_muldiv settings; |
345 | unsigned long center; |
346 | long max_delta; |
347 | long delta; |
348 | unsigned int old_oe_state; |
349 | int err; |
350 | |
351 | if (!is_valid_frequency(data, frequency: rate)) |
352 | return -EINVAL; |
353 | |
354 | /* Try using the frequency adjustment feature for a <= 950ppm change */ |
355 | err = si544_get_muldiv(data, settings: &settings); |
356 | if (err) |
357 | return err; |
358 | |
359 | center = si544_calc_center_rate(settings: &settings); |
360 | max_delta = si544_max_delta(rate: center); |
361 | delta = rate - center; |
362 | |
363 | if (abs(delta) <= max_delta) |
364 | return si544_set_delta_m(data, |
365 | delta_m: si544_calc_delta(delta, max_delta)); |
366 | |
367 | /* Too big for the delta adjustment, need to reprogram */ |
368 | err = si544_calc_muldiv(settings: &settings, frequency: rate); |
369 | if (err) |
370 | return err; |
371 | |
372 | err = regmap_read(map: data->regmap, SI544_REG_OE_STATE, val: &old_oe_state); |
373 | if (err) |
374 | return err; |
375 | |
376 | si544_enable_output(data, enable: false); |
377 | |
378 | /* Allow FCAL for this frequency update */ |
379 | err = regmap_write(map: data->regmap, SI544_REG_FCAL_OVR, val: 0); |
380 | if (err < 0) |
381 | return err; |
382 | |
383 | err = si544_set_delta_m(data, delta_m: settings.delta_m); |
384 | if (err < 0) |
385 | return err; |
386 | |
387 | err = si544_set_muldiv(data, settings: &settings); |
388 | if (err < 0) |
389 | return err; /* Undefined state now, best to leave disabled */ |
390 | |
391 | /* Trigger calibration */ |
392 | err = regmap_write(map: data->regmap, SI544_REG_CONTROL, |
393 | SI544_CONTROL_MS_ICAL2); |
394 | if (err < 0) |
395 | return err; |
396 | |
397 | /* Applying a new frequency can take up to 10ms */ |
398 | usleep_range(min: 10000, max: 12000); |
399 | |
400 | if (old_oe_state & SI544_OE_STATE_ODC_OE) |
401 | si544_enable_output(data, enable: true); |
402 | |
403 | return err; |
404 | } |
405 | |
406 | static const struct clk_ops si544_clk_ops = { |
407 | .prepare = si544_prepare, |
408 | .unprepare = si544_unprepare, |
409 | .is_prepared = si544_is_prepared, |
410 | .recalc_rate = si544_recalc_rate, |
411 | .round_rate = si544_round_rate, |
412 | .set_rate = si544_set_rate, |
413 | }; |
414 | |
415 | static bool si544_regmap_is_volatile(struct device *dev, unsigned int reg) |
416 | { |
417 | switch (reg) { |
418 | case SI544_REG_CONTROL: |
419 | case SI544_REG_FCAL_OVR: |
420 | return true; |
421 | default: |
422 | return false; |
423 | } |
424 | } |
425 | |
426 | static const struct regmap_config si544_regmap_config = { |
427 | .reg_bits = 8, |
428 | .val_bits = 8, |
429 | .cache_type = REGCACHE_MAPLE, |
430 | .max_register = SI544_REG_PAGE_SELECT, |
431 | .volatile_reg = si544_regmap_is_volatile, |
432 | }; |
433 | |
434 | static int si544_probe(struct i2c_client *client) |
435 | { |
436 | struct clk_si544 *data; |
437 | struct clk_init_data init; |
438 | int err; |
439 | |
440 | data = devm_kzalloc(dev: &client->dev, size: sizeof(*data), GFP_KERNEL); |
441 | if (!data) |
442 | return -ENOMEM; |
443 | |
444 | init.ops = &si544_clk_ops; |
445 | init.flags = 0; |
446 | init.num_parents = 0; |
447 | data->hw.init = &init; |
448 | data->i2c_client = client; |
449 | data->max_freq = (uintptr_t)i2c_get_match_data(client); |
450 | |
451 | if (of_property_read_string(np: client->dev.of_node, propname: "clock-output-names" , |
452 | out_string: &init.name)) |
453 | init.name = client->dev.of_node->name; |
454 | |
455 | data->regmap = devm_regmap_init_i2c(client, &si544_regmap_config); |
456 | if (IS_ERR(ptr: data->regmap)) |
457 | return PTR_ERR(ptr: data->regmap); |
458 | |
459 | i2c_set_clientdata(client, data); |
460 | |
461 | /* Select page 0, just to be sure, there appear to be no more */ |
462 | err = regmap_write(map: data->regmap, SI544_REG_PAGE_SELECT, val: 0); |
463 | if (err < 0) |
464 | return err; |
465 | |
466 | err = devm_clk_hw_register(dev: &client->dev, hw: &data->hw); |
467 | if (err) { |
468 | dev_err(&client->dev, "clock registration failed\n" ); |
469 | return err; |
470 | } |
471 | err = devm_of_clk_add_hw_provider(dev: &client->dev, get: of_clk_hw_simple_get, |
472 | data: &data->hw); |
473 | if (err) { |
474 | dev_err(&client->dev, "unable to add clk provider\n" ); |
475 | return err; |
476 | } |
477 | |
478 | return 0; |
479 | } |
480 | |
481 | static const struct i2c_device_id si544_id[] = { |
482 | { "si544a" , 1500000000 }, |
483 | { "si544b" , 800000000 }, |
484 | { "si544c" , 350000000 }, |
485 | { } |
486 | }; |
487 | MODULE_DEVICE_TABLE(i2c, si544_id); |
488 | |
489 | static const struct of_device_id clk_si544_of_match[] = { |
490 | { .compatible = "silabs,si544a" , .data = (void *)1500000000 }, |
491 | { .compatible = "silabs,si544b" , .data = (void *)800000000 }, |
492 | { .compatible = "silabs,si544c" , .data = (void *)350000000 }, |
493 | { } |
494 | }; |
495 | MODULE_DEVICE_TABLE(of, clk_si544_of_match); |
496 | |
497 | static struct i2c_driver si544_driver = { |
498 | .driver = { |
499 | .name = "si544" , |
500 | .of_match_table = clk_si544_of_match, |
501 | }, |
502 | .probe = si544_probe, |
503 | .id_table = si544_id, |
504 | }; |
505 | module_i2c_driver(si544_driver); |
506 | |
507 | MODULE_AUTHOR("Mike Looijmans <mike.looijmans@topic.nl>" ); |
508 | MODULE_DESCRIPTION("Si544 driver" ); |
509 | MODULE_LICENSE("GPL" ); |
510 | |