aboutsummaryrefslogtreecommitdiff
path: root/drivers/pwm/pwm-ti-ehrpwm.c
blob: ac3d731d228a5b5d49c4bb5ddef9d293a62e2c23 (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
// SPDX-License-Identifier: GPL-2.0+
/*
 * EHRPWM PWM driver
 *
 * Copyright (C) 2020 Dario Binacchi <dariobin@libero.it>
 *
 * Based on Linux kernel drivers/pwm/pwm-tiehrpwm.c
 */

#include <common.h>
#include <clk.h>
#include <div64.h>
#include <dm.h>
#include <dm/device_compat.h>
#include <pwm.h>
#include <asm/io.h>

#define NSEC_PER_SEC			        1000000000L

/* Time base module registers */
#define TI_EHRPWM_TBCTL				0x00
#define TI_EHRPWM_TBPRD				0x0A

#define TI_EHRPWM_TBCTL_PRDLD_MASK		BIT(3)
#define TI_EHRPWM_TBCTL_PRDLD_SHDW		0
#define TI_EHRPWM_TBCTL_PRDLD_IMDT		BIT(3)
#define TI_EHRPWM_TBCTL_CLKDIV_MASK		GENMASK(12, 7)
#define TI_EHRPWM_TBCTL_CTRMODE_MASK		GENMASK(1, 0)
#define TI_EHRPWM_TBCTL_CTRMODE_UP		0
#define TI_EHRPWM_TBCTL_CTRMODE_DOWN		BIT(0)
#define TI_EHRPWM_TBCTL_CTRMODE_UPDOWN		BIT(1)
#define TI_EHRPWM_TBCTL_CTRMODE_FREEZE		GENMASK(1, 0)

#define TI_EHRPWM_TBCTL_HSPCLKDIV_SHIFT		7
#define TI_EHRPWM_TBCTL_CLKDIV_SHIFT		10

#define TI_EHRPWM_CLKDIV_MAX			7
#define TI_EHRPWM_HSPCLKDIV_MAX			7
#define TI_EHRPWM_PERIOD_MAX			0xFFFF

/* Counter compare module registers */
#define TI_EHRPWM_CMPA				0x12
#define TI_EHRPWM_CMPB				0x14

/* Action qualifier module registers */
#define TI_EHRPWM_AQCTLA			0x16
#define TI_EHRPWM_AQCTLB			0x18
#define TI_EHRPWM_AQSFRC			0x1A
#define TI_EHRPWM_AQCSFRC			0x1C

#define TI_EHRPWM_AQCTL_CBU_MASK		GENMASK(9, 8)
#define TI_EHRPWM_AQCTL_CBU_FRCLOW		BIT(8)
#define TI_EHRPWM_AQCTL_CBU_FRCHIGH		BIT(9)
#define TI_EHRPWM_AQCTL_CBU_FRCTOGGLE		GENMASK(9, 8)
#define TI_EHRPWM_AQCTL_CAU_MASK		GENMASK(5, 4)
#define TI_EHRPWM_AQCTL_CAU_FRCLOW		BIT(4)
#define TI_EHRPWM_AQCTL_CAU_FRCHIGH		BIT(5)
#define TI_EHRPWM_AQCTL_CAU_FRCTOGGLE		GENMASK(5, 4)
#define TI_EHRPWM_AQCTL_PRD_MASK		GENMASK(3, 2)
#define TI_EHRPWM_AQCTL_PRD_FRCLOW		BIT(2)
#define TI_EHRPWM_AQCTL_PRD_FRCHIGH		BIT(3)
#define TI_EHRPWM_AQCTL_PRD_FRCTOGGLE		GENMASK(3, 2)
#define TI_EHRPWM_AQCTL_ZRO_MASK		GENMASK(1, 0)
#define TI_EHRPWM_AQCTL_ZRO_FRCLOW		BIT(0)
#define TI_EHRPWM_AQCTL_ZRO_FRCHIGH		BIT(1)
#define TI_EHRPWM_AQCTL_ZRO_FRCTOGGLE		GENMASK(1, 0)

#define TI_EHRPWM_AQCTL_CHANA_POLNORMAL		(TI_EHRPWM_AQCTL_CAU_FRCLOW | \
						 TI_EHRPWM_AQCTL_PRD_FRCHIGH | \
						 TI_EHRPWM_AQCTL_ZRO_FRCHIGH)
