summaryrefslogtreecommitdiff
path: root/drivers/net/can/spi/mcp25xxfd/mcp25xxfd_clock.c
blob: 32b5888df88ef9c3bebef3fa15b8ab3083065bec (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
// SPDX-License-Identifier: GPL-2.0

/* CAN bus driver for Microchip 25XXFD CAN Controller with SPI Interface
 *
 * Copyright 2019 Martin Sperl <kernel@martin.sperl.org>
 */

/* Known hardware issues and workarounds in this driver:
 *
 * * There is one situation where the controller will require a full POR
 *   (total power off) to recover from a bad Clock configuration.
 *   This happens when the wrong clock is configured in the device tree
 *   (say 4MHz are configured, while 40MHz is the actual clock frequency
 *   of the HW).
 *   In such a situation the driver tries to enable the PLL, which will
 *   never synchronize and the controller becomes unresponsive to further
 *   spi requests until a full POR.
 *
 *   Mitigation:
 *     none as of now
 *
 *   Possible implementation of a mitigation/sanity check:
 *     during initialization:
 *       * try to identify the HW at 1MHz:
 *         on success:
 *           * controller is identified
 *         on failure:
 *           * controller is absent - fail
 *       * force controller clock to run with disabled PLL
 *       * try to identify the HW at 2MHz:
 *         on success:
 *           * controller clock is >= 4 MHz
 *           * this may be 4MHz
 *         on failure:
 *           * controller clock is < 4 MHz
 *       * try to identify the HW at 2.5MHz:
 *         on success:
 *           * controller clock is >= 5 MHz
 *           * this may not be 4MHz
 *         on failure:
 *           * controller clock is 4 MHz
 *           * enable PLL
 *           * exit successfully (or run last test for verification purposes)
 *       * try to identify the HW at <dt-clock/2> MHz:
 *         on success:
 *           * controller clock is >= <dt-clock/2> MHz
 *              (it could be higher though)
 *         on failure:
 *           * the controller is not running at the
 *             clock rate configured in the DT
 *           * if PLL is enabled warn about requirements of POR
 *           * fail
 *
 *   Side-effects:
 *     * longer initialization time
 *
 *   Possible issues with mitigation:
 *     * possibly miss-identification because the SPI block may work
 *       "somewhat" at frequencies > < clock / 2 + delta f>
 *       this may be especially true for the situation where we test if
 *       2.5MHz SPI-Clock works.
 *     * also SPI HW-clock dividers may do a round down to fixed frequencies
 *       which is not properly reported and may result in false positives
 *       because a frequency lower than expected is used.
 *
 *   This is the reason why only simple testing is enabled at the risk of
 *   the need for a POR.
 */

#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/jiffies.h>
#include <linux/kernel.h>
#include <linux/mutex.h>
#include <linux/of.h>
#include <linux/spi/spi.h>

#include "mcp25xxfd_can.h"
#include "mcp25xxfd_clock.h"
#include "mcp25xxfd_cmd.h"
#include "mcp25xxfd_priv.h"

/* the PLL may take some time to synchronize - use 1 second as timeout */
#define MCP25XXFD_OSC_POLLING_JIFFIES	(HZ)

static u32 _mcp25xxfd_clkout_mask(struct mcp25xxfd_priv *priv)
{
	u32 val = 0;

	if (priv->config.clock_div2)
		val |= MCP25XXFD_OSC_SCLKDIV;

	switch (priv->config.clock_odiv) {
	case 0:
		break;
	case 1:
		val |= MCP25XXFD_OSC_CLKODIV_1 << MCP25XXFD_OSC_CLKODIV_SHIFT;
		break;
	case 2:
		val |= MCP25XXFD_OSC_CLKODIV_2 << MCP25XXFD_OSC_CLKODIV_SHIFT;
		break;
	case 4:
		val |= MCP25XXFD_OSC_CLKODIV_4 << MCP25XXFD_OSC_CLKODIV_SHIFT;
		break;
	case 10:
		val |= MCP25XXFD_OSC_CLKODIV_10 << MCP25XXFD_OSC_CLKODIV_SHIFT;
		break;
	default:
		/* this should never happen but is error-handled
		 * by the dt-parsing
		 */
		break;
	}

	return val;
}

static int _mcp25xxfd_waitfor_osc(struct mcp25xxfd_priv *priv,
				  u32 waitfor, u32 mask)
{
	unsigned long timeout;
	int ret;

	/* wait for synced pll/osc/sclk */
	timeout = jiffies + MCP25XXFD_OSC_POLLING_JIFFIES;
	while (time_before_eq(jiffies, timeout)) {
		ret = mcp25xxfd_cmd_read(priv->spi, MCP25XXFD_OSC,
					 &priv->regs.osc);
		if (ret)
			return ret;
		/* check for expected bits to be set/unset */
		if ((priv->regs.osc & mask) == waitfor)
			return 0;
	}

	return -ETIMEDOUT;
}

static int _mcp25xxfd_clock_configure_osc(struct mcp25xxfd_priv *priv,
					  u32 value, u32 waitfor, u32 mask)
{
	int ret;

	/* write the osc value to the controller - waking it if necessary */
	ret = mcp25xxfd_cmd_write(priv->spi, MCP25XXFD_OSC, value);
	if (ret)
		return ret;

	/* wait for the clock to stabelize */
	ret = _mcp25xxfd_waitfor_osc(priv, waitfor, mask);

	/* on timeout try again setting the register */
	if (ret == -ETIMEDOUT) {
		/* write the clock to the controller */
		ret = mcp25xxfd_cmd_write(priv->spi, MCP25XXFD_OSC, value);
		if (ret)
			return ret;

		/* wait for the clock to stabelize */
		ret = _mcp25xxfd_waitfor_osc(priv, waitfor, mask);
	}

	/* handle timeout special - report the fact */
	if (ret == -ETIMEDOUT)
		dev_err(&priv->spi->dev,
			"Clock did not switch within the timeout period\n");

	return ret;
}

static int _mcp25xxfd_clock_start(struct mcp25xxfd_priv *priv)
{
	u32 value = _mcp25xxfd_clkout_mask(priv);
	u32 waitfor = MCP25XXFD_OSC_OSCRDY;
	u32 mask = waitfor | MCP25XXFD_OSC_OSCDIS | MCP25XXFD_OSC_PLLRDY |
		MCP25XXFD_OSC_PLLEN;

	/* enable PLL as well - set expectations */
	if (priv->config.clock_pll) {
		value   |= MCP25XXFD_OSC_PLLEN;
		waitfor |= MCP25XXFD_OSC_PLLRDY | MCP25XXFD_OSC_PLLEN;
	}

	/* set the oscilator now */
	return _mcp25xxfd_clock_configure_osc(priv, value, waitfor, mask);
}

static int _mcp25xxfd_clock_stop(struct mcp25xxfd_priv *priv)
{
	u32 value = _mcp25xxfd_clkout_mask(priv);
	u32 waitfor = 0;
	u32 mask = MCP25XXFD_OSC_OSCDIS | MCP25XXFD_OSC_PLLRDY |
		MCP25XXFD_OSC_PLLEN;
	int ret;

	ret = _mcp25xxfd_clock_configure_osc(priv, value, waitfor, mask);
	if (ret)
		return ret;

	/* finally switch the controller mode to sleep
	 * by this time the controller should be in config mode already
	 * this way we wake to config mode again
	 */
	return mcp25xxfd_can_sleep_mode(priv);
}

int mcp25xxfd_clock_start(struct mcp25xxfd_priv *priv, int requestor_mask)
{
	int ret = 0;

	/* without a clock there is nothing we can do... */
	if (IS_ERR(priv->clk))
		return PTR_ERR(priv->clk);

	mutex_lock(&priv->clk_user_lock);

	/* if clock is already started, then skip */
	if (priv->clk_user_mask & requestor_mask)
		goto out;

	/* enable the clock on the host side*/
	ret = clk_prepare_enable(priv->clk);
	if (ret)
		goto out;

	/* enable the clock on the controller side */
	ret = _mcp25xxfd_clock_start(priv);
	if (ret)
		goto out;

	/* mark the clock for the specific component as started */
	priv->clk_user_mask |= requestor_mask;

	/* and now we use the normal spi speed */
	priv->spi_use_speed_hz = priv->spi_normal_speed_hz;

out:
	mutex_unlock(&priv->clk_user_lock);

	return ret;
}

int mcp25xxfd_clock_stop(struct mcp25xxfd_priv *priv, int requestor_mask)
{
	int ret;

	/* keep the clock on if explicitely configured */
	if (priv->config.clock_allways_on)
		return 0;

	/* without a clock there is nothing we can do... */
	if (IS_ERR(priv->clk))
		return PTR_ERR(priv->clk);

	mutex_lock(&priv->clk_user_lock);

	/* if the mask is empty then skip, as the clock is stopped */
	if (!priv->clk_user_mask)
		goto out;

	/* clear the clock mask */
	priv->clk_user_mask &= ~requestor_mask;

	/* if the mask is not empty then skip, as the clock is needed */
	if (priv->clk_user_mask)
		goto out;

	/* and now we use the setup spi speed */
	priv->spi_use_speed_hz = priv->spi_setup_speed_hz;

	/* stop the clock on the controller */
	ret = _mcp25xxfd_clock_stop(priv);

	/* and we stop the clock on the host*/
	if (!IS_ERR(priv->clk))
		clk_disable_unprepare(priv->clk);
out:
	mutex_unlock(&priv->clk_user_lock);

	return 0;
}

static int _mcp25xxfd_clock_probe(struct mcp25xxfd_priv *priv)
{
	int ret;

	/* Wait for oscillator startup timer after power up */
	mdelay(MCP25XXFD_OST_DELAY_MS);

	/* send a "blind" reset, hoping we are in Config mode */
	mcp25xxfd_cmd_reset(priv->spi);

	/* Wait for oscillator startup again */
	mdelay(MCP25XXFD_OST_DELAY_MS);

	/* check clock register that the clock is ready or disabled */
	ret = mcp25xxfd_cmd_read_regs(priv->spi, MCP25XXFD_OSC |
				      MCP25XXFD_ADDRESS_WITH_CRC,
				      &priv->regs.osc, 4);
	if (ret == -EILSEQ)
		dev_err(&priv->spi->dev,
			"CRC read of clock register resulted in a bad CRC mismatch - hw not found\n");

	if (ret)
		return ret;

	/* there can only be one... */
	switch (priv->regs.osc &
		(MCP25XXFD_OSC_OSCRDY | MCP25XXFD_OSC_OSCDIS)) {
	case MCP25XXFD_OSC_OSCRDY: /* either the clock is ready */
		break;
	case MCP25XXFD_OSC_OSCDIS: /* or the clock is disabled */
		break;
	default:
		/* otherwise there is no valid device (or in strange state)
		 *
		 * if PLL is enabled but not ready, then there may be
		 * something "fishy"
		 * this happened during driver development
		 * (enabling pll, when when on wrong clock), so best warn
		 * about such a possibility
		 */
		if ((priv->regs.osc &
		     (MCP25XXFD_OSC_PLLEN | MCP25XXFD_OSC_PLLRDY))
		    == MCP25XXFD_OSC_PLLEN)
			dev_err(&priv->spi->dev,
				"mcp25xxfd may be in a strange state - a power disconnect may be required\n");

		return -ENODEV;
	}

	return 0;
}

int mcp25xxfd_clock_probe(struct mcp25xxfd_priv *priv)
{
	int ret;

	/* this will also enable the MCP25XXFD_CLK_USER_CAN clock */
	ret = _mcp25xxfd_clock_probe(priv);

	/* on error retry a second time */
	if (ret == -ENODEV) {
		ret = _mcp25xxfd_clock_probe(priv);
		if (!ret)
			dev_info(&priv->spi->dev,
				 "found device only during retry\n");
	}
	if (ret) {
		if (ret == -ENODEV)
			dev_err(&priv->spi->dev,
				"Cannot initialize MCP%x. Wrong wiring? (oscilator register reads as %08x)\n",
				priv->model, priv->regs.osc);
	}

	return ret;
}

void mcp25xxfd_clock_release(struct mcp25xxfd_priv *priv)
{
	if (!IS_ERR_OR_NULL(priv->clk))
		clk_disable_unprepare(priv->clk);
}

#ifdef CONFIG_OF_DYNAMIC
static int mcp25xxfd_clock_of_parse(struct mcp25xxfd_priv *priv)
{
	struct spi_device *spi = priv->spi;
	const struct device_node *np = spi->dev.of_node;
	u32 val;
	int ret;

	priv->config.clock_div2 =
		of_property_read_bool(np, "microchip,clock-div2");

	priv->config.clock_allways_on =
		of_property_read_bool(np, "microchip,clock-allways-on");

	priv->config.clock_odiv = 10;
	ret = of_property_read_u32_index(np, "microchip,clock-out-div",
					 0, &val);
	if (!ret) {
		switch (val) {
		case 0:
		case 1:
		case 2:
		case 4:
		case 10:
			priv->config.clock_odiv = val;
			break;
		default:
			dev_err(&spi->dev,
				"Invalid value in device tree for microchip,clock_out_div: %u - valid values: 0, 1, 2, 4, 10\n",
				val);
			return -EINVAL;
		}
	}

	return 0;
}
#else
static int mcp25xxfd_clock_of_parse(struct mcp25xxfd_priv *priv)
{
	return 0;
}
#endif

int mcp25xxfd_clock_init(struct mcp25xxfd_priv *priv)
{
	struct spi_device *spi = priv->spi;
	struct clk *clk;
	int ret, freq;

	mutex_init(&priv->clk_user_lock);

	priv->config.clock_div2 = false;
	priv->config.clock_allways_on = false;
	priv->config.clock_odiv = 10;

	ret = mcp25xxfd_clock_of_parse(priv);
	if (ret)
		return ret;

	/* get clock */
	clk = devm_clk_get(&spi->dev, NULL);
	if (IS_ERR(clk))
		return PTR_ERR(clk);

	freq = clk_get_rate(clk);
	if (freq < MCP25XXFD_MIN_CLOCK_FREQUENCY ||
	    freq > MCP25XXFD_MAX_CLOCK_FREQUENCY) {
		dev_err(&spi->dev,
			"Clock frequency %i is not in range [%i:%i]\n",
			freq,
			MCP25XXFD_MIN_CLOCK_FREQUENCY,
			MCP25XXFD_MAX_CLOCK_FREQUENCY);
		return -ERANGE;
	}

	/* enable the clock and mark as enabled */
	ret = clk_prepare_enable(clk);
	if (ret)
		return ret;
	priv->clk = clk;

	/* if we have a clock that is <= 4MHz, enable the pll */
	priv->config.clock_pll =
		(freq <= MCP25XXFD_AUTO_PLL_MAX_CLOCK_FREQUENCY);

	/* decide on the effective clock rate */
	priv->clock_freq = freq;
	if (priv->config.clock_pll)
		priv->clock_freq *= MCP25XXFD_PLL_MULTIPLIER;
	if (priv->config.clock_div2)
		priv->clock_freq /= MCP25XXFD_SCLK_DIVIDER;

	/* calculate the clock frequencies to use
	 *
	 * setup clock speed is at most 1/4 the input clock speed
	 * the reason for the factor of 4 is that there is
	 * a clock divider in the controller that MAY be enabled in some
	 * circumstances so we may find a controller with that enabled
	 * during probe phase
	 */
	priv->spi_setup_speed_hz = freq / 4;

	/* normal operation clock speeds */
	priv->spi_normal_speed_hz = priv->clock_freq / 2;
	if (priv->config.clock_div2) {
		priv->spi_setup_speed_hz /= MCP25XXFD_SCLK_DIVIDER;
		priv->spi_normal_speed_hz /= MCP25XXFD_SCLK_DIVIDER;
	}

	/* set limit on speed */
	if (spi->max_speed_hz) {
		priv->spi_setup_speed_hz = min_t(int,
						 priv->spi_setup_speed_hz,
						 spi->max_speed_hz);
		priv->spi_normal_speed_hz = min_t(int,
						  priv->spi_normal_speed_hz,
						  spi->max_speed_hz);
	}

	/* use setup speed by default
	 * - this is switched when clock is enabled/disabled
	 */
	priv->spi_use_speed_hz = priv->spi_setup_speed_hz;

	return 0;
}

void mcp25xxfd_clock_fake_sleep(struct mcp25xxfd_priv *priv)
{
	priv->regs.osc &= ~(MCP25XXFD_OSC_OSCRDY |
			    MCP25XXFD_OSC_PLLRDY |
			    MCP25XXFD_OSC_SCLKRDY);
	priv->regs.osc |= MCP25XXFD_OSC_OSCDIS;
}