diff options
author | Romain Izard <romain.izard.pro@gmail.com> | 2017-10-19 12:44:10 -0400 |
---|---|---|
committer | Thierry Reding <thierry.reding@gmail.com> | 2017-11-15 04:51:31 -0500 |
commit | 1b3d9a93ed8313e42f108f871133346694de5ac3 (patch) | |
tree | fb4f91f498af07611a64f98d9f602e6062d6f352 | |
parent | ccb4e74aebb6fbd56fc6783f4d5c6ded48bc2f5d (diff) |
pwm: atmel-tcb: Support backup mode
Save and restore registers for the PWM on suspend and resume, which
makes hibernation and backup modes possible.
Signed-off-by: Romain Izard <romain.izard.pro@gmail.com>
Acked-by: Nicolas Ferre <nicolas.ferre@microchip.com>
Signed-off-by: Thierry Reding <thierry.reding@gmail.com>
-rw-r--r-- | drivers/pwm/pwm-atmel-tcb.c | 63 |
1 files changed, 61 insertions, 2 deletions
diff --git a/drivers/pwm/pwm-atmel-tcb.c b/drivers/pwm/pwm-atmel-tcb.c index 75db585a2a94..acd3ce8ecf3f 100644 --- a/drivers/pwm/pwm-atmel-tcb.c +++ b/drivers/pwm/pwm-atmel-tcb.c | |||
@@ -37,11 +37,20 @@ struct atmel_tcb_pwm_device { | |||
37 | unsigned period; /* PWM period expressed in clk cycles */ | 37 | unsigned period; /* PWM period expressed in clk cycles */ |
38 | }; | 38 | }; |
39 | 39 | ||
40 | struct atmel_tcb_channel { | ||
41 | u32 enabled; | ||
42 | u32 cmr; | ||
43 | u32 ra; | ||
44 | u32 rb; | ||
45 | u32 rc; | ||
46 | }; | ||
47 | |||
40 | struct atmel_tcb_pwm_chip { | 48 | struct atmel_tcb_pwm_chip { |
41 | struct pwm_chip chip; | 49 | struct pwm_chip chip; |
42 | spinlock_t lock; | 50 | spinlock_t lock; |
43 | struct atmel_tc *tc; | 51 | struct atmel_tc *tc; |
44 | struct atmel_tcb_pwm_device *pwms[NPWM]; | 52 | struct atmel_tcb_pwm_device *pwms[NPWM]; |
53 | struct atmel_tcb_channel bkup[NPWM / 2]; | ||
45 | }; | 54 | }; |
46 | 55 | ||
47 | static inline struct atmel_tcb_pwm_chip *to_tcb_chip(struct pwm_chip *chip) | 56 | static inline struct atmel_tcb_pwm_chip *to_tcb_chip(struct pwm_chip *chip) |
@@ -175,12 +184,15 @@ static void atmel_tcb_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) | |||
175 | * Use software trigger to apply the new setting. | 184 | * Use software trigger to apply the new setting. |
176 | * If both PWM devices in this group are disabled we stop the clock. | 185 | * If both PWM devices in this group are disabled we stop the clock. |
177 | */ | 186 | */ |
178 | if (!(cmr & (ATMEL_TC_ACPC | ATMEL_TC_BCPC))) | 187 | if (!(cmr & (ATMEL_TC_ACPC | ATMEL_TC_BCPC))) { |
179 | __raw_writel(ATMEL_TC_SWTRG | ATMEL_TC_CLKDIS, | 188 | __raw_writel(ATMEL_TC_SWTRG | ATMEL_TC_CLKDIS, |
180 | regs + ATMEL_TC_REG(group, CCR)); | 189 | regs + ATMEL_TC_REG(group, CCR)); |
181 | else | 190 | tcbpwmc->bkup[group].enabled = 1; |
191 | } else { | ||
182 | __raw_writel(ATMEL_TC_SWTRG, regs + | 192 | __raw_writel(ATMEL_TC_SWTRG, regs + |
183 | ATMEL_TC_REG(group, CCR)); | 193 | ATMEL_TC_REG(group, CCR)); |
194 | tcbpwmc->bkup[group].enabled = 0; | ||
195 | } | ||
184 | 196 | ||
185 | spin_unlock(&tcbpwmc->lock); | 197 | spin_unlock(&tcbpwmc->lock); |
186 | } | 198 | } |
@@ -263,6 +275,7 @@ static int atmel_tcb_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) | |||
263 | /* Use software trigger to apply the new setting */ | 275 | /* Use software trigger to apply the new setting */ |
264 | __raw_writel(ATMEL_TC_CLKEN | ATMEL_TC_SWTRG, | 276 | __raw_writel(ATMEL_TC_CLKEN | ATMEL_TC_SWTRG, |
265 | regs + ATMEL_TC_REG(group, CCR)); | 277 | regs + ATMEL_TC_REG(group, CCR)); |
278 | tcbpwmc->bkup[group].enabled = 1; | ||
266 | spin_unlock(&tcbpwmc->lock); | 279 | spin_unlock(&tcbpwmc->lock); |
267 | return 0; | 280 | return 0; |
268 | } | 281 | } |
@@ -445,10 +458,56 @@ static const struct of_device_id atmel_tcb_pwm_dt_ids[] = { | |||
445 | }; | 458 | }; |
446 | MODULE_DEVICE_TABLE(of, atmel_tcb_pwm_dt_ids); | 459 | MODULE_DEVICE_TABLE(of, atmel_tcb_pwm_dt_ids); |
447 | 460 | ||
461 | #ifdef CONFIG_PM_SLEEP | ||
462 | static int atmel_tcb_pwm_suspend(struct device *dev) | ||
463 | { | ||
464 | struct platform_device *pdev = to_platform_device(dev); | ||
465 | struct atmel_tcb_pwm_chip *tcbpwm = platform_get_drvdata(pdev); | ||
466 | void __iomem *base = tcbpwm->tc->regs; | ||
467 | int i; | ||
468 | |||
469 | for (i = 0; i < (NPWM / 2); i++) { | ||
470 | struct atmel_tcb_channel *chan = &tcbpwm->bkup[i]; | ||
471 | |||
472 | chan->cmr = readl(base + ATMEL_TC_REG(i, CMR)); | ||
473 | chan->ra = readl(base + ATMEL_TC_REG(i, RA)); | ||
474 | chan->rb = readl(base + ATMEL_TC_REG(i, RB)); | ||
475 | chan->rc = readl(base + ATMEL_TC_REG(i, RC)); | ||
476 | } | ||
477 | return 0; | ||
478 | } | ||
479 | |||
480 | static int atmel_tcb_pwm_resume(struct device *dev) | ||
481 | { | ||
482 | struct platform_device *pdev = to_platform_device(dev); | ||
483 | struct atmel_tcb_pwm_chip *tcbpwm = platform_get_drvdata(pdev); | ||
484 | void __iomem *base = tcbpwm->tc->regs; | ||
485 | int i; | ||
486 | |||
487 | for (i = 0; i < (NPWM / 2); i++) { | ||
488 | struct atmel_tcb_channel *chan = &tcbpwm->bkup[i]; | ||
489 | |||
490 | writel(chan->cmr, base + ATMEL_TC_REG(i, CMR)); | ||
491 | writel(chan->ra, base + ATMEL_TC_REG(i, RA)); | ||
492 | writel(chan->rb, base + ATMEL_TC_REG(i, RB)); | ||
493 | writel(chan->rc, base + ATMEL_TC_REG(i, RC)); | ||
494 | if (chan->enabled) { | ||
495 | writel(ATMEL_TC_CLKEN | ATMEL_TC_SWTRG, | ||
496 | base + ATMEL_TC_REG(i, CCR)); | ||
497 | } | ||
498 | } | ||
499 | return 0; | ||
500 | } | ||
501 | #endif | ||
502 | |||
503 | static SIMPLE_DEV_PM_OPS(atmel_tcb_pwm_pm_ops, atmel_tcb_pwm_suspend, | ||
504 | atmel_tcb_pwm_resume); | ||
505 | |||
448 | static struct platform_driver atmel_tcb_pwm_driver = { | 506 | static struct platform_driver atmel_tcb_pwm_driver = { |
449 | .driver = { | 507 | .driver = { |
450 | .name = "atmel-tcb-pwm", | 508 | .name = "atmel-tcb-pwm", |
451 | .of_match_table = atmel_tcb_pwm_dt_ids, | 509 | .of_match_table = atmel_tcb_pwm_dt_ids, |
510 | .pm = &atmel_tcb_pwm_pm_ops, | ||
452 | }, | 511 | }, |
453 | .probe = atmel_tcb_pwm_probe, | 512 | .probe = atmel_tcb_pwm_probe, |
454 | .remove = atmel_tcb_pwm_remove, | 513 | .remove = atmel_tcb_pwm_remove, |