#define TI_EHRPWM_AQCTL_CHANA_POLINVERSED	(TI_EHRPWM_AQCTL_CAU_FRCHIGH | \
						 TI_EHRPWM_AQCTL_PRD_FRCLOW | \
						 TI_EHRPWM_AQCTL_ZRO_FRCLOW)
#define TI_EHRPWM_AQCTL_CHANB_POLNORMAL		(TI_EHRPWM_AQCTL_CBU_FRCLOW | \
						 TI_EHRPWM_AQCTL_PRD_FRCHIGH | \
						 TI_EHRPWM_AQCTL_ZRO_FRCHIGH)
#define TI_EHRPWM_AQCTL_CHANB_POLINVERSED	(TI_EHRPWM_AQCTL_CBU_FRCHIGH | \
						 TI_EHRPWM_AQCTL_PRD_FRCLOW | \
						 TI_EHRPWM_AQCTL_ZRO_FRCLOW)

#define TI_EHRPWM_AQSFRC_RLDCSF_MASK		GENMASK(7, 6)
#define TI_EHRPWM_AQSFRC_RLDCSF_ZRO		0
#define TI_EHRPWM_AQSFRC_RLDCSF_PRD		BIT(6)
#define TI_EHRPWM_AQSFRC_RLDCSF_ZROPRD		BIT(7)
#define TI_EHRPWM_AQSFRC_RLDCSF_IMDT		GENMASK(7, 6)

#define TI_EHRPWM_AQCSFRC_CSFB_MASK		GENMASK(3, 2)
#define TI_EHRPWM_AQCSFRC_CSFB_FRCDIS		0
#define TI_EHRPWM_AQCSFRC_CSFB_FRCLOW		BIT(2)
#define TI_EHRPWM_AQCSFRC_CSFB_FRCHIGH		BIT(3)
#define TI_EHRPWM_AQCSFRC_CSFB_DISSWFRC		GENMASK(3, 2)
#define TI_EHRPWM_AQCSFRC_CSFA_MASK		GENMASK(1, 0)
#define TI_EHRPWM_AQCSFRC_CSFA_FRCDIS		0
#define TI_EHRPWM_AQCSFRC_CSFA_FRCLOW		BIT(0)
#define TI_EHRPWM_AQCSFRC_CSFA_FRCHIGH		BIT(1)
#define TI_EHRPWM_AQCSFRC_CSFA_DISSWFRC		GENMASK(1, 0)

#define TI_EHRPWM_NUM_CHANNELS                  2

struct ti_ehrpwm_priv {
	fdt_addr_t regs;
	u32 clk_rate;
	struct clk tbclk;
	unsigned long period_cycles[TI_EHRPWM_NUM_CHANNELS];
	bool polarity_reversed[TI_EHRPWM_NUM_CHANNELS];
};

static void ti_ehrpwm_modify(u16 val, u16 mask, fdt_addr_t reg)
{
	unsigned short v;

	v = readw(reg);
	v &= ~mask;
	v |= val & mask;
	writew(v, reg);
}

static int ti_ehrpwm_set_invert(struct udevice *dev, uint channel,
				bool polarity)
{
	struct ti_ehrpwm_priv *priv = dev_get_priv(dev);

	if (channel >= TI_EHRPWM_NUM_CHANNELS)
		return -ENOSPC;

	/* Configuration of polarity in hardware delayed, do at enable */
	priv->polarity_reversed[channel] = polarity;
	return 0;
}

/**
 * set_prescale_div -	Set up the prescaler divider function
 * @rqst_prescaler:	prescaler value min
 * @prescale_div:	prescaler value set
 * @tb_clk_div:		Time Base Control prescaler bits
 */
