summaryrefslogtreecommitdiff
path: root/drivers/clk/clk-si570.c
blob: 0a6d70c497267872ee843d7bc3e399d5491acb86 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Driver for Silicon Labs Si570/Si571 Programmable XO/VCXO
 *
 * Copyright (C) 2010, 2011 Ericsson AB.
 * Copyright (C) 2011 Guenter Roeck.
 * Copyright (C) 2011 - 2021 Xilinx Inc.
 *
 * Author: Guenter Roeck <guenter.roeck@ericsson.com>
 *	   Sören Brinkmann <soren.brinkmann@xilinx.com>
 */

#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/regmap.h>
#include <linux/slab.h>

/* Si570 registers */
#define SI570_REG_HS_N1		7
#define SI570_REG_N1_RFREQ0	8
#define SI570_REG_RFREQ1	9
#define SI570_REG_RFREQ2	10
#define SI570_REG_RFREQ3	11
#define SI570_REG_RFREQ4	12
#define SI570_REG_CONTROL	135
#define SI570_REG_FREEZE_DCO	137
#define SI570_DIV_OFFSET_7PPM	6

#define HS_DIV_SHIFT		5
#define HS_DIV_MASK		0xe0
#define HS_DIV_OFFSET		4
#define N1_6_2_MASK		0x1f
#define N1_1_0_MASK		0xc0
#define RFREQ_37_32_MASK	0x3f

#define SI570_MIN_FREQ		10000000L
#define SI570_MAX_FREQ		1417500000L
#define SI598_MAX_FREQ		525000000L

#define FDCO_MIN		4850000000LL
#define FDCO_MAX		5670000000LL

#define SI570_CNTRL_RECALL	(1 << 0)
#define SI570_CNTRL_FREEZE_M	(1 << 5)
#define SI570_CNTRL_NEWFREQ	(1 << 6)

#define SI570_FREEZE_DCO	(1 << 4)

/**
 * struct clk_si570:
 * @hw:	Clock hw struct
 * @regmap:	Device's regmap
 * @div_offset:	Rgister offset for dividers
 * @max_freq:	Maximum frequency for this device
 * @fxtal:	Factory xtal frequency
 * @n1:		Clock divider N1
 * @hs_div:	Clock divider HSDIV
 * @rfreq:	Clock multiplier RFREQ
 * @frequency:	Current output frequency
 * @i2c_client:	I2C client pointer
 */
struct clk_si570 {
	struct clk_hw hw;
	struct regmap *regmap;
	unsigned int div_offset;
	u64 max_freq;
	u64 fxtal;
	unsigned int n1;
	unsigned int hs_div;
	u64 rfreq;
	u64 frequency;
	struct i2c_client *i2c_client;
};
#define to_clk_si570(_hw)	container_of(_hw, struct clk_si570, hw)

enum clk_si570_variant {
	si57x,
	si59x
};

/**
 * si570_get_divs() - Read clock dividers from HW
 * @data:	Pointer to struct clk_si570
 * @rfreq:	Fractional multiplier (output)
 * @n1:		Divider N1 (output)
 * @hs_div:	Divider HSDIV (output)
 * Returns 0 on success, negative errno otherwise.
 *
 * Retrieve clock dividers and multipliers from the HW.
 */
static int si570_get_divs(struct clk_si570 *data, u64 *rfreq,
		unsigned int *n1, unsigned int *hs_div)
{
	int err;
	u8 reg[6];
	u64 tmp;

	err = regmap_bulk_read(data->regmap, SI570_REG_HS_N1 + data->div_offset,
			reg, ARRAY_SIZE(reg));
	if (err)
		return err;

	*hs_div = ((reg[0] & HS_DIV_MASK) >> HS_DIV_SHIFT) + HS_DIV_OFFSET;
	*n1 = ((reg[0] & N1_6_2_MASK) << 2) + ((reg[1] & N1_1_0_MASK) >> 6) + 1;
	/* Handle invalid cases */
	if (*n1 > 1)
		*n1 &= ~1;

	tmp = reg[1] & RFREQ_37_32_MASK;
	tmp = (tmp << 8) + reg[2];
	tmp = (tmp << 8) + reg[3];
	tmp = (tmp << 8) + reg[4];
	tmp = (tmp << 8) + reg[5];
	*rfreq = tmp;

	return 0;
}

