aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMike Looijmans <mike.looijmans@topic.nl>2015-10-02 03:15:29 -0400
committerStephen Boyd <sboyd@codeaurora.org>2015-10-09 02:52:55 -0400
commit8ce20e6617fedb195b7b243fc74cdef1cf1684f6 (patch)
treeb568b83b40a7af594de148ae9d28f9b719b82729
parent6082d88e1d0001fd466c3350b7b256e96f8433ab (diff)
Add driver for the si514 clock generator chip
This patch adds the driver and devicetree documentation for the Silicon Labs SI514 clock generator chip. This is an I2C controlled oscillator capable of generating clock signals ranging from 100kHz to 250MHz. Signed-off-by: Mike Looijmans <mike.looijmans@topic.nl> [sboyd@codeaurora.org: Drop clk.h include, remove some casts] Signed-off-by: Stephen Boyd <sboyd@codeaurora.org>
-rw-r--r--Documentation/devicetree/bindings/clock/silabs,si514.txt24
-rw-r--r--drivers/clk/Kconfig10
-rw-r--r--drivers/clk/Makefile1
-rw-r--r--drivers/clk/clk-si514.c379
4 files changed, 414 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/clock/silabs,si514.txt b/Documentation/devicetree/bindings/clock/silabs,si514.txt
new file mode 100644
index 000000000000..ea1a9dbc63b6
--- /dev/null
+++ b/Documentation/devicetree/bindings/clock/silabs,si514.txt
@@ -0,0 +1,24 @@
1Binding for Silicon Labs 514 programmable I2C clock generator.
2
3Reference
4This binding uses the common clock binding[1]. Details about the device can be
5found in the datasheet[2].
6
7[1] Documentation/devicetree/bindings/clock/clock-bindings.txt
8[2] Si514 datasheet
9 http://www.silabs.com/Support%20Documents/TechnicalDocs/si514.pdf
10
11Required properties:
12 - compatible: Shall be "silabs,si514"
13 - reg: I2C device address.
14 - #clock-cells: From common clock bindings: Shall be 0.
15
16Optional properties:
17 - clock-output-names: From common clock bindings. Recommended to be "si514".
18
19Example:
20 si514: clock-generator@55 {
21 reg = <0x55>;
22 #clock-cells = <0>;
23 compatible = "silabs,si514";
24 };
diff --git a/drivers/clk/Kconfig b/drivers/clk/Kconfig
index bd9f3120370f..573517151976 100644
--- a/drivers/clk/Kconfig
+++ b/drivers/clk/Kconfig
@@ -69,6 +69,16 @@ config COMMON_CLK_SI5351
69 This driver supports Silicon Labs 5351A/B/C programmable clock 69 This driver supports Silicon Labs 5351A/B/C programmable clock
70 generators. 70 generators.
71 71
72config COMMON_CLK_SI514
73 tristate "Clock driver for SiLabs 514 devices"
74 depends on I2C
75 depends on OF
76 select REGMAP_I2C
77 help
78 ---help---
79 This driver supports the Silicon Labs 514 programmable clock
80 generator.
81
72config COMMON_CLK_SI570 82config COMMON_CLK_SI570
73 tristate "Clock driver for SiLabs 570 and compatible devices" 83 tristate "Clock driver for SiLabs 570 and compatible devices"
74 depends on I2C 84 depends on I2C
diff --git a/drivers/clk/Makefile b/drivers/clk/Makefile
index d08b3e5985be..6594e53a4444 100644
--- a/drivers/clk/Makefile
+++ b/drivers/clk/Makefile
@@ -37,6 +37,7 @@ obj-$(CONFIG_CLK_QORIQ) += clk-qoriq.o
37obj-$(CONFIG_COMMON_CLK_RK808) += clk-rk808.o 37obj-$(CONFIG_COMMON_CLK_RK808) += clk-rk808.o
38obj-$(CONFIG_COMMON_CLK_S2MPS11) += clk-s2mps11.o 38obj-$(CONFIG_COMMON_CLK_S2MPS11) += clk-s2mps11.o
39obj-$(CONFIG_COMMON_CLK_SI5351) += clk-si5351.o 39obj-$(CONFIG_COMMON_CLK_SI5351) += clk-si5351.o
40obj-$(CONFIG_COMMON_CLK_SI514) += clk-si514.o
40obj-$(CONFIG_COMMON_CLK_SI570) += clk-si570.o 41obj-$(CONFIG_COMMON_CLK_SI570) += clk-si570.o
41obj-$(CONFIG_COMMON_CLK_CDCE925) += clk-cdce925.o 42obj-$(CONFIG_COMMON_CLK_CDCE925) += clk-cdce925.o
42obj-$(CONFIG_ARCH_STM32) += clk-stm32f4.o 43obj-$(CONFIG_ARCH_STM32) += clk-stm32f4.o
diff --git a/drivers/clk/clk-si514.c b/drivers/clk/clk-si514.c
new file mode 100644
index 000000000000..6af7dce54241
--- /dev/null
+++ b/drivers/clk/clk-si514.c
@@ -0,0 +1,379 @@
1/*
2 * Driver for Silicon Labs Si514 Programmable Oscillator
3 *
4 * Copyright (C) 2015 Topic Embedded Products
5 *
6 * Author: Mike Looijmans <mike.looijmans@topic.nl>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 */
18
19#include <linux/clk-provider.h>
20#include <linux/delay.h>
21#include <linux/module.h>
22#include <linux/i2c.h>
23#include <linux/regmap.h>
24#include <linux/slab.h>
25
26/* I2C registers */
27#define SI514_REG_LP 0
28#define SI514_REG_M_FRAC1 5
29#define SI514_REG_M_FRAC2 6
30#define SI514_REG_M_FRAC3 7
31#define SI514_REG_M_INT_FRAC 8
32#define SI514_REG_M_INT 9
33#define SI514_REG_HS_DIV 10
34#define SI514_REG_LS_HS_DIV 11
35#define SI514_REG_OE_STATE 14
36#define SI514_REG_RESET 128
37#define SI514_REG_CONTROL 132
38
39/* Register values */
40#define SI514_RESET_RST BIT(7)
41
42#define SI514_CONTROL_FCAL BIT(0)
43#define SI514_CONTROL_OE BIT(2)
44
45#define SI514_MIN_FREQ 100000U
46#define SI514_MAX_FREQ 250000000U
47
48#define FXO 31980000U
49
50#define FVCO_MIN 2080000000U
51#define FVCO_MAX 2500000000U
52
53#define HS_DIV_MAX 1022
54
55struct clk_si514 {
56 struct clk_hw hw;
57 struct regmap *regmap;
58 struct i2c_client *i2c_client;
59};
60#define to_clk_si514(_hw) container_of(_hw, struct clk_si514, hw)
61
62/* Multiplier/divider settings */
63struct clk_si514_muldiv {
64 u32 m_frac; /* 29-bit Fractional part of multiplier M */
65 u8 m_int; /* Integer part of multiplier M, 65..78 */
66 u8 ls_div_bits; /* 2nd divider, as 2^x */
67 u16 hs_div; /* 1st divider, must be even and 10<=x<=1022 */
68};
69
70/* Enables or disables the output driver */
71static int si514_enable_output(struct clk_si514 *data, bool enable)
72{
73 return regmap_update_bits(data->regmap, SI514_REG_CONTROL,
74 SI514_CONTROL_OE, enable ? SI514_CONTROL_OE : 0);
75}
76
77/* Retrieve clock multiplier and dividers from hardware */
78static int si514_get_muldiv(struct clk_si514 *data,
79 struct clk_si514_muldiv *settings)
80{
81 int err;
82 u8 reg[7];
83
84 err = regmap_bulk_read(data->regmap, SI514_REG_M_FRAC1,
85 reg, ARRAY_SIZE(reg));
86 if (err)
87 return err;
88
89 settings->m_frac = reg[0] | reg[1] << 8 | reg[2] << 16 |
90 (reg[3] & 0x1F) << 24;
91 settings->m_int = (reg[4] & 0x3f) << 3 | reg[3] >> 5;
92 settings->ls_div_bits = (reg[6] >> 4) & 0x07;
93 settings->hs_div = (reg[6] & 0x03) << 8 | reg[5];
94 return 0;
95}
96
97static int si514_set_muldiv(struct clk_si514 *data,
98 struct clk_si514_muldiv *settings)
99{
100 u8 lp;
101 u8 reg[7];
102 int err;
103
104 /* Calculate LP1/LP2 according to table 13 in the datasheet */
105 /* 65.259980246 */
106 if (settings->m_int < 65 ||
107 (settings->m_int == 65 && settings->m_frac <= 139575831))
108 lp = 0x22;
109 /* 67.859763463 */
110 else if (settings->m_int < 67 ||
111 (settings->m_int == 67 && settings->m_frac <= 461581994))
112 lp = 0x23;
113 /* 72.937624981 */
114 else if (settings->m_int < 72 ||
115 (settings->m_int == 72 && settings->m_frac <= 503383578))
116 lp = 0x33;
117 /* 75.843265046 */
118 else if (settings->m_int < 75 ||
119 (settings->m_int == 75 && settings->m_frac <= 452724474))
120 lp = 0x34;
121 else
122 lp = 0x44;
123
124 err = regmap_write(data->regmap, SI514_REG_LP, lp);
125 if (err < 0)
126 return err;
127
128 reg[0] = settings->m_frac;
129 reg[1] = settings->m_frac >> 8;
130 reg[2] = settings->m_frac >> 16;
131 reg[3] = settings->m_frac >> 24 | settings->m_int << 5;
132 reg[4] = settings->m_int >> 3;
133 reg[5] = settings->hs_div;
134 reg[6] = (settings->hs_div >> 8) | (settings->ls_div_bits << 4);
135
136 err = regmap_bulk_write(data->regmap, SI514_REG_HS_DIV, reg + 5, 2);
137 if (err < 0)
138 return err;
139 /*
140 * Writing to SI514_REG_M_INT_FRAC triggers the clock change, so that
141 * must be written last
142 */
143 return regmap_bulk_write(data->regmap, SI514_REG_M_FRAC1, reg, 5);
144}
145
146/* Calculate divider settings for a given frequency */
147static int si514_calc_muldiv(struct clk_si514_muldiv *settings,
148 unsigned long frequency)
149{
150 u64 m;
151 u32 ls_freq;
152 u32 tmp;
153 u8 res;
154
155 if ((frequency < SI514_MIN_FREQ) || (frequency > SI514_MAX_FREQ))
156 return -EINVAL;
157
158 /* Determine the minimum value of LS_DIV and resulting target freq. */
159 ls_freq = frequency;
160 if (frequency >= (FVCO_MIN / HS_DIV_MAX))
161 settings->ls_div_bits = 0;
162 else {
163 res = 1;
164 tmp = 2 * HS_DIV_MAX;
165 while (tmp <= (HS_DIV_MAX * 32)) {
166 if ((frequency * tmp) >= FVCO_MIN)
167 break;
168 ++res;
169 tmp <<= 1;
170 }
171 settings->ls_div_bits = res;
172 ls_freq = frequency << res;
173 }
174
175 /* Determine minimum HS_DIV, round up to even number */
176 settings->hs_div = DIV_ROUND_UP(FVCO_MIN >> 1, ls_freq) << 1;
177
178 /* M = LS_DIV x HS_DIV x frequency / F_XO (in fixed-point) */
179 m = ((u64)(ls_freq * settings->hs_div) << 29) + (FXO / 2);
180 do_div(m, FXO);
181 settings->m_frac = (u32)m & (BIT(29) - 1);
182 settings->m_int = (u32)(m >> 29);
183
184 return 0;
185}
186
187/* Calculate resulting frequency given the register settings */
188static unsigned long si514_calc_rate(struct clk_si514_muldiv *settings)
189{
190 u64 m = settings->m_frac | ((u64)settings->m_int << 29);
191 u32 d = settings->hs_div * BIT(settings->ls_div_bits);
192
193 return ((u32)(((m * FXO) + (FXO / 2)) >> 29)) / d;
194}
195
196static unsigned long si514_recalc_rate(struct clk_hw *hw,
197 unsigned long parent_rate)
198{
199 struct clk_si514 *data = to_clk_si514(hw);
200 struct clk_si514_muldiv settings;
201 int err;
202
203 err = si514_get_muldiv(data, &settings);
204 if (err) {
205 dev_err(&data->i2c_client->dev, "unable to retrieve settings\n");
206 return 0;
207 }
208
209 return si514_calc_rate(&settings);
210}
211
212static long si514_round_rate(struct clk_hw *hw, unsigned long rate,
213 unsigned long *parent_rate)
214{
215 struct clk_si514_muldiv settings;
216 int err;
217
218 if (!rate)
219 return 0;
220
221 err = si514_calc_muldiv(&settings, rate);
222 if (err)
223 return err;
224
225 return si514_calc_rate(&settings);
226}
227
228/*
229 * Update output frequency for big frequency changes (> 1000 ppm).
230 * The chip supports <1000ppm changes "on the fly", we haven't implemented
231 * that here.
232 */
233static int si514_set_rate(struct clk_hw *hw, unsigned long rate,
234 unsigned long parent_rate)
235{
236 struct clk_si514 *data = to_clk_si514(hw);
237 struct clk_si514_muldiv settings;
238 int err;
239
240 err = si514_calc_muldiv(&settings, rate);
241 if (err)
242 return err;
243
244 si514_enable_output(data, false);
245
246 err = si514_set_muldiv(data, &settings);
247 if (err < 0)
248 return err; /* Undefined state now, best to leave disabled */
249
250 /* Trigger calibration */
251 err = regmap_write(data->regmap, SI514_REG_CONTROL, SI514_CONTROL_FCAL);
252 if (err < 0)
253 return err;
254
255 /* Applying a new frequency can take up to 10ms */
256 usleep_range(10000, 12000);
257
258 si514_enable_output(data, true);
259
260 return err;
261}
262
263static const struct clk_ops si514_clk_ops = {
264 .recalc_rate = si514_recalc_rate,
265 .round_rate = si514_round_rate,
266 .set_rate = si514_set_rate,
267};
268
269static bool si514_regmap_is_volatile(struct device *dev, unsigned int reg)
270{
271 switch (reg) {
272 case SI514_REG_CONTROL:
273 case SI514_REG_RESET:
274 return true;
275 default:
276 return false;
277 }
278}
279
280static bool si514_regmap_is_writeable(struct device *dev, unsigned int reg)
281{
282 switch (reg) {
283 case SI514_REG_LP:
284 case SI514_REG_M_FRAC1 ... SI514_REG_LS_HS_DIV:
285 case SI514_REG_OE_STATE:
286 case SI514_REG_RESET:
287 case SI514_REG_CONTROL:
288 return true;
289 default:
290 return false;
291 }
292}
293
294static const struct regmap_config si514_regmap_config = {
295 .reg_bits = 8,
296 .val_bits = 8,
297 .cache_type = REGCACHE_RBTREE,
298 .max_register = SI514_REG_CONTROL,
299 .writeable_reg = si514_regmap_is_writeable,
300 .volatile_reg = si514_regmap_is_volatile,
301};
302
303static int si514_probe(struct i2c_client *client,
304 const struct i2c_device_id *id)
305{
306 struct clk_si514 *data;
307 struct clk_init_data init;
308 struct clk *clk;
309 int err;
310
311 data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);
312 if (!data)
313 return -ENOMEM;
314
315 init.ops = &si514_clk_ops;
316 init.flags = CLK_IS_ROOT;
317 init.num_parents = 0;
318 data->hw.init = &init;
319 data->i2c_client = client;
320
321 if (of_property_read_string(client->dev.of_node, "clock-output-names",
322 &init.name))
323 init.name = client->dev.of_node->name;
324
325 data->regmap = devm_regmap_init_i2c(client, &si514_regmap_config);
326 if (IS_ERR(data->regmap)) {
327 dev_err(&client->dev, "failed to allocate register map\n");
328 return PTR_ERR(data->regmap);
329 }
330
331 i2c_set_clientdata(client, data);
332
333 clk = devm_clk_register(&client->dev, &data->hw);
334 if (IS_ERR(clk)) {
335 dev_err(&client->dev, "clock registration failed\n");
336 return PTR_ERR(clk);
337 }
338 err = of_clk_add_provider(client->dev.of_node, of_clk_src_simple_get,
339 clk);
340 if (err) {
341 dev_err(&client->dev, "unable to add clk provider\n");
342 return err;
343 }
344
345 return 0;
346}
347
348static int si514_remove(struct i2c_client *client)
349{
350 of_clk_del_provider(client->dev.of_node);
351 return 0;
352}
353
354static const struct i2c_device_id si514_id[] = {
355 { "si514", 0 },
356 { }
357};
358MODULE_DEVICE_TABLE(i2c, si514_id);
359
360static const struct of_device_id clk_si514_of_match[] = {
361 { .compatible = "silabs,si514" },
362 { },
363};
364MODULE_DEVICE_TABLE(of, clk_si514_of_match);
365
366static struct i2c_driver si514_driver = {
367 .driver = {
368 .name = "si514",
369 .of_match_table = clk_si514_of_match,
370 },
371 .probe = si514_probe,
372 .remove = si514_remove,
373 .id_table = si514_id,
374};
375module_i2c_driver(si514_driver);
376
377MODULE_AUTHOR("Mike Looijmans <mike.looijmans@topic.nl>");
378MODULE_DESCRIPTION("Si514 driver");
379MODULE_LICENSE("GPL");