static int set_prescale_div(unsigned long rqst_prescaler, u16 *prescale_div,
			    u16 *tb_clk_div)
{
	unsigned int clkdiv, hspclkdiv;

	for (clkdiv = 0; clkdiv <= TI_EHRPWM_CLKDIV_MAX; clkdiv++) {
		for (hspclkdiv = 0; hspclkdiv <= TI_EHRPWM_HSPCLKDIV_MAX;
		     hspclkdiv++) {
			/*
			 * calculations for prescaler value :
			 * prescale_div = HSPCLKDIVIDER * CLKDIVIDER.
			 * HSPCLKDIVIDER =  2 ** hspclkdiv
			 * CLKDIVIDER = (1),            if clkdiv == 0 *OR*
			 *              (2 * clkdiv),   if clkdiv != 0
			 *
			 * Configure prescale_div value such that period
			 * register value is less than 65535.
			 */

			*prescale_div = (1 << clkdiv) *
				(hspclkdiv ? (hspclkdiv * 2) : 1);
			if (*prescale_div > rqst_prescaler) {
				*tb_clk_div =
				    (clkdiv << TI_EHRPWM_TBCTL_CLKDIV_SHIFT) |
				    (hspclkdiv <<
				     TI_EHRPWM_TBCTL_HSPCLKDIV_SHIFT);
				return 0;
			}
		}
	}

	return 1;
}

static void ti_ehrpwm_configure_polarity(struct udevice *dev, uint channel)
{
	struct ti_ehrpwm_priv *priv = dev_get_priv(dev);
	u16 aqctl_val, aqctl_mask;
	unsigned int aqctl_reg;

	/*
	 * Configure PWM output to HIGH/LOW level on counter
	 * reaches compare register value and LOW/HIGH level
	 * on counter value reaches period register value and
	 * zero value on counter
	 */
	if (channel == 1) {
		aqctl_reg = TI_EHRPWM_AQCTLB;
		aqctl_mask = TI_EHRPWM_AQCTL_CBU_MASK;

		if (priv->polarity_reversed[channel])
			aqctl_val = TI_EHRPWM_AQCTL_CHANB_POLINVERSED;
		else
			aqctl_val = TI_EHRPWM_AQCTL_CHANB_POLNORMAL;
	} else {
		aqctl_reg = TI_EHRPWM_AQCTLA;
		aqctl_mask = TI_EHRPWM_AQCTL_CAU_MASK;

		if (priv->polarity_reversed[channel])
			aqctl_val = TI_EHRPWM_AQCTL_CHANA_POLINVERSED;
		else
			aqctl_val = TI_EHRPWM_AQCTL_CHANA_POLNORMAL;
	}

	aqctl_mask |= TI_EHRPWM_AQCTL_PRD_MASK | TI_EHRPWM_AQCTL_ZRO_MASK;
	ti_ehrpwm_modify(aqctl_val, aqctl_mask, priv->regs + aqctl_reg);
}

/*
 * period_ns = 10^9 * (ps_divval * period_cycles) / PWM_CLK_RATE
 * duty_ns   = 10^9 * (ps_divval * duty_cycles) / PWM_CLK_RATE
 */