/**
 * si570_get_defaults() - Get default values
 * @data:	Driver data structure
 * @fout:	Factory frequency output
 * @skip_recall:	If true, don't recall NVM into RAM
 * Returns 0 on success, negative errno otherwise.
 */
static int si570_get_defaults(struct clk_si570 *data, u64 fout,
			      bool skip_recall)
{
	int err;
	u64 fdco;

	if (!skip_recall)
		regmap_write(data->regmap, SI570_REG_CONTROL,
			     SI570_CNTRL_RECALL);

	err = si570_get_divs(data, &data->rfreq, &data->n1, &data->hs_div);
	if (err)
		return err;

	/*
	 * Accept optional precision loss to avoid arithmetic overflows.
	 * Acceptable per Silicon Labs Application Note AN334.
	 */
	fdco = fout * data->n1 * data->hs_div;
	if (fdco >= (1LL << 36))
		data->fxtal = div64_u64(fdco << 24, data->rfreq >> 4);
	else
		data->fxtal = div64_u64(fdco << 28, data->rfreq);

	data->frequency = fout;

	return 0;
}

/**
 * si570_update_rfreq() - Update clock multiplier
 * @data:	Driver data structure
 * Passes on regmap_bulk_write() return value.
 */
static int si570_update_rfreq(struct clk_si570 *data)
{
	u8 reg[5];

	reg[0] = ((data->n1 - 1) << 6) |
		((data->rfreq >> 32) & RFREQ_37_32_MASK);
	reg[1] = (data->rfreq >> 24) & 0xff;
	reg[2] = (data->rfreq >> 16) & 0xff;
	reg[3] = (data->rfreq >> 8) & 0xff;
	reg[4] = data->rfreq & 0xff;

	return regmap_bulk_write(data->regmap, SI570_REG_N1_RFREQ0 +
			data->div_offset, reg, ARRAY_SIZE(reg));
}

/**
 * si570_calc_divs() - Caluclate clock dividers
 * @frequency:	Target frequency
 * @data:	Driver data structure
 * @out_rfreq:	RFREG fractional multiplier (output)
 * @out_n1:	Clock divider N1 (output)
 * @out_hs_div:	Clock divider HSDIV (output)
 * Returns 0 on success, negative errno otherwise.
 *
 * Calculate the clock dividers (@out_hs_div, @out_n1) and clock multiplier
 * (@out_rfreq) for a given target @frequency.
 */
static int si570_calc_divs(unsigned long frequency, struct clk_si570 *data,
		u64 *out_rfreq, unsigned int *out_n1, unsigned int *out_hs_div)
{
	int i;
	unsigned int n1, hs_div;
	u64 fdco, best_fdco = ULLONG_MAX;
	static const uint8_t si570_hs_div_values[] = { 11, 9, 7, 6, 5, 4 };

	for (i = 0; i < ARRAY_SIZE(si570_hs_div_values); i++) {
		hs_div = si570_hs_div_values[i];
		/* Calculate lowest possible value for n1 */
		n1 = div_u64(div_u64(FDCO_MIN, hs_div), frequency);
		if (!n1 || (n1 & 1))
			n1++;
		while (n1 <= 128) {
			fdco = (u64)frequency * (u64)hs_div * (u64)n1;
			if (fdco > FDCO_MAX)
				break;
			if (fdco >= FDCO_MIN && fdco < best_fdco) {
				*out_n1 = n1;
				*out_hs_div = hs_div;
				*out_rfreq = div64_u64(fdco << 28, data->fxtal);
				best_fdco = fdco;
			}
			n1 += (n1 == 1 ? 1 : 2);
		}
	}

	if (best_fdco == ULLONG_MAX)
		return -EINVAL;

	return 0;
}

static unsigned long si570_recalc_rate(struct clk_hw *hw,
		unsigned long parent_rate)
{
	int err;
	u64 rfreq, rate;
	unsigned int n1, hs_div;
	struct clk_si570 *data = to_clk_si570(hw);

	err = si570_get_divs(data, &rfreq, &n1, &hs_div);
	if (err) {
		dev_err(&data->i2c_client->dev, "unable to recalc rate\n");
		return data->frequency;
	}

	rfreq = div_u64(rfreq, hs_div * n1);
	rate = (data->fxtal * rfreq) >> 28;

	return rate;
}

