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>
This commit is contained in:
committed by
Thierry Reding
parent
ccb4e74aeb
commit
1b3d9a93ed
@@ -37,11 +37,20 @@ struct atmel_tcb_pwm_device {
|
|||||||
unsigned period; /* PWM period expressed in clk cycles */
|
unsigned period; /* PWM period expressed in clk cycles */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct atmel_tcb_channel {
|
||||||
|
u32 enabled;
|
||||||
|
u32 cmr;
|
||||||
|
u32 ra;
|
||||||
|
u32 rb;
|
||||||
|
u32 rc;
|
||||||
|
};
|
||||||
|
|
||||||
struct atmel_tcb_pwm_chip {
|
struct atmel_tcb_pwm_chip {
|
||||||
struct pwm_chip chip;
|
struct pwm_chip chip;
|
||||||
spinlock_t lock;
|
spinlock_t lock;
|
||||||
struct atmel_tc *tc;
|
struct atmel_tc *tc;
|
||||||
struct atmel_tcb_pwm_device *pwms[NPWM];
|
struct atmel_tcb_pwm_device *pwms[NPWM];
|
||||||
|
struct atmel_tcb_channel bkup[NPWM / 2];
|
||||||
};
|
};
|
||||||
|
|
||||||
static inline struct atmel_tcb_pwm_chip *to_tcb_chip(struct pwm_chip *chip)
|
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)
|
|||||||
* Use software trigger to apply the new setting.
|
* Use software trigger to apply the new setting.
|
||||||
* If both PWM devices in this group are disabled we stop the clock.
|
* If both PWM devices in this group are disabled we stop the clock.
|
||||||
*/
|
*/
|
||||||
if (!(cmr & (ATMEL_TC_ACPC | ATMEL_TC_BCPC)))
|
if (!(cmr & (ATMEL_TC_ACPC | ATMEL_TC_BCPC))) {
|
||||||
__raw_writel(ATMEL_TC_SWTRG | ATMEL_TC_CLKDIS,
|
__raw_writel(ATMEL_TC_SWTRG | ATMEL_TC_CLKDIS,
|
||||||
regs + ATMEL_TC_REG(group, CCR));
|
regs + ATMEL_TC_REG(group, CCR));
|
||||||
else
|
tcbpwmc->bkup[group].enabled = 1;
|
||||||
|
} else {
|
||||||
__raw_writel(ATMEL_TC_SWTRG, regs +
|
__raw_writel(ATMEL_TC_SWTRG, regs +
|
||||||
ATMEL_TC_REG(group, CCR));
|
ATMEL_TC_REG(group, CCR));
|
||||||
|
tcbpwmc->bkup[group].enabled = 0;
|
||||||
|
}
|
||||||
|
|
||||||
spin_unlock(&tcbpwmc->lock);
|
spin_unlock(&tcbpwmc->lock);
|
||||||
}
|
}
|
||||||
@@ -263,6 +275,7 @@ static int atmel_tcb_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm)
|
|||||||
/* Use software trigger to apply the new setting */
|
/* Use software trigger to apply the new setting */
|
||||||
__raw_writel(ATMEL_TC_CLKEN | ATMEL_TC_SWTRG,
|
__raw_writel(ATMEL_TC_CLKEN | ATMEL_TC_SWTRG,
|
||||||
regs + ATMEL_TC_REG(group, CCR));
|
regs + ATMEL_TC_REG(group, CCR));
|
||||||
|
tcbpwmc->bkup[group].enabled = 1;
|
||||||
spin_unlock(&tcbpwmc->lock);
|
spin_unlock(&tcbpwmc->lock);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -445,10 +458,56 @@ static const struct of_device_id atmel_tcb_pwm_dt_ids[] = {
|
|||||||
};
|
};
|
||||||
MODULE_DEVICE_TABLE(of, atmel_tcb_pwm_dt_ids);
|
MODULE_DEVICE_TABLE(of, atmel_tcb_pwm_dt_ids);
|
||||||
|
|
||||||
|
#ifdef CONFIG_PM_SLEEP
|
||||||
|
static int atmel_tcb_pwm_suspend(struct device *dev)
|
||||||
|
{
|
||||||
|
struct platform_device *pdev = to_platform_device(dev);
|
||||||
|
struct atmel_tcb_pwm_chip *tcbpwm = platform_get_drvdata(pdev);
|
||||||
|
void __iomem *base = tcbpwm->tc->regs;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < (NPWM / 2); i++) {
|
||||||
|
struct atmel_tcb_channel *chan = &tcbpwm->bkup[i];
|
||||||
|
|
||||||
|
chan->cmr = readl(base + ATMEL_TC_REG(i, CMR));
|
||||||
|
chan->ra = readl(base + ATMEL_TC_REG(i, RA));
|
||||||
|
chan->rb = readl(base + ATMEL_TC_REG(i, RB));
|
||||||
|
chan->rc = readl(base + ATMEL_TC_REG(i, RC));
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int atmel_tcb_pwm_resume(struct device *dev)
|
||||||
|
{
|
||||||
|
struct platform_device *pdev = to_platform_device(dev);
|
||||||
|
struct atmel_tcb_pwm_chip *tcbpwm = platform_get_drvdata(pdev);
|
||||||
|
void __iomem *base = tcbpwm->tc->regs;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < (NPWM / 2); i++) {
|
||||||
|
struct atmel_tcb_channel *chan = &tcbpwm->bkup[i];
|
||||||
|
|
||||||
|
writel(chan->cmr, base + ATMEL_TC_REG(i, CMR));
|
||||||
|
writel(chan->ra, base + ATMEL_TC_REG(i, RA));
|
||||||
|
writel(chan->rb, base + ATMEL_TC_REG(i, RB));
|
||||||
|
writel(chan->rc, base + ATMEL_TC_REG(i, RC));
|
||||||
|
if (chan->enabled) {
|
||||||
|
writel(ATMEL_TC_CLKEN | ATMEL_TC_SWTRG,
|
||||||
|
base + ATMEL_TC_REG(i, CCR));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static SIMPLE_DEV_PM_OPS(atmel_tcb_pwm_pm_ops, atmel_tcb_pwm_suspend,
|
||||||
|
atmel_tcb_pwm_resume);
|
||||||
|
|
||||||
static struct platform_driver atmel_tcb_pwm_driver = {
|
static struct platform_driver atmel_tcb_pwm_driver = {
|
||||||
.driver = {
|
.driver = {
|
||||||
.name = "atmel-tcb-pwm",
|
.name = "atmel-tcb-pwm",
|
||||||
.of_match_table = atmel_tcb_pwm_dt_ids,
|
.of_match_table = atmel_tcb_pwm_dt_ids,
|
||||||
|
.pm = &atmel_tcb_pwm_pm_ops,
|
||||||
},
|
},
|
||||||
.probe = atmel_tcb_pwm_probe,
|
.probe = atmel_tcb_pwm_probe,
|
||||||
.remove = atmel_tcb_pwm_remove,
|
.remove = atmel_tcb_pwm_remove,
|
||||||
|
|||||||
Reference in New Issue
Block a user