diff options
Diffstat (limited to 'drivers/pwm/pwm-tiehrpwm.c')
| -rw-r--r-- | drivers/pwm/pwm-tiehrpwm.c | 177 |
1 files changed, 162 insertions, 15 deletions
diff --git a/drivers/pwm/pwm-tiehrpwm.c b/drivers/pwm/pwm-tiehrpwm.c index d3c1dff0a0d..cb75133085a 100644 --- a/drivers/pwm/pwm-tiehrpwm.c +++ b/drivers/pwm/pwm-tiehrpwm.c @@ -25,6 +25,9 @@ #include <linux/err.h> #include <linux/clk.h> #include <linux/pm_runtime.h> +#include <linux/of_device.h> + +#include "pwm-tipwmss.h" /* EHRPWM registers and bits definitions */ @@ -109,12 +112,25 @@ #define NUM_PWM_CHANNEL 2 /* EHRPWM channels */ +struct ehrpwm_context { + u16 tbctl; + u16 tbprd; + u16 cmpa; + u16 cmpb; + u16 aqctla; + u16 aqctlb; + u16 aqsfrc; + u16 aqcsfrc; +}; + struct ehrpwm_pwm_chip { struct pwm_chip chip; unsigned int clk_rate; void __iomem *mmio_base; unsigned long period_cycles[NUM_PWM_CHANNEL]; enum pwm_polarity polarity[NUM_PWM_CHANNEL]; + struct clk *tbclk; + struct ehrpwm_context ctx; }; static inline struct ehrpwm_pwm_chip *to_ehrpwm_pwm_chip(struct pwm_chip *chip) @@ -122,12 +138,17 @@ static inline struct ehrpwm_pwm_chip *to_ehrpwm_pwm_chip(struct pwm_chip *chip) return container_of(chip, struct ehrpwm_pwm_chip, chip); } -static void ehrpwm_write(void *base, int offset, unsigned int val) +static inline u16 ehrpwm_read(void __iomem *base, int offset) +{ + return readw(base + offset); +} + +static inline void ehrpwm_write(void __iomem *base, int offset, unsigned int val) { writew(val & 0xFFFF, base + offset); } -static void ehrpwm_modify(void *base, int offset, +static void ehrpwm_modify(void __iomem *base, int offset, unsigned short mask, unsigned short val) { unsigned short regval; @@ -313,6 +334,7 @@ static int ehrpwm_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) { struct ehrpwm_pwm_chip *pc = to_ehrpwm_pwm_chip(chip); unsigned short aqcsfrc_val, aqcsfrc_mask; + int ret; /* Leave clock enabled on enabling PWM */ pm_runtime_get_sync(chip->dev); @@ -335,6 +357,14 @@ static int ehrpwm_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) /* Channels polarity can be configured from action qualifier module */ configure_polarity(pc, pwm->hwpwm); + /* Enable TBCLK before enabling PWM device */ + ret = clk_enable(pc->tbclk); + if (ret) { + dev_err(chip->dev, "Failed to enable TBCLK for %s\n", + dev_name(pc->chip.dev)); + return ret; + } + /* Enable time counter for free_run */ ehrpwm_modify(pc->mmio_base, TBCTL, TBCTL_RUN_MASK, TBCTL_FREE_RUN); return 0; @@ -363,6 +393,9 @@ static void ehrpwm_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) ehrpwm_modify(pc->mmio_base, AQCSFRC, aqcsfrc_mask, aqcsfrc_val); + /* Disabling TBCLK on PWM disable */ + clk_disable(pc->tbclk); + /* Stop Time base counter */ ehrpwm_modify(pc->mmio_base, TBCTL, TBCTL_RUN_MASK, TBCTL_STOP_NEXT); @@ -392,18 +425,23 @@ static const struct pwm_ops ehrpwm_pwm_ops = { .owner = THIS_MODULE, }; -static int __devinit ehrpwm_pwm_probe(struct platform_device *pdev) +static const struct of_device_id ehrpwm_of_match[] = { + { .compatible = "ti,am33xx-ehrpwm" }, + {}, +}; +MODULE_DEVICE_TABLE(of, ehrpwm_of_match); + +static int ehrpwm_pwm_probe(struct platform_device *pdev) { int ret; struct resource *r; struct clk *clk; struct ehrpwm_pwm_chip *pc; + u16 status; pc = devm_kzalloc(&pdev->dev, sizeof(*pc), GFP_KERNEL); - if (!pc) { - dev_err(&pdev->dev, "failed to allocate memory\n"); + if (!pc) return -ENOMEM; - } clk = devm_clk_get(&pdev->dev, "fck"); if (IS_ERR(clk)) { @@ -419,18 +457,28 @@ static int __devinit ehrpwm_pwm_probe(struct platform_device *pdev) pc->chip.dev = &pdev->dev; pc->chip.ops = &ehrpwm_pwm_ops; + pc->chip.of_xlate = of_pwm_xlate_with_flags; + pc->chip.of_pwm_n_cells = 3; pc->chip.base = -1; pc->chip.npwm = NUM_PWM_CHANNEL; r = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!r) { - dev_err(&pdev->dev, "no memory resource defined\n"); - return -ENODEV; + pc->mmio_base = devm_ioremap_resource(&pdev->dev, r); + if (IS_ERR(pc->mmio_base)) + return PTR_ERR(pc->mmio_base); + + /* Acquire tbclk for Time Base EHRPWM submodule */ + pc->tbclk = devm_clk_get(&pdev->dev, "tbclk"); + if (IS_ERR(pc->tbclk)) { + dev_err(&pdev->dev, "Failed to get tbclk\n"); + return PTR_ERR(pc->tbclk); } - pc->mmio_base = devm_request_and_ioremap(&pdev->dev, r); - if (!pc->mmio_base) - return -EADDRNOTAVAIL; + ret = clk_prepare(pc->tbclk); + if (ret < 0) { + dev_err(&pdev->dev, "clk_prepare() failed: %d\n", ret); + return ret; + } ret = pwmchip_add(&pc->chip); if (ret < 0) { @@ -439,25 +487,124 @@ static int __devinit ehrpwm_pwm_probe(struct platform_device *pdev) } pm_runtime_enable(&pdev->dev); + pm_runtime_get_sync(&pdev->dev); + + status = pwmss_submodule_state_change(pdev->dev.parent, + PWMSS_EPWMCLK_EN); + if (!(status & PWMSS_EPWMCLK_EN_ACK)) { + dev_err(&pdev->dev, "PWMSS config space clock enable failed\n"); + ret = -EINVAL; + goto pwmss_clk_failure; + } + + pm_runtime_put_sync(&pdev->dev); + platform_set_drvdata(pdev, pc); return 0; + +pwmss_clk_failure: + pm_runtime_put_sync(&pdev->dev); + pm_runtime_disable(&pdev->dev); + pwmchip_remove(&pc->chip); + clk_unprepare(pc->tbclk); + return ret; } -static int __devexit ehrpwm_pwm_remove(struct platform_device *pdev) +static int ehrpwm_pwm_remove(struct platform_device *pdev) { struct ehrpwm_pwm_chip *pc = platform_get_drvdata(pdev); + clk_unprepare(pc->tbclk); + + pm_runtime_get_sync(&pdev->dev); + /* + * Due to hardware misbehaviour, acknowledge of the stop_req + * is missing. Hence checking of the status bit skipped. + */ + pwmss_submodule_state_change(pdev->dev.parent, PWMSS_EPWMCLK_STOP_REQ); + pm_runtime_put_sync(&pdev->dev); + pm_runtime_put_sync(&pdev->dev); pm_runtime_disable(&pdev->dev); return pwmchip_remove(&pc->chip); } +#ifdef CONFIG_PM_SLEEP +static void ehrpwm_pwm_save_context(struct ehrpwm_pwm_chip *pc) +{ + pm_runtime_get_sync(pc->chip.dev); + pc->ctx.tbctl = ehrpwm_read(pc->mmio_base, TBCTL); + pc->ctx.tbprd = ehrpwm_read(pc->mmio_base, TBPRD); + pc->ctx.cmpa = ehrpwm_read(pc->mmio_base, CMPA); + pc->ctx.cmpb = ehrpwm_read(pc->mmio_base, CMPB); + pc->ctx.aqctla = ehrpwm_read(pc->mmio_base, AQCTLA); + pc->ctx.aqctlb = ehrpwm_read(pc->mmio_base, AQCTLB); + pc->ctx.aqsfrc = ehrpwm_read(pc->mmio_base, AQSFRC); + pc->ctx.aqcsfrc = ehrpwm_read(pc->mmio_base, AQCSFRC); + pm_runtime_put_sync(pc->chip.dev); +} + +static void ehrpwm_pwm_restore_context(struct ehrpwm_pwm_chip *pc) +{ + ehrpwm_write(pc->mmio_base, TBPRD, pc->ctx.tbprd); + ehrpwm_write(pc->mmio_base, CMPA, pc->ctx.cmpa); + ehrpwm_write(pc->mmio_base, CMPB, pc->ctx.cmpb); + ehrpwm_write(pc->mmio_base, AQCTLA, pc->ctx.aqctla); + ehrpwm_write(pc->mmio_base, AQCTLB, pc->ctx.aqctlb); + ehrpwm_write(pc->mmio_base, AQSFRC, pc->ctx.aqsfrc); + ehrpwm_write(pc->mmio_base, AQCSFRC, pc->ctx.aqcsfrc); + ehrpwm_write(pc->mmio_base, TBCTL, pc->ctx.tbctl); +} + +static int ehrpwm_pwm_suspend(struct device *dev) +{ + struct ehrpwm_pwm_chip *pc = dev_get_drvdata(dev); + int i; + + ehrpwm_pwm_save_context(pc); + for (i = 0; i < pc->chip.npwm; i++) { + struct pwm_device *pwm = &pc->chip.pwms[i]; + + if (!test_bit(PWMF_ENABLED, &pwm->flags)) + continue; + + /* Disable explicitly if PWM is running */ + pm_runtime_put_sync(dev); + } + return 0; +} + +static int ehrpwm_pwm_resume(struct device *dev) +{ + struct ehrpwm_pwm_chip *pc = dev_get_drvdata(dev); + int i; + + for (i = 0; i < pc->chip.npwm; i++) { + struct pwm_device *pwm = &pc->chip.pwms[i]; + + if (!test_bit(PWMF_ENABLED, &pwm->flags)) + continue; + + /* Enable explicitly if PWM was running */ + pm_runtime_get_sync(dev); + } + ehrpwm_pwm_restore_context(pc); + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(ehrpwm_pwm_pm_ops, ehrpwm_pwm_suspend, + ehrpwm_pwm_resume); + static struct platform_driver ehrpwm_pwm_driver = { .driver = { - .name = "ehrpwm", + .name = "ehrpwm", + .owner = THIS_MODULE, + .of_match_table = ehrpwm_of_match, + .pm = &ehrpwm_pwm_pm_ops, }, .probe = ehrpwm_pwm_probe, - .remove = __devexit_p(ehrpwm_pwm_remove), + .remove = ehrpwm_pwm_remove, }; module_platform_driver(ehrpwm_pwm_driver); |