static long si570_round_rate(struct clk_hw *hw, unsigned long rate,
		unsigned long *parent_rate)
{
	int err;
	u64 rfreq;
	unsigned int n1, hs_div;
	struct clk_si570 *data = to_clk_si570(hw);

	if (!rate)
		return 0;

	if (div64_u64(abs(rate - data->frequency) * 10000LL,
				data->frequency) < 35) {
		rfreq = div64_u64((data->rfreq * rate) +
				div64_u64(data->frequency, 2), data->frequency);
		n1 = data->n1;
		hs_div = data->hs_div;

	} else {
		err = si570_calc_divs(rate, data, &rfreq, &n1, &hs_div);
		if (err) {
			dev_err(&data->i2c_client->dev,
					"unable to round rate\n");
			return 0;
		}
	}

	return rate;
}

/**
 * si570_set_frequency() - Adjust output frequency
 * @data:	Driver data structure
 * @frequency:	Target frequency
 * Returns 0 on success.
 *
 * Update output frequency for big frequency changes (> 3,500 ppm).
 */
static int si570_set_frequency(struct clk_si570 *data, unsigned long frequency)
{
	int err;

	err = si570_calc_divs(frequency, data, &data->rfreq, &data->n1,
			&data->hs_div);
	if (err)
		return err;

	/*
	 * The DCO reg should be accessed with a read-modify-write operation
	 * per AN334
	 */
	regmap_write(data->regmap, SI570_REG_FREEZE_DCO, SI570_FREEZE_DCO);
	regmap_write(data->regmap, SI570_REG_HS_N1 + data->div_offset,
			((data->hs_div - HS_DIV_OFFSET) << HS_DIV_SHIFT) |
			(((data->n1 - 1) >> 2) & N1_6_2_MASK));
	si570_update_rfreq(data);
	regmap_write(data->regmap, SI570_REG_FREEZE_DCO, 0);
	regmap_write(data->regmap, SI570_REG_CONTROL, SI570_CNTRL_NEWFREQ);

	/* Applying a new frequency can take up to 10ms */
	usleep_range(10000, 12000);

	return 0;
}

/**
 * si570_set_frequency_small() - Adjust output frequency
 * @data:	Driver data structure
 * @frequency:	Target frequency
 * Returns 0 on success.
 *
 * Update output frequency for small frequency changes (< 3,500 ppm).
 */
static int si570_set_frequency_small(struct clk_si570 *data,
				     unsigned long frequency)
{
	/*
	 * This is a re-implementation of DIV_ROUND_CLOSEST
	 * using the div64_u64 function lieu of letting the compiler
	 * insert EABI calls
	 */
	data->rfreq = div64_u64((data->rfreq * frequency) +
			div_u64(data->frequency, 2), data->frequency);
	regmap_write(data->regmap, SI570_REG_CONTROL, SI570_CNTRL_FREEZE_M);
	si570_update_rfreq(data);
	regmap_write(data->regmap, SI570_REG_CONTROL, 0);

	/* Applying a new frequency (small change) can take up to 100us */
	usleep_range(100, 200);

	return 0;
}

static int si570_set_rate(struct clk_hw *hw, unsigned long rate,
		unsigned long parent_rate)
{
	struct clk_si570 *data = to_clk_si570(hw);
	struct i2c_client *client = data->i2c_client;
	int err;

	if (rate < SI570_MIN_FREQ || rate > data->max_freq) {
		dev_err(&client->dev,
			"requested frequency %lu Hz is out of range\n", rate);
		return -EINVAL;
	}

	if (div64_u64(abs(rate - data->frequency) * 10000LL,
				data->frequency) < 35)
		err = si570_set_frequency_small(data, rate);
	else
		err = si570_set_frequency(data, rate);

	if (err)
		return err;

	data->frequency = rate;

	return 0;
}

static const struct clk_ops si570_clk_ops = {
	.recalc_rate = si570_recalc_rate,
	.round_rate = si570_round_rate,
	.set_rate = si570_set_rate,
};

static bool si570_regmap_is_volatile(struct device *dev, unsigned int reg)
{
	switch (reg) {
	case SI570_REG_CONTROL:
		return true;
	default:
		return false;
	}
}

static bool si570_regmap_is_writeable(struct device *dev, unsigned int reg)
{
	switch (reg) {
	case SI570_REG_HS_N1 ... (SI570_REG_RFREQ4 + SI570_DIV_OFFSET_7PPM):
	case SI570_REG_CONTROL:
	case SI570_REG_FREEZE_DCO:
		return true;
	default:
		return false;
	}
}