static int ti_ehrpwm_set_config(struct udevice *dev, uint channel,
				uint period_ns, uint duty_ns)
{
	struct ti_ehrpwm_priv *priv = dev_get_priv(dev);
	u32 period_cycles, duty_cycles;
	u16 ps_divval, tb_divval;
	unsigned int i, cmp_reg;
	unsigned long long c;

	if (channel >= TI_EHRPWM_NUM_CHANNELS)
		return -ENOSPC;

	if (period_ns > NSEC_PER_SEC)
		return -ERANGE;

	c = priv->clk_rate;
	c = c * period_ns;
	do_div(c, NSEC_PER_SEC);
	period_cycles = (unsigned long)c;

	if (period_cycles < 1) {
		period_cycles = 1;
		duty_cycles = 1;
	} else {
		c = priv->clk_rate;
		c = c * duty_ns;
		do_div(c, NSEC_PER_SEC);
		duty_cycles = (unsigned long)c;
	}

	dev_dbg(dev, "channel=%d, period_ns=%d, duty_ns=%d\n",
		channel, period_ns, duty_ns);

	/*
	 * Period values should be same for multiple PWM channels as IP uses
	 * same period register for multiple channels.
	 */
	for (i = 0; i < TI_EHRPWM_NUM_CHANNELS; i++) {
		if (priv->period_cycles[i] &&
		    priv->period_cycles[i] != period_cycles) {
			/*
			 * Allow channel to reconfigure period if no other
			 * channels being configured.
			 */
			if (i == channel)
				continue;

			dev_err(dev, "period value conflicts with channel %u\n",
				i);
			return -EINVAL;
		}
	}

	priv->period_cycles[channel] = period_cycles;

	/* Configure clock prescaler to support Low frequency PWM wave */
	if (set_prescale_div(period_cycles / TI_EHRPWM_PERIOD_MAX, &ps_divval,
			     &tb_divval)) {
		dev_err(dev, "unsupported values\n");
		return -EINVAL;
	}

	/* Update clock prescaler values */
	ti_ehrpwm_modify(tb_divval, TI_EHRPWM_TBCTL_CLKDIV_MASK,
			 priv->regs + TI_EHRPWM_TBCTL);

	/* Update period & duty cycle with presacler division */
	period_cycles = period_cycles / ps_divval;
	duty_cycles = duty_cycles / ps_divval;

	/* Configure shadow loading on Period register */
	ti_ehrpwm_modify(TI_EHRPWM_TBCTL_PRDLD_SHDW, TI_EHRPWM_TBCTL_PRDLD_MASK,
			 priv->regs + TI_EHRPWM_TBCTL);

	writew(period_cycles, priv->regs + TI_EHRPWM_TBPRD);

	/* Configure ehrpwm counter for up-count mode */
	ti_ehrpwm_modify(TI_EHRPWM_TBCTL_CTRMODE_UP,
			 TI_EHRPWM_TBCTL_CTRMODE_MASK,
			 priv->regs + TI_EHRPWM_TBCTL);

	if (channel == 1)
		/* Channel 1 configured with compare B register */
		cmp_reg = TI_EHRPWM_CMPB;
	else
		/* Channel 0 configured with compare A register */
		cmp_reg = TI_EHRPWM_CMPA;

	writew(duty_cycles, priv->regs + cmp_reg);
	return 0;
}

static int ti_ehrpwm_disable(struct udevice *dev, uint channel)
{
	struct ti_ehrpwm_priv *priv = dev_get_priv(dev);
	u16 aqcsfrc_val, aqcsfrc_mask;
	int err;

	if (channel >= TI_EHRPWM_NUM_CHANNELS)
		return -ENOSPC;

	/* Action Qualifier puts PWM output low forcefully */
	if (channel) {
		aqcsfrc_val = TI_EHRPWM_AQCSFRC_CSFB_FRCLOW;
		aqcsfrc_mask = TI_EHRPWM_AQCSFRC_CSFB_MASK;
	} else {
		aqcsfrc_val = TI_EHRPWM_AQCSFRC_CSFA_FRCLOW;
		aqcsfrc_mask = TI_EHRPWM_AQCSFRC_CSFA_MASK;
	}

	/* Update shadow register first before modifying active register */
	ti_ehrpwm_modify(TI_EHRPWM_AQSFRC_RLDCSF_ZRO,
			 TI_EHRPWM_AQSFRC_RLDCSF_MASK,
			 priv->regs + TI_EHRPWM_AQSFRC);

	ti_ehrpwm_modify(aqcsfrc_val, aqcsfrc_mask,
			 priv->regs + TI_EHRPWM_AQCSFRC);

	/*
	 * Changes to immediate action on Action Qualifier. This puts
	 * Action Qualifier control on PWM output from next TBCLK
	 */
	ti_ehrpwm_modify(TI_EHRPWM_AQSFRC_RLDCSF_IMDT,
			 TI_EHRPWM_AQSFRC_RLDCSF_MASK,
			 priv->regs + TI_EHRPWM_AQSFRC);

	ti_ehrpwm_modify(aqcsfrc_val, aqcsfrc_mask,
			 priv->regs + TI_EHRPWM_AQCSFRC);

	/* Disabling TBCLK on PWM disable */
	err = clk_disable(&priv->tbclk);
	if (err) {
		dev_err(dev, "failed to disable tbclk\n");
		return err;
	}

	return 0;
}

