diff options
Diffstat (limited to 'drivers/pwm/pwm-imx-tpm.c')
-rw-r--r-- | drivers/pwm/pwm-imx-tpm.c | 449 |
1 files changed, 449 insertions, 0 deletions
diff --git a/drivers/pwm/pwm-imx-tpm.c b/drivers/pwm/pwm-imx-tpm.c new file mode 100644 index 000000000000..e8385c1cf342 --- /dev/null +++ b/drivers/pwm/pwm-imx-tpm.c | |||
@@ -0,0 +1,449 @@ | |||
1 | // SPDX-License-Identifier: GPL-2.0 | ||
2 | /* | ||
3 | * Copyright 2018-2019 NXP. | ||
4 | * | ||
5 | * Limitations: | ||
6 | * - The TPM counter and period counter are shared between | ||
7 | * multiple channels, so all channels should use same period | ||
8 | * settings. | ||
9 | * - Changes to polarity cannot be latched at the time of the | ||
10 | * next period start. | ||
11 | * - Changing period and duty cycle together isn't atomic, | ||
12 | * with the wrong timing it might happen that a period is | ||
13 | * produced with old duty cycle but new period settings. | ||
14 | */ | ||
15 | |||
16 | #include <linux/bitfield.h> | ||
17 | #include <linux/bitops.h> | ||
18 | #include <linux/clk.h> | ||
19 | #include <linux/err.h> | ||
20 | #include <linux/io.h> | ||
21 | #include <linux/log2.h> | ||
22 | #include <linux/module.h> | ||
23 | #include <linux/of.h> | ||
24 | #include <linux/of_address.h> | ||
25 | #include <linux/platform_device.h> | ||
26 | #include <linux/pwm.h> | ||
27 | #include <linux/slab.h> | ||
28 | |||
29 | #define PWM_IMX_TPM_PARAM 0x4 | ||
30 | #define PWM_IMX_TPM_GLOBAL 0x8 | ||
31 | #define PWM_IMX_TPM_SC 0x10 | ||
32 | #define PWM_IMX_TPM_CNT 0x14 | ||
33 | #define PWM_IMX_TPM_MOD 0x18 | ||
34 | #define PWM_IMX_TPM_CnSC(n) (0x20 + (n) * 0x8) | ||
35 | #define PWM_IMX_TPM_CnV(n) (0x24 + (n) * 0x8) | ||
36 | |||
37 | #define PWM_IMX_TPM_PARAM_CHAN GENMASK(7, 0) | ||
38 | |||
39 | #define PWM_IMX_TPM_SC_PS GENMASK(2, 0) | ||
40 | #define PWM_IMX_TPM_SC_CMOD GENMASK(4, 3) | ||
41 | #define PWM_IMX_TPM_SC_CMOD_INC_EVERY_CLK FIELD_PREP(PWM_IMX_TPM_SC_CMOD, 1) | ||
42 | #define PWM_IMX_TPM_SC_CPWMS BIT(5) | ||
43 | |||
44 | #define PWM_IMX_TPM_CnSC_CHF BIT(7) | ||
45 | #define PWM_IMX_TPM_CnSC_MSB BIT(5) | ||
46 | #define PWM_IMX_TPM_CnSC_MSA BIT(4) | ||
47 | |||
48 | /* | ||
49 | * The reference manual describes this field as two separate bits. The | ||
50 | * semantic of the two bits isn't orthogonal though, so they are treated | ||
51 | * together as a 2-bit field here. | ||
52 | */ | ||
53 | #define PWM_IMX_TPM_CnSC_ELS GENMASK(3, 2) | ||
54 | #define PWM_IMX_TPM_CnSC_ELS_INVERSED FIELD_PREP(PWM_IMX_TPM_CnSC_ELS, 1) | ||
55 | #define PWM_IMX_TPM_CnSC_ELS_NORMAL FIELD_PREP(PWM_IMX_TPM_CnSC_ELS, 2) | ||
56 | |||
57 | |||
58 | #define PWM_IMX_TPM_MOD_WIDTH 16 | ||
59 | #define PWM_IMX_TPM_MOD_MOD GENMASK(PWM_IMX_TPM_MOD_WIDTH - 1, 0) | ||
60 | |||
61 | struct imx_tpm_pwm_chip { | ||
62 | struct pwm_chip chip; | ||
63 | struct clk *clk; | ||
64 | void __iomem *base; | ||
65 | struct mutex lock; | ||
66 | u32 user_count; | ||
67 | u32 enable_count; | ||
68 | u32 real_period; | ||
69 | }; | ||
70 | |||
71 | struct imx_tpm_pwm_param { | ||
72 | u8 prescale; | ||
73 | u32 mod; | ||
74 | u32 val; | ||
75 | }; | ||
76 | |||
77 | static inline struct imx_tpm_pwm_chip * | ||
78 | to_imx_tpm_pwm_chip(struct pwm_chip *chip) | ||
79 | { | ||
80 | return container_of(chip, struct imx_tpm_pwm_chip, chip); | ||
81 | } | ||
82 | |||
83 | /* | ||
84 | * This function determines for a given pwm_state *state that a consumer | ||
85 | * might request the pwm_state *real_state that eventually is implemented | ||
86 | * by the hardware and the necessary register values (in *p) to achieve | ||
87 | * this. | ||
88 | */ | ||
89 | static int pwm_imx_tpm_round_state(struct pwm_chip *chip, | ||
90 | struct imx_tpm_pwm_param *p, | ||
91 | struct pwm_state *real_state, | ||
92 | struct pwm_state *state) | ||
93 | { | ||
94 | struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip); | ||
95 | u32 rate, prescale, period_count, clock_unit; | ||
96 | u64 tmp; | ||
97 | |||
98 | rate = clk_get_rate(tpm->clk); | ||
99 | tmp = (u64)state->period * rate; | ||
100 | clock_unit = DIV_ROUND_CLOSEST_ULL(tmp, NSEC_PER_SEC); | ||
101 | if (clock_unit <= PWM_IMX_TPM_MOD_MOD) | ||
102 | prescale = 0; | ||
103 | else | ||
104 | prescale = ilog2(clock_unit) + 1 - PWM_IMX_TPM_MOD_WIDTH; | ||
105 | |||
106 | if ((!FIELD_FIT(PWM_IMX_TPM_SC_PS, prescale))) | ||
107 | return -ERANGE; | ||
108 | p->prescale = prescale; | ||
109 | |||
110 | period_count = (clock_unit + ((1 << prescale) >> 1)) >> prescale; | ||
111 | p->mod = period_count; | ||
112 | |||
113 | /* calculate real period HW can support */ | ||
114 | tmp = (u64)period_count << prescale; | ||
115 | tmp *= NSEC_PER_SEC; | ||
116 | real_state->period = DIV_ROUND_CLOSEST_ULL(tmp, rate); | ||
117 | |||
118 | /* | ||
119 | * if eventually the PWM output is inactive, either | ||
120 | * duty cycle is 0 or status is disabled, need to | ||
121 | * make sure the output pin is inactive. | ||
122 | */ | ||
123 | if (!state->enabled) | ||
124 | real_state->duty_cycle = 0; | ||
125 | else | ||
126 | real_state->duty_cycle = state->duty_cycle; | ||
127 | |||
128 | tmp = (u64)p->mod * real_state->duty_cycle; | ||
129 | p->val = DIV_ROUND_CLOSEST_ULL(tmp, real_state->period); | ||
130 | |||
131 | real_state->polarity = state->polarity; | ||
132 | real_state->enabled = state->enabled; | ||
133 | |||
134 | return 0; | ||
135 | } | ||
136 | |||
137 | static void pwm_imx_tpm_get_state(struct pwm_chip *chip, | ||
138 | struct pwm_device *pwm, | ||
139 | struct pwm_state *state) | ||
140 | { | ||
141 | struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip); | ||
142 | u32 rate, val, prescale; | ||
143 | u64 tmp; | ||
144 | |||
145 | /* get period */ | ||
146 | state->period = tpm->real_period; | ||
147 | |||
148 | /* get duty cycle */ | ||
149 | rate = clk_get_rate(tpm->clk); | ||
150 | val = readl(tpm->base + PWM_IMX_TPM_SC); | ||
151 | prescale = FIELD_GET(PWM_IMX_TPM_SC_PS, val); | ||
152 | tmp = readl(tpm->base + PWM_IMX_TPM_CnV(pwm->hwpwm)); | ||
153 | tmp = (tmp << prescale) * NSEC_PER_SEC; | ||
154 | state->duty_cycle = DIV_ROUND_CLOSEST_ULL(tmp, rate); | ||
155 | |||
156 | /* get polarity */ | ||
157 | val = readl(tpm->base + PWM_IMX_TPM_CnSC(pwm->hwpwm)); | ||
158 | if ((val & PWM_IMX_TPM_CnSC_ELS) == PWM_IMX_TPM_CnSC_ELS_INVERSED) | ||
159 | state->polarity = PWM_POLARITY_INVERSED; | ||
160 | else | ||
161 | /* | ||
162 | * Assume reserved values (2b00 and 2b11) to yield | ||
163 | * normal polarity. | ||
164 | */ | ||
165 | state->polarity = PWM_POLARITY_NORMAL; | ||
166 | |||
167 | /* get channel status */ | ||
168 | state->enabled = FIELD_GET(PWM_IMX_TPM_CnSC_ELS, val) ? true : false; | ||
169 | } | ||
170 | |||
171 | /* this function is supposed to be called with mutex hold */ | ||
172 | static int pwm_imx_tpm_apply_hw(struct pwm_chip *chip, | ||
173 | struct imx_tpm_pwm_param *p, | ||
174 | struct pwm_state *state, | ||
175 | struct pwm_device *pwm) | ||
176 | { | ||
177 | struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip); | ||
178 | bool period_update = false; | ||
179 | bool duty_update = false; | ||
180 | u32 val, cmod, cur_prescale; | ||
181 | unsigned long timeout; | ||
182 | struct pwm_state c; | ||
183 | |||
184 | if (state->period != tpm->real_period) { | ||
185 | /* | ||
186 | * TPM counter is shared by multiple channels, so | ||
187 | * prescale and period can NOT be modified when | ||
188 | * there are multiple channels in use with different | ||
189 | * period settings. | ||
190 | */ | ||
191 | if (tpm->user_count > 1) | ||
192 | return -EBUSY; | ||
193 | |||
194 | val = readl(tpm->base + PWM_IMX_TPM_SC); | ||
195 | cmod = FIELD_GET(PWM_IMX_TPM_SC_CMOD, val); | ||
196 | cur_prescale = FIELD_GET(PWM_IMX_TPM_SC_PS, val); | ||
197 | if (cmod && cur_prescale != p->prescale) | ||
198 | return -EBUSY; | ||
199 | |||
200 | /* set TPM counter prescale */ | ||
201 | val &= ~PWM_IMX_TPM_SC_PS; | ||
202 | val |= FIELD_PREP(PWM_IMX_TPM_SC_PS, p->prescale); | ||
203 | writel(val, tpm->base + PWM_IMX_TPM_SC); | ||
204 | |||
205 | /* | ||
206 | * set period count: | ||
207 | * if the PWM is disabled (CMOD[1:0] = 2b00), then MOD register | ||
208 | * is updated when MOD register is written. | ||
209 | * | ||
210 | * if the PWM is enabled (CMOD[1:0] ≠ 2b00), the period length | ||
211 | * is latched into hardware when the next period starts. | ||
212 | */ | ||
213 | writel(p->mod, tpm->base + PWM_IMX_TPM_MOD); | ||
214 | tpm->real_period = state->period; | ||
215 | period_update = true; | ||
216 | } | ||
217 | |||
218 | pwm_imx_tpm_get_state(chip, pwm, &c); | ||
219 | |||
220 | /* polarity is NOT allowed to be changed if PWM is active */ | ||
221 | if (c.enabled && c.polarity != state->polarity) | ||
222 | return -EBUSY; | ||
223 | |||
224 | if (state->duty_cycle != c.duty_cycle) { | ||
225 | /* | ||
226 | * set channel value: | ||
227 | * if the PWM is disabled (CMOD[1:0] = 2b00), then CnV register | ||
228 | * is updated when CnV register is written. | ||
229 | * | ||
230 | * if the PWM is enabled (CMOD[1:0] ≠ 2b00), the duty length | ||
231 | * is latched into hardware when the next period starts. | ||
232 | */ | ||
233 | writel(p->val, tpm->base + PWM_IMX_TPM_CnV(pwm->hwpwm)); | ||
234 | duty_update = true; | ||
235 | } | ||
236 | |||
237 | /* make sure MOD & CnV registers are updated */ | ||
238 | if (period_update || duty_update) { | ||
239 | timeout = jiffies + msecs_to_jiffies(tpm->real_period / | ||
240 | NSEC_PER_MSEC + 1); | ||
241 | while (readl(tpm->base + PWM_IMX_TPM_MOD) != p->mod | ||
242 | || readl(tpm->base + PWM_IMX_TPM_CnV(pwm->hwpwm)) | ||
243 | != p->val) { | ||
244 | if (time_after(jiffies, timeout)) | ||
245 | return -ETIME; | ||
246 | cpu_relax(); | ||
247 | } | ||
248 | } | ||
249 | |||
250 | /* | ||
251 | * polarity settings will enabled/disable output status | ||
252 | * immediately, so if the channel is disabled, need to | ||
253 | * make sure MSA/MSB/ELS are set to 0 which means channel | ||
254 | * disabled. | ||
255 | */ | ||
256 | val = readl(tpm->base + PWM_IMX_TPM_CnSC(pwm->hwpwm)); | ||
257 | val &= ~(PWM_IMX_TPM_CnSC_ELS | PWM_IMX_TPM_CnSC_MSA | | ||
258 | PWM_IMX_TPM_CnSC_MSB); | ||
259 | if (state->enabled) { | ||
260 | /* | ||
261 | * set polarity (for edge-aligned PWM modes) | ||
262 | * | ||
263 | * ELS[1:0] = 2b10 yields normal polarity behaviour, | ||
264 | * ELS[1:0] = 2b01 yields inversed polarity. | ||
265 | * The other values are reserved. | ||
266 | */ | ||
267 | val |= PWM_IMX_TPM_CnSC_MSB; | ||
268 | val |= (state->polarity == PWM_POLARITY_NORMAL) ? | ||
269 | PWM_IMX_TPM_CnSC_ELS_NORMAL : | ||
270 | PWM_IMX_TPM_CnSC_ELS_INVERSED; | ||
271 | } | ||
272 | writel(val, tpm->base + PWM_IMX_TPM_CnSC(pwm->hwpwm)); | ||
273 | |||
274 | /* control the counter status */ | ||
275 | if (state->enabled != c.enabled) { | ||
276 | val = readl(tpm->base + PWM_IMX_TPM_SC); | ||
277 | if (state->enabled) { | ||
278 | if (++tpm->enable_count == 1) | ||
279 | val |= PWM_IMX_TPM_SC_CMOD_INC_EVERY_CLK; | ||
280 | } else { | ||
281 | if (--tpm->enable_count == 0) | ||
282 | val &= ~PWM_IMX_TPM_SC_CMOD; | ||
283 | } | ||
284 | writel(val, tpm->base + PWM_IMX_TPM_SC); | ||
285 | } | ||
286 | |||
287 | return 0; | ||
288 | } | ||
289 | |||
290 | static int pwm_imx_tpm_apply(struct pwm_chip *chip, | ||
291 | struct pwm_device *pwm, | ||
292 | struct pwm_state *state) | ||
293 | { | ||
294 | struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip); | ||
295 | struct imx_tpm_pwm_param param; | ||
296 | struct pwm_state real_state; | ||
297 | int ret; | ||
298 | |||
299 | ret = pwm_imx_tpm_round_state(chip, ¶m, &real_state, state); | ||
300 | if (ret) | ||
301 | return ret; | ||
302 | |||
303 | mutex_lock(&tpm->lock); | ||
304 | ret = pwm_imx_tpm_apply_hw(chip, ¶m, &real_state, pwm); | ||
305 | mutex_unlock(&tpm->lock); | ||
306 | |||
307 | return ret; | ||
308 | } | ||
309 | |||
310 | static int pwm_imx_tpm_request(struct pwm_chip *chip, struct pwm_device *pwm) | ||
311 | { | ||
312 | struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip); | ||
313 | |||
314 | mutex_lock(&tpm->lock); | ||
315 | tpm->user_count++; | ||
316 | mutex_unlock(&tpm->lock); | ||
317 | |||
318 | return 0; | ||
319 | } | ||
320 | |||
321 | static void pwm_imx_tpm_free(struct pwm_chip *chip, struct pwm_device *pwm) | ||
322 | { | ||
323 | struct imx_tpm_pwm_chip *tpm = to_imx_tpm_pwm_chip(chip); | ||
324 | |||
325 | mutex_lock(&tpm->lock); | ||
326 | tpm->user_count--; | ||
327 | mutex_unlock(&tpm->lock); | ||
328 | } | ||
329 | |||
330 | static const struct pwm_ops imx_tpm_pwm_ops = { | ||
331 | .request = pwm_imx_tpm_request, | ||
332 | .free = pwm_imx_tpm_free, | ||
333 | .get_state = pwm_imx_tpm_get_state, | ||
334 | .apply = pwm_imx_tpm_apply, | ||
335 | .owner = THIS_MODULE, | ||
336 | }; | ||
337 | |||
338 | static int pwm_imx_tpm_probe(struct platform_device *pdev) | ||
339 | { | ||
340 | struct imx_tpm_pwm_chip *tpm; | ||
341 | int ret; | ||
342 | u32 val; | ||
343 | |||
344 | tpm = devm_kzalloc(&pdev->dev, sizeof(*tpm), GFP_KERNEL); | ||
345 | if (!tpm) | ||
346 | return -ENOMEM; | ||
347 | |||
348 | platform_set_drvdata(pdev, tpm); | ||
349 | |||
350 | tpm->base = devm_platform_ioremap_resource(pdev, 0); | ||
351 | if (IS_ERR(tpm->base)) | ||
352 | return PTR_ERR(tpm->base); | ||
353 | |||
354 | tpm->clk = devm_clk_get(&pdev->dev, NULL); | ||
355 | if (IS_ERR(tpm->clk)) { | ||
356 | ret = PTR_ERR(tpm->clk); | ||
357 | if (ret != -EPROBE_DEFER) | ||
358 | dev_err(&pdev->dev, | ||
359 | "failed to get PWM clock: %d\n", ret); | ||
360 | return ret; | ||
361 | } | ||
362 | |||
363 | ret = clk_prepare_enable(tpm->clk); | ||
364 | if (ret) { | ||
365 | dev_err(&pdev->dev, | ||
366 | "failed to prepare or enable clock: %d\n", ret); | ||
367 | return ret; | ||
368 | } | ||
369 | |||
370 | tpm->chip.dev = &pdev->dev; | ||
371 | tpm->chip.ops = &imx_tpm_pwm_ops; | ||
372 | tpm->chip.base = -1; | ||
373 | tpm->chip.of_xlate = of_pwm_xlate_with_flags; | ||
374 | tpm->chip.of_pwm_n_cells = 3; | ||
375 | |||
376 | /* get number of channels */ | ||
377 | val = readl(tpm->base + PWM_IMX_TPM_PARAM); | ||
378 | tpm->chip.npwm = FIELD_GET(PWM_IMX_TPM_PARAM_CHAN, val); | ||
379 | |||
380 | mutex_init(&tpm->lock); | ||
381 | |||
382 | ret = pwmchip_add(&tpm->chip); | ||
383 | if (ret) { | ||
384 | dev_err(&pdev->dev, "failed to add PWM chip: %d\n", ret); | ||
385 | clk_disable_unprepare(tpm->clk); | ||
386 | } | ||
387 | |||
388 | return ret; | ||
389 | } | ||
390 | |||
391 | static int pwm_imx_tpm_remove(struct platform_device *pdev) | ||
392 | { | ||
393 | struct imx_tpm_pwm_chip *tpm = platform_get_drvdata(pdev); | ||
394 | int ret = pwmchip_remove(&tpm->chip); | ||
395 | |||
396 | clk_disable_unprepare(tpm->clk); | ||
397 | |||
398 | return ret; | ||
399 | } | ||
400 | |||
401 | static int __maybe_unused pwm_imx_tpm_suspend(struct device *dev) | ||
402 | { | ||
403 | struct imx_tpm_pwm_chip *tpm = dev_get_drvdata(dev); | ||
404 | |||
405 | if (tpm->enable_count > 0) | ||
406 | return -EBUSY; | ||
407 | |||
408 | clk_disable_unprepare(tpm->clk); | ||
409 | |||
410 | return 0; | ||
411 | } | ||
412 | |||
413 | static int __maybe_unused pwm_imx_tpm_resume(struct device *dev) | ||
414 | { | ||
415 | struct imx_tpm_pwm_chip *tpm = dev_get_drvdata(dev); | ||
416 | int ret = 0; | ||
417 | |||
418 | ret = clk_prepare_enable(tpm->clk); | ||
419 | if (ret) | ||
420 | dev_err(dev, | ||
421 | "failed to prepare or enable clock: %d\n", | ||
422 | ret); | ||
423 | |||
424 | return ret; | ||
425 | } | ||
426 | |||
427 | static SIMPLE_DEV_PM_OPS(imx_tpm_pwm_pm, | ||
428 | pwm_imx_tpm_suspend, pwm_imx_tpm_resume); | ||
429 | |||
430 | static const struct of_device_id imx_tpm_pwm_dt_ids[] = { | ||
431 | { .compatible = "fsl,imx7ulp-pwm", }, | ||
432 | { /* sentinel */ } | ||
433 | }; | ||
434 | MODULE_DEVICE_TABLE(of, imx_tpm_pwm_dt_ids); | ||
435 | |||
436 | static struct platform_driver imx_tpm_pwm_driver = { | ||
437 | .driver = { | ||
438 | .name = "imx7ulp-tpm-pwm", | ||
439 | .of_match_table = imx_tpm_pwm_dt_ids, | ||
440 | .pm = &imx_tpm_pwm_pm, | ||
441 | }, | ||
442 | .probe = pwm_imx_tpm_probe, | ||
443 | .remove = pwm_imx_tpm_remove, | ||
444 | }; | ||
445 | module_platform_driver(imx_tpm_pwm_driver); | ||
446 | |||
447 | MODULE_AUTHOR("Anson Huang <Anson.Huang@nxp.com>"); | ||
448 | MODULE_DESCRIPTION("i.MX TPM PWM Driver"); | ||
449 | MODULE_LICENSE("GPL v2"); | ||