static const struct regmap_config si570_regmap_config = {
	.reg_bits = 8,
	.val_bits = 8,
	.cache_type = REGCACHE_RBTREE,
	.max_register = 137,
	.writeable_reg = si570_regmap_is_writeable,
	.volatile_reg = si570_regmap_is_volatile,
};

static const struct i2c_device_id si570_id[] = {
	{ "si570", si57x },
	{ "si571", si57x },
	{ "si598", si59x },
	{ "si599", si59x },
	{ }
};
MODULE_DEVICE_TABLE(i2c, si570_id);

static int si570_probe(struct i2c_client *client)
{
	struct clk_si570 *data;
	struct clk_init_data init;
	const struct i2c_device_id *id = i2c_match_id(si570_id, client);
	u32 initial_fout, factory_fout, stability;
	bool skip_recall;
	int err;
	enum clk_si570_variant variant = id->driver_data;

	data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);
	if (!data)
		return -ENOMEM;

	init.ops = &si570_clk_ops;
	init.flags = 0;
	init.num_parents = 0;
	data->hw.init = &init;
	data->i2c_client = client;

	if (variant == si57x) {
		err = of_property_read_u32(client->dev.of_node,
				"temperature-stability", &stability);
		if (err) {
			dev_err(&client->dev,
				  "'temperature-stability' property missing\n");
			return err;
		}
		/* adjust register offsets for 7ppm devices */
		if (stability == 7)
			data->div_offset = SI570_DIV_OFFSET_7PPM;

		data->max_freq = SI570_MAX_FREQ;
	} else {
		data->max_freq = SI598_MAX_FREQ;
	}

	if (of_property_read_string(client->dev.of_node, "clock-output-names",
			&init.name))
		init.name = client->dev.of_node->name;

	err = of_property_read_u32(client->dev.of_node, "factory-fout",
			&factory_fout);
	if (err) {
		dev_err(&client->dev, "'factory-fout' property missing\n");
		return err;
	}

	skip_recall = of_property_read_bool(client->dev.of_node,
					    "silabs,skip-recall");

	data->regmap = devm_regmap_init_i2c(client, &si570_regmap_config);
	if (IS_ERR(data->regmap)) {
		dev_err(&client->dev, "failed to allocate register map\n");
		return PTR_ERR(data->regmap);
	}

	i2c_set_clientdata(client, data);
	err = si570_get_defaults(data, factory_fout, skip_recall);
	if (err)
		return err;

	err = devm_clk_hw_register(&client->dev, &data->hw);
	if (err) {
		dev_err(&client->dev, "clock registration failed\n");
		return err;
	}
	err = of_clk_add_hw_provider(client->dev.of_node, of_clk_hw_simple_get,
				     &data->hw);
	if (err) {
		dev_err(&client->dev, "unable to add clk provider\n");
		return err;
	}

	/* Read the requested initial output frequency from device tree */
	if (!of_property_read_u32(client->dev.of_node, "clock-frequency",
				&initial_fout)) {
		err = clk_set_rate(data->hw.clk, initial_fout);
		if (err) {
			of_clk_del_provider(client->dev.of_node);
			return err;
		}
	}

	/* Display a message indicating that we've successfully registered */
	dev_info(&client->dev, "registered, current frequency %llu Hz\n",
			data->frequency);

	return 0;
}

static void si570_remove(struct i2c_client *client)
{
	of_clk_del_provider(client->dev.of_node);
}

static const struct of_device_id clk_si570_of_match[] = {
	{ .compatible = "silabs,si570" },
	{ .compatible = "silabs,si571" },
	{ .compatible = "silabs,si598" },
	{ .compatible = "silabs,si599" },
	{ },
};
MODULE_DEVICE_TABLE(of, clk_si570_of_match);

static struct i2c_driver si570_driver = {
	.driver = {
		.name = "si570",
		.of_match_table = clk_si570_of_match,
	},
	.probe_new	= si570_probe,
	.remove		= si570_remove,
	.id_table	= si570_id,
};
module_i2c_driver(si570_driver);

MODULE_AUTHOR("Guenter Roeck <guenter.roeck@ericsson.com>");
MODULE_AUTHOR("Soeren Brinkmann <soren.brinkmann@xilinx.com>");
MODULE_DESCRIPTION("Si570 driver");
MODULE_LICENSE("GPL");