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 <mike.looijmans@topic.nl> | |
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 | ||
89 | /* Retrieve clock multiplier and dividers from hardware */ | |
90 | static int si544_get_muldiv(struct clk_si544 *data, | |
91 | struct clk_si544_muldiv *settings) | |
92 | { | |
93 | int err; | |
94 | u8 reg[6]; | |
95 | ||
96 | err = regmap_bulk_read(data->regmap, SI544_REG_HS_DIV, reg, 2); | |
97 | if (err) | |
98 | return err; | |
99 | ||
100 | settings->ls_div_bits = (reg[1] >> 4) & 0x07; | |
101 | settings->hs_div = (reg[1] & 0x07) << 8 | reg[0]; | |
102 | ||
103 | err = regmap_bulk_read(data->regmap, SI544_REG_FBDIV0, reg, 6); | |
104 | if (err) | |
105 | return err; | |
106 | ||
107 | settings->fb_div_int = reg[4] | (reg[5] & 0x07) << 8; | |
108 | settings->fb_div_frac = reg[0] | reg[1] << 8 | reg[2] << 16 | | |
109 | reg[3] << 24; | |
110 | return 0; | |
111 | } | |
112 | ||
113 | static int si544_set_muldiv(struct clk_si544 *data, | |
114 | struct clk_si544_muldiv *settings) | |
115 | { | |
116 | int err; | |
117 | u8 reg[6]; | |
118 | ||
119 | reg[0] = settings->hs_div; | |
120 | reg[1] = settings->hs_div >> 8 | settings->ls_div_bits << 4; | |
121 | ||
122 | err = regmap_bulk_write(data->regmap, SI544_REG_HS_DIV, reg, 2); | |
123 | if (err < 0) | |
124 | return err; | |
125 | ||
126 | reg[0] = settings->fb_div_frac; | |
127 | reg[1] = settings->fb_div_frac >> 8; | |
128 | reg[2] = settings->fb_div_frac >> 16; | |
129 | reg[3] = settings->fb_div_frac >> 24; | |
130 | reg[4] = settings->fb_div_int; | |
131 | reg[5] = settings->fb_div_int >> 8; | |
132 | ||
133 | /* | |
134 | * Writing to SI544_REG_FBDIV40 triggers the clock change, so that | |
135 | * must be written last | |
136 | */ | |
137 | return regmap_bulk_write(data->regmap, SI544_REG_FBDIV0, reg, 6); | |
138 | } | |
139 | ||
140 | static bool is_valid_frequency(const struct clk_si544 *data, | |
141 | unsigned long frequency) | |
142 | { | |
143 | unsigned long max_freq = 0; | |
144 | ||
145 | if (frequency < SI544_MIN_FREQ) | |
146 | return false; | |
147 | ||
148 | switch (data->speed_grade) { | |
149 | case si544a: | |
150 | max_freq = 1500000000; | |
151 | break; | |
152 | case si544b: | |
153 | max_freq = 800000000; | |
154 | break; | |
155 | case si544c: | |
156 | max_freq = 350000000; | |
157 | break; | |
158 | } | |
159 | ||
160 | return frequency <= max_freq; | |
161 | } | |
162 | ||
163 | /* Calculate divider settings for a given frequency */ | |
164 | static int si544_calc_muldiv(struct clk_si544_muldiv *settings, | |
165 | unsigned long frequency) | |
166 | { | |
167 | u64 vco; | |
168 | u32 ls_freq; | |
169 | u32 tmp; | |
170 | u8 res; | |
171 | ||
172 | /* Determine the minimum value of LS_DIV and resulting target freq. */ | |
173 | ls_freq = frequency; | |
174 | settings->ls_div_bits = 0; | |
175 | ||
176 | if (frequency >= MIN_HSDIV_FREQ) { | |
177 | settings->ls_div_bits = 0; | |
178 | } else { | |
179 | res = 1; | |
180 | tmp = 2 * HS_DIV_MAX; | |
181 | while (tmp <= (HS_DIV_MAX * 32)) { | |
182 | if (((u64)frequency * tmp) >= FVCO_MIN) | |
183 | break; | |
184 | ++res; | |
185 | tmp <<= 1; | |
186 | } | |
187 | settings->ls_div_bits = res; | |
188 | ls_freq = frequency << res; | |
189 | } | |
190 | ||
191 | /* Determine minimum HS_DIV by rounding up */ | |
192 | vco = FVCO_MIN + ls_freq - 1; | |
193 | do_div(vco, ls_freq); | |
194 | settings->hs_div = vco; | |
195 | ||
196 | /* round up to even number when required */ | |
197 | if ((settings->hs_div & 1) && | |
198 | (settings->hs_div > HS_DIV_MAX_ODD || settings->ls_div_bits)) | |
199 | ++settings->hs_div; | |
200 | ||
201 | /* Calculate VCO frequency (in 10..12GHz range) */ | |
202 | vco = (u64)ls_freq * settings->hs_div; | |
203 | ||
204 | /* Calculate the integer part of the feedback divider */ | |
205 | tmp = do_div(vco, FXO); | |
206 | settings->fb_div_int = vco; | |
207 | ||
208 | /* And the fractional bits using the remainder */ | |
209 | vco = (u64)tmp << 32; | |
4d3f36c5 | 210 | vco += FXO / 2; /* Round to nearest multiple */ |
953cc3e8 ML |
211 | do_div(vco, FXO); |
212 | settings->fb_div_frac = vco; | |
213 | ||
214 | return 0; | |
215 | } | |
216 | ||
217 | /* Calculate resulting frequency given the register settings */ | |
218 | static unsigned long si544_calc_rate(struct clk_si544_muldiv *settings) | |
219 | { | |
220 | u32 d = settings->hs_div * BIT(settings->ls_div_bits); | |
221 | u64 vco; | |
222 | ||
223 | /* Calculate VCO from the fractional part */ | |
224 | vco = (u64)settings->fb_div_frac * FXO; | |
225 | vco += (FXO / 2); | |
226 | vco >>= 32; | |
227 | ||
228 | /* Add the integer part of the VCO frequency */ | |
229 | vco += (u64)settings->fb_div_int * FXO; | |
230 | ||
231 | /* Apply divider to obtain the generated frequency */ | |
232 | do_div(vco, d); | |
233 | ||
234 | return vco; | |
235 | } | |
236 | ||
237 | static unsigned long si544_recalc_rate(struct clk_hw *hw, | |
238 | unsigned long parent_rate) | |
239 | { | |
240 | struct clk_si544 *data = to_clk_si544(hw); | |
241 | struct clk_si544_muldiv settings; | |
242 | int err; | |
243 | ||
244 | err = si544_get_muldiv(data, &settings); | |
245 | if (err) | |
246 | return 0; | |
247 | ||
248 | return si544_calc_rate(&settings); | |
249 | } | |
250 | ||
251 | static long si544_round_rate(struct clk_hw *hw, unsigned long rate, | |
252 | unsigned long *parent_rate) | |
253 | { | |
254 | struct clk_si544 *data = to_clk_si544(hw); | |
255 | struct clk_si544_muldiv settings; | |
256 | int err; | |
257 | ||
258 | if (!is_valid_frequency(data, rate)) | |
259 | return -EINVAL; | |
260 | ||
261 | err = si544_calc_muldiv(&settings, rate); | |
262 | if (err) | |
263 | return err; | |
264 | ||
265 | return si544_calc_rate(&settings); | |
266 | } | |
267 | ||
268 | /* | |
269 | * Update output frequency for "big" frequency changes | |
270 | */ | |
271 | static int si544_set_rate(struct clk_hw *hw, unsigned long rate, | |
272 | unsigned long parent_rate) | |
273 | { | |
274 | struct clk_si544 *data = to_clk_si544(hw); | |
275 | struct clk_si544_muldiv settings; | |
276 | int err; | |
277 | ||
278 | if (!is_valid_frequency(data, rate)) | |
279 | return -EINVAL; | |
280 | ||
281 | err = si544_calc_muldiv(&settings, rate); | |
282 | if (err) | |
283 | return err; | |
284 | ||
285 | si544_enable_output(data, false); | |
286 | ||
287 | /* Allow FCAL for this frequency update */ | |
288 | err = regmap_write(data->regmap, SI544_REG_FCAL_OVR, 0); | |
289 | if (err < 0) | |
290 | return err; | |
291 | ||
292 | ||
293 | err = si544_set_muldiv(data, &settings); | |
294 | if (err < 0) | |
295 | return err; /* Undefined state now, best to leave disabled */ | |
296 | ||
297 | /* Trigger calibration */ | |
298 | err = regmap_write(data->regmap, SI544_REG_CONTROL, | |
299 | SI544_CONTROL_MS_ICAL2); | |
300 | if (err < 0) | |
301 | return err; | |
302 | ||
303 | /* Applying a new frequency can take up to 10ms */ | |
304 | usleep_range(10000, 12000); | |
305 | ||
306 | si544_enable_output(data, true); | |
307 | ||
308 | return err; | |
309 | } | |
310 | ||
311 | static const struct clk_ops si544_clk_ops = { | |
312 | .recalc_rate = si544_recalc_rate, | |
313 | .round_rate = si544_round_rate, | |
314 | .set_rate = si544_set_rate, | |
315 | }; | |
316 | ||
317 | static bool si544_regmap_is_volatile(struct device *dev, unsigned int reg) | |
318 | { | |
319 | switch (reg) { | |
320 | case SI544_REG_CONTROL: | |
321 | case SI544_REG_FCAL_OVR: | |
322 | return true; | |
323 | default: | |
324 | return false; | |
325 | } | |
326 | } | |
327 | ||
328 | static const struct regmap_config si544_regmap_config = { | |
329 | .reg_bits = 8, | |
330 | .val_bits = 8, | |
331 | .cache_type = REGCACHE_RBTREE, | |
332 | .max_register = SI544_REG_PAGE_SELECT, | |
333 | .volatile_reg = si544_regmap_is_volatile, | |
334 | }; | |
335 | ||
336 | static int si544_probe(struct i2c_client *client, | |
337 | const struct i2c_device_id *id) | |
338 | { | |
339 | struct clk_si544 *data; | |
340 | struct clk_init_data init; | |
341 | int err; | |
342 | ||
343 | data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL); | |
344 | if (!data) | |
345 | return -ENOMEM; | |
346 | ||
347 | init.ops = &si544_clk_ops; | |
348 | init.flags = 0; | |
349 | init.num_parents = 0; | |
350 | data->hw.init = &init; | |
351 | data->i2c_client = client; | |
352 | data->speed_grade = id->driver_data; | |
353 | ||
354 | if (of_property_read_string(client->dev.of_node, "clock-output-names", | |
355 | &init.name)) | |
356 | init.name = client->dev.of_node->name; | |
357 | ||
358 | data->regmap = devm_regmap_init_i2c(client, &si544_regmap_config); | |
359 | if (IS_ERR(data->regmap)) | |
360 | return PTR_ERR(data->regmap); | |
361 | ||
362 | i2c_set_clientdata(client, data); | |
363 | ||
364 | /* Select page 0, just to be sure, there appear to be no more */ | |
365 | err = regmap_write(data->regmap, SI544_REG_PAGE_SELECT, 0); | |
366 | if (err < 0) | |
367 | return err; | |
368 | ||
369 | err = devm_clk_hw_register(&client->dev, &data->hw); | |
370 | if (err) { | |
371 | dev_err(&client->dev, "clock registration failed\n"); | |
372 | return err; | |
373 | } | |
374 | err = devm_of_clk_add_hw_provider(&client->dev, of_clk_hw_simple_get, | |
375 | &data->hw); | |
376 | if (err) { | |
377 | dev_err(&client->dev, "unable to add clk provider\n"); | |
378 | return err; | |
379 | } | |
380 | ||
381 | return 0; | |
382 | } | |
383 | ||
384 | static const struct i2c_device_id si544_id[] = { | |
385 | { "si544a", si544a }, | |
386 | { "si544b", si544b }, | |
387 | { "si544c", si544c }, | |
388 | { } | |
389 | }; | |
390 | MODULE_DEVICE_TABLE(i2c, si544_id); | |
391 | ||
392 | static const struct of_device_id clk_si544_of_match[] = { | |
393 | { .compatible = "silabs,si544a" }, | |
394 | { .compatible = "silabs,si544b" }, | |
395 | { .compatible = "silabs,si544c" }, | |
396 | { }, | |
397 | }; | |
398 | MODULE_DEVICE_TABLE(of, clk_si544_of_match); | |
399 | ||
400 | static struct i2c_driver si544_driver = { | |
401 | .driver = { | |
402 | .name = "si544", | |
403 | .of_match_table = clk_si544_of_match, | |
404 | }, | |
405 | .probe = si544_probe, | |
406 | .id_table = si544_id, | |
407 | }; | |
408 | module_i2c_driver(si544_driver); | |
409 | ||
410 | MODULE_AUTHOR("Mike Looijmans <mike.looijmans@topic.nl>"); | |
411 | MODULE_DESCRIPTION("Si544 driver"); | |
412 | MODULE_LICENSE("GPL"); |