static int ti_ehrpwm_enable(struct udevice *dev, uint channel)
{
	struct ti_ehrpwm_priv *priv = dev_get_priv(dev);
	u16 aqcsfrc_val, aqcsfrc_mask;
	int err;

	if (channel >= TI_EHRPWM_NUM_CHANNELS)
		return -ENOSPC;

	/* Disabling Action Qualifier on PWM output */
	if (channel) {
		aqcsfrc_val = TI_EHRPWM_AQCSFRC_CSFB_FRCDIS;
		aqcsfrc_mask = TI_EHRPWM_AQCSFRC_CSFB_MASK;
	} else {
		aqcsfrc_val = TI_EHRPWM_AQCSFRC_CSFA_FRCDIS;
		aqcsfrc_mask = TI_EHRPWM_AQCSFRC_CSFA_MASK;
	}

	/* Changes to shadow mode */
	ti_ehrpwm_modify(TI_EHRPWM_AQSFRC_RLDCSF_ZRO,
			 TI_EHRPWM_AQSFRC_RLDCSF_MASK,
			 priv->regs + TI_EHRPWM_AQSFRC);

	ti_ehrpwm_modify(aqcsfrc_val, aqcsfrc_mask,
			 priv->regs + TI_EHRPWM_AQCSFRC);

	/* Channels polarity can be configured from action qualifier module */
	ti_ehrpwm_configure_polarity(dev, channel);

	err = clk_enable(&priv->tbclk);
	if (err) {
		dev_err(dev, "failed to enable tbclk\n");
		return err;
	}

	return 0;
}

static int ti_ehrpwm_set_enable(struct udevice *dev, uint channel, bool enable)
{
	if (enable)
		return ti_ehrpwm_enable(dev, channel);

	return ti_ehrpwm_disable(dev, channel);
}

static int ti_ehrpwm_of_to_plat(struct udevice *dev)
{
	struct ti_ehrpwm_priv *priv = dev_get_priv(dev);

	priv->regs = dev_read_addr(dev);
	if (priv->regs == FDT_ADDR_T_NONE) {
		dev_err(dev, "invalid address\n");
		return -EINVAL;
	}

	dev_dbg(dev, "regs=0x%08lx\n", priv->regs);
	return 0;
}

static int ti_ehrpwm_remove(struct udevice *dev)
{
	struct ti_ehrpwm_priv *priv = dev_get_priv(dev);

	clk_release_all(&priv->tbclk, 1);
	return 0;
}

static int ti_ehrpwm_probe(struct udevice *dev)
{
	struct ti_ehrpwm_priv *priv = dev_get_priv(dev);
	struct clk clk;
	int err;

	err = clk_get_by_name(dev, "fck", &clk);
	if (err) {
		dev_err(dev, "failed to get clock\n");
		return err;
	}

	priv->clk_rate = clk_get_rate(&clk);
	if (IS_ERR_VALUE(priv->clk_rate) || !priv->clk_rate) {
		dev_err(dev, "failed to get clock rate\n");
		if (IS_ERR_VALUE(priv->clk_rate))
			return priv->clk_rate;

		return -EINVAL;
	}

	/* Acquire tbclk for Time Base EHRPWM submodule */
	err = clk_get_by_name(dev, "tbclk", &priv->tbclk);
	if (err) {
		dev_err(dev, "failed to get tbclk clock\n");
		return err;
	}

	return 0;
}

static const struct pwm_ops ti_ehrpwm_ops = {
	.set_config = ti_ehrpwm_set_config,
	.set_enable = ti_ehrpwm_set_enable,
	.set_invert = ti_ehrpwm_set_invert,
};

static const struct udevice_id ti_ehrpwm_ids[] = {
	{.compatible = "ti,am3352-ehrpwm"},
	{.compatible = "ti,am33xx-ehrpwm"},
	{}
};

U_BOOT_DRIVER(ti_ehrpwm) = {
	.name = "ti_ehrpwm",
	.id = UCLASS_PWM,
	.of_match = ti_ehrpwm_ids,
	.ops = &ti_ehrpwm_ops,
	.ofdata_to_platdata = ti_ehrpwm_of_to_plat,
	.probe = ti_ehrpwm_probe,
	.remove = ti_ehrpwm_remove,
	.priv_auto = sizeof(struct ti_ehrpwm_priv),
};