From 0aa0869c3c9b10338dd92a20fa4a9b6959f177b5 Mon Sep 17 00:00:00 2001 From: "Philip, Avinash" Date: Tue, 24 Jul 2012 19:35:32 +0530 Subject: pwm: Add support for configuring the PWM polarity Some hardware supports inverting the polarity of the PWM signal. This commit adds support to the PWM framework to allow users of the PWM API to configure the polarity. Note that in order to reduce complexity, changing the polarity of a PWM signal is only allowed while the PWM is disabled. A practical example where this can prove useful is to simulate inversion of the duty cycle. While inversion of polarity and duty cycle are not exactly the same, the differences for most use-cases are negligible. Signed-off-by: Philip, Avinash Signed-off-by: Thierry Reding --- drivers/pwm/core.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) (limited to 'drivers/pwm') diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c index c6e05078d3a..85592e97ef2 100644 --- a/drivers/pwm/core.c +++ b/drivers/pwm/core.c @@ -378,6 +378,28 @@ int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns) } EXPORT_SYMBOL_GPL(pwm_config); +/** + * pwm_set_polarity() - configure the polarity of a PWM signal + * @pwm: PWM device + * @polarity: new polarity of the PWM signal + * + * Note that the polarity cannot be configured while the PWM device is enabled + */ +int pwm_set_polarity(struct pwm_device *pwm, enum pwm_polarity polarity) +{ + if (!pwm || !pwm->chip->ops) + return -EINVAL; + + if (!pwm->chip->ops->set_polarity) + return -ENOSYS; + + if (test_bit(PWMF_ENABLED, &pwm->flags)) + return -EBUSY; + + return pwm->chip->ops->set_polarity(pwm->chip, pwm, polarity); +} +EXPORT_SYMBOL_GPL(pwm_set_polarity); + /** * pwm_enable() - start a PWM output toggling * @pwm: PWM device -- cgit v1.2.3-70-g09d2 From 6354316dbe5a13b25bea15d7ffc891be025eb267 Mon Sep 17 00:00:00 2001 From: Alexandre Courbot Date: Wed, 1 Aug 2012 19:20:58 +0900 Subject: pwm: add devm_pwm_get() and devm_pwm_put() Add resource managed variants of pwm_get() and pwm_put() for convenience. Code is largely inspired by the equivalent devm functions of the regulator framework. Signed-off-by: Alexandre Courbot Signed-off-by: Thierry Reding --- Documentation/driver-model/devres.txt | 4 +++ Documentation/pwm.txt | 3 +- drivers/pwm/core.c | 58 +++++++++++++++++++++++++++++++++++ include/linux/pwm.h | 3 ++ 4 files changed, 67 insertions(+), 1 deletion(-) (limited to 'drivers/pwm') diff --git a/Documentation/driver-model/devres.txt b/Documentation/driver-model/devres.txt index 950856bd2e3..43cff70465a 100644 --- a/Documentation/driver-model/devres.txt +++ b/Documentation/driver-model/devres.txt @@ -284,3 +284,7 @@ CLOCK PINCTRL devm_pinctrl_get() devm_pinctrl_put() + +PWM + devm_pwm_get() + devm_pwm_put() diff --git a/Documentation/pwm.txt b/Documentation/pwm.txt index 554290ebab9..7d2b4c9b544 100644 --- a/Documentation/pwm.txt +++ b/Documentation/pwm.txt @@ -36,7 +36,8 @@ Legacy users can request a PWM device using pwm_request() and free it after usage with pwm_free(). New users should use the pwm_get() function and pass to it the consumer -device or a consumer name. pwm_put() is used to free the PWM device. +device or a consumer name. pwm_put() is used to free the PWM device. Managed +variants of these functions, devm_pwm_get() and devm_pwm_put(), also exist. After being requested a PWM has to be configured using: diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c index 85592e97ef2..92b1782d0d8 100644 --- a/drivers/pwm/core.c +++ b/drivers/pwm/core.c @@ -646,6 +646,64 @@ out: } EXPORT_SYMBOL_GPL(pwm_put); +static void devm_pwm_release(struct device *dev, void *res) +{ + pwm_put(*(struct pwm_device **)res); +} + +/** + * devm_pwm_get() - resource managed pwm_get() + * @dev: device for PWM consumer + * @con_id: consumer name + * + * This function performs like pwm_get() but the acquired PWM device will + * automatically be released on driver detach. + */ +struct pwm_device *devm_pwm_get(struct device *dev, const char *con_id) +{ + struct pwm_device **ptr, *pwm; + + ptr = devres_alloc(devm_pwm_release, sizeof(**ptr), GFP_KERNEL); + if (!ptr) + return ERR_PTR(-ENOMEM); + + pwm = pwm_get(dev, con_id); + if (!IS_ERR(pwm)) { + *ptr = pwm; + devres_add(dev, ptr); + } else { + devres_free(ptr); + } + + return pwm; +} +EXPORT_SYMBOL_GPL(devm_pwm_get); + +static int devm_pwm_match(struct device *dev, void *res, void *data) +{ + struct pwm_device **p = res; + + if (WARN_ON(!p || !*p)) + return 0; + + return *p == data; +} + +/** + * devm_pwm_put() - resource managed pwm_put() + * @dev: device for PWM consumer + * @pwm: PWM device + * + * Release a PWM previously allocated using devm_pwm_get(). Calling this + * function is usually not needed because devm-allocated resources are + * automatically released on driver detach. + */ +void devm_pwm_put(struct device *dev, struct pwm_device *pwm) +{ + WARN_ON(devres_release(dev, devm_pwm_release, devm_pwm_match, pwm)); +} +EXPORT_SYMBOL_GPL(devm_pwm_put); + #ifdef CONFIG_DEBUG_FS static void pwm_dbg_show(struct pwm_chip *chip, struct seq_file *s) { diff --git a/include/linux/pwm.h b/include/linux/pwm.h index 354764cf5f7..40c76431895 100644 --- a/include/linux/pwm.h +++ b/include/linux/pwm.h @@ -148,6 +148,9 @@ struct pwm_device *pwm_request_from_chip(struct pwm_chip *chip, struct pwm_device *pwm_get(struct device *dev, const char *consumer); void pwm_put(struct pwm_device *pwm); +struct pwm_device *devm_pwm_get(struct device *dev, const char *consumer); +void devm_pwm_put(struct device *dev, struct pwm_device *pwm); + struct pwm_lookup { struct list_head list; const char *provider; -- cgit v1.2.3-70-g09d2 From 454870a44b0687675180506b7774fb559d610675 Mon Sep 17 00:00:00 2001 From: "Philip, Avinash" Date: Thu, 6 Sep 2012 10:40:02 +0530 Subject: pwm: pwm-tiecap: Add support for configuring polarity of PWM ECAP APWM hardware supports polarity configuration of PWM output. This commit adds support for polarity configuration of ECAP APWM. Signed-off-by: Philip, Avinash Signed-off-by: Thierry Reding --- drivers/pwm/pwm-tiecap.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) (limited to 'drivers/pwm') diff --git a/drivers/pwm/pwm-tiecap.c b/drivers/pwm/pwm-tiecap.c index 4b6688909fe..081471fbb09 100644 --- a/drivers/pwm/pwm-tiecap.c +++ b/drivers/pwm/pwm-tiecap.c @@ -32,6 +32,7 @@ #define CAP3 0x10 #define CAP4 0x14 #define ECCTL2 0x2A +#define ECCTL2_APWM_POL_LOW BIT(10) #define ECCTL2_APWM_MODE BIT(9) #define ECCTL2_SYNC_SEL_DISA (BIT(7) | BIT(6)) #define ECCTL2_TSCTR_FREERUN BIT(4) @@ -111,6 +112,26 @@ static int ecap_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, return 0; } +static int ecap_pwm_set_polarity(struct pwm_chip *chip, struct pwm_device *pwm, + enum pwm_polarity polarity) +{ + struct ecap_pwm_chip *pc = to_ecap_pwm_chip(chip); + unsigned short reg_val; + + pm_runtime_get_sync(pc->chip.dev); + reg_val = readw(pc->mmio_base + ECCTL2); + if (polarity == PWM_POLARITY_INVERSED) + /* Duty cycle defines LOW period of PWM */ + reg_val |= ECCTL2_APWM_POL_LOW; + else + /* Duty cycle defines HIGH period of PWM */ + reg_val &= ~ECCTL2_APWM_POL_LOW; + + writew(reg_val, pc->mmio_base + ECCTL2); + pm_runtime_put_sync(pc->chip.dev); + return 0; +} + static int ecap_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) { struct ecap_pwm_chip *pc = to_ecap_pwm_chip(chip); @@ -157,6 +178,7 @@ static void ecap_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) static const struct pwm_ops ecap_pwm_ops = { .free = ecap_pwm_free, .config = ecap_pwm_config, + .set_polarity = ecap_pwm_set_polarity, .enable = ecap_pwm_enable, .disable = ecap_pwm_disable, .owner = THIS_MODULE, -- cgit v1.2.3-70-g09d2 From daa5629b21a1e59ed0ef9515a9e791d2f75cc5ca Mon Sep 17 00:00:00 2001 From: "Philip, Avinash" Date: Thu, 6 Sep 2012 10:40:03 +0530 Subject: pwm: pwm-tiehrpwm: Add support for configuring polarity of PWM EHRPWM hardware supports polarity configuration of PWM output. However configuration of polarity done in hardware only in .enable() to ensure PWM output present only after enabling PWM. This commit adds support for polarity configuration for EHRPWM. When being here, remove configuring of polarity during .config() and do it explicitly from .set_polarity(). Signed-off-by: Philip, Avinash Signed-off-by: Thierry Reding --- drivers/pwm/pwm-tiehrpwm.c | 73 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 51 insertions(+), 22 deletions(-) (limited to 'drivers/pwm') diff --git a/drivers/pwm/pwm-tiehrpwm.c b/drivers/pwm/pwm-tiehrpwm.c index b1996bcd5b7..caf00feadc6 100644 --- a/drivers/pwm/pwm-tiehrpwm.c +++ b/drivers/pwm/pwm-tiehrpwm.c @@ -81,6 +81,15 @@ #define AQCTL_ZRO_FRCHIGH BIT(1) #define AQCTL_ZRO_FRCTOGGLE (BIT(1) | BIT(0)) +#define AQCTL_CHANA_POLNORMAL (AQCTL_CAU_FRCLOW | AQCTL_PRD_FRCHIGH | \ + AQCTL_ZRO_FRCHIGH) +#define AQCTL_CHANA_POLINVERSED (AQCTL_CAU_FRCHIGH | AQCTL_PRD_FRCLOW | \ + AQCTL_ZRO_FRCLOW) +#define AQCTL_CHANB_POLNORMAL (AQCTL_CBU_FRCLOW | AQCTL_PRD_FRCHIGH | \ + AQCTL_ZRO_FRCHIGH) +#define AQCTL_CHANB_POLINVERSED (AQCTL_CBU_FRCHIGH | AQCTL_PRD_FRCLOW | \ + AQCTL_ZRO_FRCLOW) + #define AQSFRC_RLDCSF_MASK (BIT(7) | BIT(6)) #define AQSFRC_RLDCSF_ZRO 0 #define AQSFRC_RLDCSF_PRD BIT(6) @@ -105,6 +114,7 @@ struct ehrpwm_pwm_chip { unsigned int clk_rate; void __iomem *mmio_base; unsigned long period_cycles[NUM_PWM_CHANNEL]; + enum pwm_polarity polarity[NUM_PWM_CHANNEL]; }; static inline struct ehrpwm_pwm_chip *to_ehrpwm_pwm_chip(struct pwm_chip *chip) @@ -165,39 +175,37 @@ static int set_prescale_div(unsigned long rqst_prescaler, return 1; } -static void configure_chans(struct ehrpwm_pwm_chip *pc, int chan, - unsigned long duty_cycles) +static void configure_polarity(struct ehrpwm_pwm_chip *pc, int chan) { - int cmp_reg, aqctl_reg; + int aqctl_reg; unsigned short aqctl_val, aqctl_mask; /* - * Channels can be configured from action qualifier module. - * Channel 0 configured with compare A register and for - * up-counter mode. - * Channel 1 configured with compare B register and for - * up-counter mode. + * 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 (chan == 1) { aqctl_reg = AQCTLB; - cmp_reg = CMPB; - /* Configure PWM Low from compare B value */ - aqctl_val = AQCTL_CBU_FRCLOW; aqctl_mask = AQCTL_CBU_MASK; + + if (pc->polarity[chan] == PWM_POLARITY_INVERSED) + aqctl_val = AQCTL_CHANB_POLINVERSED; + else + aqctl_val = AQCTL_CHANB_POLNORMAL; } else { - cmp_reg = CMPA; aqctl_reg = AQCTLA; - /* Configure PWM Low from compare A value*/ - aqctl_val = AQCTL_CAU_FRCLOW; aqctl_mask = AQCTL_CAU_MASK; + + if (pc->polarity[chan] == PWM_POLARITY_INVERSED) + aqctl_val = AQCTL_CHANA_POLINVERSED; + else + aqctl_val = AQCTL_CHANA_POLNORMAL; } - /* Configure PWM High from period value and zero value */ - aqctl_val |= AQCTL_PRD_FRCHIGH | AQCTL_ZRO_FRCHIGH; aqctl_mask |= AQCTL_PRD_MASK | AQCTL_ZRO_MASK; - ehrpwm_modify(pc->mmio_base, aqctl_reg, aqctl_mask, aqctl_val); - - ehrpwm_write(pc->mmio_base, cmp_reg, duty_cycles); + ehrpwm_modify(pc->mmio_base, aqctl_reg, aqctl_mask, aqctl_val); } /* @@ -211,7 +219,7 @@ static int ehrpwm_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, unsigned long long c; unsigned long period_cycles, duty_cycles; unsigned short ps_divval, tb_divval; - int i; + int i, cmp_reg; if (period_ns < 0 || duty_ns < 0 || period_ns > NSEC_PER_SEC) return -ERANGE; @@ -278,12 +286,29 @@ static int ehrpwm_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, ehrpwm_modify(pc->mmio_base, TBCTL, TBCTL_CTRMODE_MASK, TBCTL_CTRMODE_UP); - /* Configure the channel for duty cycle */ - configure_chans(pc, pwm->hwpwm, duty_cycles); + if (pwm->hwpwm == 1) + /* Channel 1 configured with compare B register */ + cmp_reg = CMPB; + else + /* Channel 0 configured with compare A register */ + cmp_reg = CMPA; + + ehrpwm_write(pc->mmio_base, cmp_reg, duty_cycles); + pm_runtime_put_sync(chip->dev); return 0; } +static int ehrpwm_pwm_set_polarity(struct pwm_chip *chip, + struct pwm_device *pwm, enum pwm_polarity polarity) +{ + struct ehrpwm_pwm_chip *pc = to_ehrpwm_pwm_chip(chip); + + /* Configuration of polarity in hardware delayed, do at enable */ + pc->polarity[pwm->hwpwm] = polarity; + return 0; +} + static int ehrpwm_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) { struct ehrpwm_pwm_chip *pc = to_ehrpwm_pwm_chip(chip); @@ -307,6 +332,9 @@ static int ehrpwm_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) ehrpwm_modify(pc->mmio_base, AQCSFRC, aqcsfrc_mask, aqcsfrc_val); + /* Channels polarity can be configured from action qualifier module */ + configure_polarity(pc, pwm->hwpwm); + /* Enable time counter for free_run */ ehrpwm_modify(pc->mmio_base, TBCTL, TBCTL_RUN_MASK, TBCTL_FREE_RUN); return 0; @@ -358,6 +386,7 @@ static void ehrpwm_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) static const struct pwm_ops ehrpwm_pwm_ops = { .free = ehrpwm_pwm_free, .config = ehrpwm_pwm_config, + .set_polarity = ehrpwm_pwm_set_polarity, .enable = ehrpwm_pwm_enable, .disable = ehrpwm_pwm_disable, .owner = THIS_MODULE, -- cgit v1.2.3-70-g09d2 From 19e73333236a6115617f8ffb4cc290bdb6f2865a Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Tue, 3 Jul 2012 17:28:14 +0200 Subject: pwm: i.MX: factor out SoC specific functions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To cleanup the code and to make it easier to support different SoCs. Signed-off-by: Sascha Hauer Reviewed-by: Shawn Guo Reviewed-by: Benoît Thébaudeau Signed-off-by: Thierry Reding --- drivers/pwm/pwm-imx.c | 146 ++++++++++++++++++++++++++++---------------------- 1 file changed, 83 insertions(+), 63 deletions(-) (limited to 'drivers/pwm') diff --git a/drivers/pwm/pwm-imx.c b/drivers/pwm/pwm-imx.c index 2a0b3533397..8b7f01e0a10 100644 --- a/drivers/pwm/pwm-imx.c +++ b/drivers/pwm/pwm-imx.c @@ -46,81 +46,96 @@ struct imx_chip { void __iomem *mmio_base; struct pwm_chip chip; + + int (*config)(struct pwm_chip *chip, + struct pwm_device *pwm, int duty_ns, int period_ns); }; #define to_imx_chip(chip) container_of(chip, struct imx_chip, chip) -static int imx_pwm_config(struct pwm_chip *chip, +static int imx_pwm_config_v1(struct pwm_chip *chip, struct pwm_device *pwm, int duty_ns, int period_ns) { struct imx_chip *imx = to_imx_chip(chip); - if (!(cpu_is_mx1() || cpu_is_mx21())) { - unsigned long long c; - unsigned long period_cycles, duty_cycles, prescale; - u32 cr; - - c = clk_get_rate(imx->clk); - c = c * period_ns; - do_div(c, 1000000000); - period_cycles = c; - - prescale = period_cycles / 0x10000 + 1; - - period_cycles /= prescale; - c = (unsigned long long)period_cycles * duty_ns; - do_div(c, period_ns); - duty_cycles = c; - - /* - * according to imx pwm RM, the real period value should be - * PERIOD value in PWMPR plus 2. - */ - if (period_cycles > 2) - period_cycles -= 2; - else - period_cycles = 0; - - writel(duty_cycles, imx->mmio_base + MX3_PWMSAR); - writel(period_cycles, imx->mmio_base + MX3_PWMPR); - - cr = MX3_PWMCR_PRESCALER(prescale) | - MX3_PWMCR_DOZEEN | MX3_PWMCR_WAITEN | - MX3_PWMCR_DBGEN | MX3_PWMCR_EN; - - if (cpu_is_mx25()) - cr |= MX3_PWMCR_CLKSRC_IPG; - else - cr |= MX3_PWMCR_CLKSRC_IPG_HIGH; - - writel(cr, imx->mmio_base + MX3_PWMCR); - } else if (cpu_is_mx1() || cpu_is_mx21()) { - /* The PWM subsystem allows for exact frequencies. However, - * I cannot connect a scope on my device to the PWM line and - * thus cannot provide the program the PWM controller - * exactly. Instead, I'm relying on the fact that the - * Bootloader (u-boot or WinCE+haret) has programmed the PWM - * function group already. So I'll just modify the PWM sample - * register to follow the ratio of duty_ns vs. period_ns - * accordingly. - * - * This is good enough for programming the brightness of - * the LCD backlight. - * - * The real implementation would divide PERCLK[0] first by - * both the prescaler (/1 .. /128) and then by CLKSEL - * (/2 .. /16). - */ - u32 max = readl(imx->mmio_base + MX1_PWMP); - u32 p = max * duty_ns / period_ns; - writel(max - p, imx->mmio_base + MX1_PWMS); - } else { - BUG(); - } + /* + * The PWM subsystem allows for exact frequencies. However, + * I cannot connect a scope on my device to the PWM line and + * thus cannot provide the program the PWM controller + * exactly. Instead, I'm relying on the fact that the + * Bootloader (u-boot or WinCE+haret) has programmed the PWM + * function group already. So I'll just modify the PWM sample + * register to follow the ratio of duty_ns vs. period_ns + * accordingly. + * + * This is good enough for programming the brightness of + * the LCD backlight. + * + * The real implementation would divide PERCLK[0] first by + * both the prescaler (/1 .. /128) and then by CLKSEL + * (/2 .. /16). + */ + u32 max = readl(imx->mmio_base + MX1_PWMP); + u32 p = max * duty_ns / period_ns; + writel(max - p, imx->mmio_base + MX1_PWMS); + + return 0; +} + +static int imx_pwm_config_v2(struct pwm_chip *chip, + struct pwm_device *pwm, int duty_ns, int period_ns) +{ + struct imx_chip *imx = to_imx_chip(chip); + unsigned long long c; + unsigned long period_cycles, duty_cycles, prescale; + u32 cr; + + c = clk_get_rate(imx->clk); + c = c * period_ns; + do_div(c, 1000000000); + period_cycles = c; + + prescale = period_cycles / 0x10000 + 1; + + period_cycles /= prescale; + c = (unsigned long long)period_cycles * duty_ns; + do_div(c, period_ns); + duty_cycles = c; + + /* + * according to imx pwm RM, the real period value should be + * PERIOD value in PWMPR plus 2. + */ + if (period_cycles > 2) + period_cycles -= 2; + else + period_cycles = 0; + + writel(duty_cycles, imx->mmio_base + MX3_PWMSAR); + writel(period_cycles, imx->mmio_base + MX3_PWMPR); + + cr = MX3_PWMCR_PRESCALER(prescale) | + MX3_PWMCR_DOZEEN | MX3_PWMCR_WAITEN | + MX3_PWMCR_DBGEN | MX3_PWMCR_EN; + + if (cpu_is_mx25()) + cr |= MX3_PWMCR_CLKSRC_IPG; + else + cr |= MX3_PWMCR_CLKSRC_IPG_HIGH; + + writel(cr, imx->mmio_base + MX3_PWMCR); return 0; } +static int imx_pwm_config(struct pwm_chip *chip, + struct pwm_device *pwm, int duty_ns, int period_ns) +{ + struct imx_chip *imx = to_imx_chip(chip); + + return imx->config(chip, pwm, duty_ns, period_ns); +} + static int imx_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) { struct imx_chip *imx = to_imx_chip(chip); @@ -187,6 +202,11 @@ static int __devinit imx_pwm_probe(struct platform_device *pdev) if (imx->mmio_base == NULL) return -EADDRNOTAVAIL; + if (cpu_is_mx1() || cpu_is_mx21()) + imx->config = imx_pwm_config_v1; + else + imx->config = imx_pwm_config_v2; + ret = pwmchip_add(&imx->chip); if (ret < 0) return ret; -- cgit v1.2.3-70-g09d2 From 140827c148f2f3a95fdcec5310f6cd980139b383 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Tue, 28 Aug 2012 09:12:01 +0200 Subject: pwm: i.MX: remove unnecessary if in pwm_[en|dis]able MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The PWM core makes sure that pwm_enable/disable are called only once. Still keep the enabled state since we will need it in pwm_config. Signed-off-by: Sascha Hauer Reviewed-by: Shawn Guo Reviewed-by: Benoît Thébaudeau Signed-off-by: Thierry Reding --- drivers/pwm/pwm-imx.c | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) (limited to 'drivers/pwm') diff --git a/drivers/pwm/pwm-imx.c b/drivers/pwm/pwm-imx.c index 8b7f01e0a10..5d426ffe6a3 100644 --- a/drivers/pwm/pwm-imx.c +++ b/drivers/pwm/pwm-imx.c @@ -42,7 +42,7 @@ struct imx_chip { struct clk *clk; - int clk_enabled; + int enabled; void __iomem *mmio_base; struct pwm_chip chip; @@ -139,14 +139,15 @@ static int imx_pwm_config(struct pwm_chip *chip, static int imx_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) { struct imx_chip *imx = to_imx_chip(chip); - int rc = 0; + int ret; - if (!imx->clk_enabled) { - rc = clk_prepare_enable(imx->clk); - if (!rc) - imx->clk_enabled = 1; - } - return rc; + ret = clk_prepare_enable(imx->clk); + if (ret) + return ret; + + imx->enabled = 1; + + return 0; } static void imx_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) @@ -155,10 +156,8 @@ static void imx_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) writel(0, imx->mmio_base + MX3_PWMCR); - if (imx->clk_enabled) { - clk_disable_unprepare(imx->clk); - imx->clk_enabled = 0; - } + clk_disable_unprepare(imx->clk); + imx->enabled = 0; } static struct pwm_ops imx_pwm_ops = { @@ -190,8 +189,6 @@ static int __devinit imx_pwm_probe(struct platform_device *pdev) imx->chip.base = -1; imx->chip.npwm = 1; - imx->clk_enabled = 0; - r = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (r == NULL) { dev_err(&pdev->dev, "no memory resource defined\n"); -- cgit v1.2.3-70-g09d2 From 66ad6a613abeeeabc5217a0498fae63205e8ddb8 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Tue, 28 Aug 2012 11:39:25 +0200 Subject: pwm: i.MX: add functions to enable/disable pwm. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We used to enable/disable the PWM only by switching the clock on or off. Instead, use the dedicated register bits. These differ on different SoCs, so introduce a SoC specific function for this. Signed-off-by: Sascha Hauer Reviewed-by: Shawn Guo Reviewed-by: Benoît Thébaudeau Signed-off-by: Thierry Reding --- drivers/pwm/pwm-imx.c | 48 ++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 4 deletions(-) (limited to 'drivers/pwm') diff --git a/drivers/pwm/pwm-imx.c b/drivers/pwm/pwm-imx.c index 5d426ffe6a3..13d4d22f12c 100644 --- a/drivers/pwm/pwm-imx.c +++ b/drivers/pwm/pwm-imx.c @@ -25,6 +25,7 @@ #define MX1_PWMS 0x04 /* PWM Sample Register */ #define MX1_PWMP 0x08 /* PWM Period Register */ +#define MX1_PWMC_EN (1 << 4) /* i.MX27, i.MX31, i.MX35 share the same PWM function block: */ @@ -49,6 +50,7 @@ struct imx_chip { int (*config)(struct pwm_chip *chip, struct pwm_device *pwm, int duty_ns, int period_ns); + void (*set_enable)(struct pwm_chip *chip, bool enable); }; #define to_imx_chip(chip) container_of(chip, struct imx_chip, chip) @@ -82,6 +84,21 @@ static int imx_pwm_config_v1(struct pwm_chip *chip, return 0; } +static void imx_pwm_set_enable_v1(struct pwm_chip *chip, bool enable) +{ + struct imx_chip *imx = to_imx_chip(chip); + u32 val; + + val = readl(imx->mmio_base + MX1_PWMC); + + if (enable) + val |= MX1_PWMC_EN; + else + val &= ~MX1_PWMC_EN; + + writel(val, imx->mmio_base + MX1_PWMC); +} + static int imx_pwm_config_v2(struct pwm_chip *chip, struct pwm_device *pwm, int duty_ns, int period_ns) { @@ -116,7 +133,10 @@ static int imx_pwm_config_v2(struct pwm_chip *chip, cr = MX3_PWMCR_PRESCALER(prescale) | MX3_PWMCR_DOZEEN | MX3_PWMCR_WAITEN | - MX3_PWMCR_DBGEN | MX3_PWMCR_EN; + MX3_PWMCR_DBGEN; + + if (imx->enabled) + cr |= MX3_PWMCR_EN; if (cpu_is_mx25()) cr |= MX3_PWMCR_CLKSRC_IPG; @@ -128,6 +148,21 @@ static int imx_pwm_config_v2(struct pwm_chip *chip, return 0; } +static void imx_pwm_set_enable_v2(struct pwm_chip *chip, bool enable) +{ + struct imx_chip *imx = to_imx_chip(chip); + u32 val; + + val = readl(imx->mmio_base + MX3_PWMCR); + + if (enable) + val |= MX3_PWMCR_EN; + else + val &= ~MX3_PWMCR_EN; + + writel(val, imx->mmio_base + MX3_PWMCR); +} + static int imx_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, int duty_ns, int period_ns) { @@ -145,6 +180,8 @@ static int imx_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) if (ret) return ret; + imx->set_enable(chip, true); + imx->enabled = 1; return 0; @@ -154,7 +191,7 @@ static void imx_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) { struct imx_chip *imx = to_imx_chip(chip); - writel(0, imx->mmio_base + MX3_PWMCR); + imx->set_enable(chip, false); clk_disable_unprepare(imx->clk); imx->enabled = 0; @@ -199,10 +236,13 @@ static int __devinit imx_pwm_probe(struct platform_device *pdev) if (imx->mmio_base == NULL) return -EADDRNOTAVAIL; - if (cpu_is_mx1() || cpu_is_mx21()) + if (cpu_is_mx1() || cpu_is_mx21()) { imx->config = imx_pwm_config_v1; - else + imx->set_enable = imx_pwm_set_enable_v1; + } else { imx->config = imx_pwm_config_v2; + imx->set_enable = imx_pwm_set_enable_v2; + } ret = pwmchip_add(&imx->chip); if (ret < 0) -- cgit v1.2.3-70-g09d2 From 208d038f4792b5ca0dc76deebbc0ae13b4f1a744 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Tue, 28 Aug 2012 08:27:40 +0200 Subject: pwm: i.MX: Use module_platform_driver MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Sascha Hauer Reviewed-by: Shawn Guo Reviewed-by: Benoît Thébaudeau Signed-off-by: Thierry Reding --- drivers/pwm/pwm-imx.c | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) (limited to 'drivers/pwm') diff --git a/drivers/pwm/pwm-imx.c b/drivers/pwm/pwm-imx.c index 13d4d22f12c..a1e799ee07d 100644 --- a/drivers/pwm/pwm-imx.c +++ b/drivers/pwm/pwm-imx.c @@ -271,17 +271,7 @@ static struct platform_driver imx_pwm_driver = { .remove = __devexit_p(imx_pwm_remove), }; -static int __init imx_pwm_init(void) -{ - return platform_driver_register(&imx_pwm_driver); -} -arch_initcall(imx_pwm_init); - -static void __exit imx_pwm_exit(void) -{ - platform_driver_unregister(&imx_pwm_driver); -} -module_exit(imx_pwm_exit); +module_platform_driver(imx_pwm_driver); MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Sascha Hauer "); -- cgit v1.2.3-70-g09d2 From 479e2e301c626cc64fb27b6b1938655eaba8b036 Mon Sep 17 00:00:00 2001 From: Philipp Zabel Date: Mon, 25 Jun 2012 16:16:25 +0200 Subject: pwm: i.MX: add devicetree support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit At the same time remove platform based support. No user for this driver has made it into mainline so far, so all we break is out of tree stuff. Signed-off-by: Philipp Zabel Signed-off-by: Sascha Hauer Reviewed-by: Shawn Guo Reviewed-by: Benoît Thébaudeau Signed-off-by: Thierry Reding --- Documentation/devicetree/bindings/pwm/imx-pwm.txt | 17 +++++++++ drivers/pwm/pwm-imx.c | 44 ++++++++++++++++++----- 2 files changed, 52 insertions(+), 9 deletions(-) create mode 100644 Documentation/devicetree/bindings/pwm/imx-pwm.txt (limited to 'drivers/pwm') diff --git a/Documentation/devicetree/bindings/pwm/imx-pwm.txt b/Documentation/devicetree/bindings/pwm/imx-pwm.txt new file mode 100644 index 00000000000..9b9b18514b6 --- /dev/null +++ b/Documentation/devicetree/bindings/pwm/imx-pwm.txt @@ -0,0 +1,17 @@ +Freescale i.MX PWM controller + +Required properties: +- compatible: should be "fsl,-pwm" +- reg: physical base address and length of the controller's registers +- #pwm-cells: should be 2. The first cell specifies the per-chip index + of the PWM to use and the second cell is the duty cycle in nanoseconds. +- interrupts: The interrupt for the pwm controller + +Example: + +pwm1: pwm@53fb4000 { + #pwm-cells = <2>; + compatible = "fsl,imx53-pwm", "fsl,imx27-pwm"; + reg = <0x53fb4000 0x4000>; + interrupts = <61>; +}; diff --git a/drivers/pwm/pwm-imx.c b/drivers/pwm/pwm-imx.c index a1e799ee07d..0f6c436c063 100644 --- a/drivers/pwm/pwm-imx.c +++ b/drivers/pwm/pwm-imx.c @@ -16,9 +16,9 @@ #include #include #include +#include #include - /* i.MX1 and i.MX21 share the same PWM function block: */ #define MX1_PWMC 0x00 /* PWM Control Register */ @@ -204,12 +204,41 @@ static struct pwm_ops imx_pwm_ops = { .owner = THIS_MODULE, }; +struct imx_pwm_data { + int (*config)(struct pwm_chip *chip, + struct pwm_device *pwm, int duty_ns, int period_ns); + void (*set_enable)(struct pwm_chip *chip, bool enable); +}; + +static struct imx_pwm_data imx_pwm_data_v1 = { + .config = imx_pwm_config_v1, + .set_enable = imx_pwm_set_enable_v1, +}; + +static struct imx_pwm_data imx_pwm_data_v2 = { + .config = imx_pwm_config_v2, + .set_enable = imx_pwm_set_enable_v2, +}; + +static const struct of_device_id imx_pwm_dt_ids[] = { + { .compatible = "fsl,imx1-pwm", .data = &imx_pwm_data_v1, }, + { .compatible = "fsl,imx27-pwm", .data = &imx_pwm_data_v2, }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, imx_pwm_dt_ids); + static int __devinit imx_pwm_probe(struct platform_device *pdev) { + const struct of_device_id *of_id = + of_match_device(imx_pwm_dt_ids, &pdev->dev); + struct imx_pwm_data *data; struct imx_chip *imx; struct resource *r; int ret = 0; + if (!of_id) + return -ENODEV; + imx = devm_kzalloc(&pdev->dev, sizeof(*imx), GFP_KERNEL); if (imx == NULL) { dev_err(&pdev->dev, "failed to allocate memory\n"); @@ -236,13 +265,9 @@ static int __devinit imx_pwm_probe(struct platform_device *pdev) if (imx->mmio_base == NULL) return -EADDRNOTAVAIL; - if (cpu_is_mx1() || cpu_is_mx21()) { - imx->config = imx_pwm_config_v1; - imx->set_enable = imx_pwm_set_enable_v1; - } else { - imx->config = imx_pwm_config_v2; - imx->set_enable = imx_pwm_set_enable_v2; - } + data = of_id->data; + imx->config = data->config; + imx->set_enable = data->set_enable; ret = pwmchip_add(&imx->chip); if (ret < 0) @@ -265,7 +290,8 @@ static int __devexit imx_pwm_remove(struct platform_device *pdev) static struct platform_driver imx_pwm_driver = { .driver = { - .name = "mxc_pwm", + .name = "imx-pwm", + .of_match_table = of_match_ptr(imx_pwm_dt_ids), }, .probe = imx_pwm_probe, .remove = __devexit_p(imx_pwm_remove), -- cgit v1.2.3-70-g09d2 From 8d1c24bfd20829f5943c76b85c4973db264dd666 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Tue, 28 Aug 2012 12:03:29 +0200 Subject: pwm: i.MX: use per clock unconditionally MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The i.MX PWM module has two clocks: The ipg clock and the ipg highfreq (peripheral) clock. The ipg clock has to be enabled for this hardware to work. The actual PWM output can either be driven by the ipg clock or the ipg highfreq. The ipg highfreq has the advantage that it runs even when the SoC is in low power modes. Use the always running clock also on i.MX25. Signed-off-by: Sascha Hauer Reviewed-by: Shawn Guo Reviewed-by: Benoît Thébaudeau Signed-off-by: Thierry Reding --- drivers/pwm/pwm-imx.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) (limited to 'drivers/pwm') diff --git a/drivers/pwm/pwm-imx.c b/drivers/pwm/pwm-imx.c index 0f6c436c063..852de6ce6e1 100644 --- a/drivers/pwm/pwm-imx.c +++ b/drivers/pwm/pwm-imx.c @@ -17,7 +17,6 @@ #include #include #include -#include /* i.MX1 and i.MX21 share the same PWM function block: */ @@ -133,16 +132,11 @@ static int imx_pwm_config_v2(struct pwm_chip *chip, cr = MX3_PWMCR_PRESCALER(prescale) | MX3_PWMCR_DOZEEN | MX3_PWMCR_WAITEN | - MX3_PWMCR_DBGEN; + MX3_PWMCR_DBGEN | MX3_PWMCR_CLKSRC_IPG_HIGH; if (imx->enabled) cr |= MX3_PWMCR_EN; - if (cpu_is_mx25()) - cr |= MX3_PWMCR_CLKSRC_IPG; - else - cr |= MX3_PWMCR_CLKSRC_IPG_HIGH; - writel(cr, imx->mmio_base + MX3_PWMCR); return 0; -- cgit v1.2.3-70-g09d2 From 7b27c160c68152581c702b9f1fe362338d2a0cad Mon Sep 17 00:00:00 2001 From: Philipp Zabel Date: Mon, 25 Jun 2012 16:15:20 +0200 Subject: pwm: i.MX: fix clock lookup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The i.MX PWM core has two clocks: The ipg clock and the ipg highfreq (peripheral) clock. The ipg clock has to be enabled for this hardware to work. The actual PWM output can either be driven by the ipg clock or the ipg highfreq. The ipg highfreq has the advantage that it runs even when the SoC is in low power modes. This patch requests both clocks and enables the ipg clock for accessing registers and the peripheral clock to actually turn on the PWM. Signed-off-by: Philipp Zabel Signed-off-by: Sascha Hauer Reviewed-by: Shawn Guo Reviewed-by: Benoît Thébaudeau Signed-off-by: Thierry Reding --- drivers/pwm/pwm-imx.c | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) (limited to 'drivers/pwm') diff --git a/drivers/pwm/pwm-imx.c b/drivers/pwm/pwm-imx.c index 852de6ce6e1..8a5d3ae2946 100644 --- a/drivers/pwm/pwm-imx.c +++ b/drivers/pwm/pwm-imx.c @@ -40,7 +40,8 @@ #define MX3_PWMCR_EN (1 << 0) struct imx_chip { - struct clk *clk; + struct clk *clk_per; + struct clk *clk_ipg; int enabled; void __iomem *mmio_base; @@ -106,7 +107,7 @@ static int imx_pwm_config_v2(struct pwm_chip *chip, unsigned long period_cycles, duty_cycles, prescale; u32 cr; - c = clk_get_rate(imx->clk); + c = clk_get_rate(imx->clk_per); c = c * period_ns; do_div(c, 1000000000); period_cycles = c; @@ -161,8 +162,17 @@ static int imx_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, int duty_ns, int period_ns) { struct imx_chip *imx = to_imx_chip(chip); + int ret; + + ret = clk_prepare_enable(imx->clk_ipg); + if (ret) + return ret; - return imx->config(chip, pwm, duty_ns, period_ns); + ret = imx->config(chip, pwm, duty_ns, period_ns); + + clk_disable_unprepare(imx->clk_ipg); + + return ret; } static int imx_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) @@ -170,7 +180,7 @@ static int imx_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) struct imx_chip *imx = to_imx_chip(chip); int ret; - ret = clk_prepare_enable(imx->clk); + ret = clk_prepare_enable(imx->clk_per); if (ret) return ret; @@ -187,7 +197,7 @@ static void imx_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) imx->set_enable(chip, false); - clk_disable_unprepare(imx->clk); + clk_disable_unprepare(imx->clk_per); imx->enabled = 0; } @@ -239,10 +249,19 @@ static int __devinit imx_pwm_probe(struct platform_device *pdev) return -ENOMEM; } - imx->clk = devm_clk_get(&pdev->dev, "pwm"); + imx->clk_per = devm_clk_get(&pdev->dev, "per"); + if (IS_ERR(imx->clk_per)) { + dev_err(&pdev->dev, "getting per clock failed with %ld\n", + PTR_ERR(imx->clk_per)); + return PTR_ERR(imx->clk_per); + } - if (IS_ERR(imx->clk)) - return PTR_ERR(imx->clk); + imx->clk_ipg = devm_clk_get(&pdev->dev, "ipg"); + if (IS_ERR(imx->clk_ipg)) { + dev_err(&pdev->dev, "getting ipg clock failed with %ld\n", + PTR_ERR(imx->clk_ipg)); + return PTR_ERR(imx->clk_ipg); + } imx->chip.ops = &imx_pwm_ops; imx->chip.dev = &pdev->dev; -- cgit v1.2.3-70-g09d2 From 6173f8f4ed9c2bd4184908e35f46755a0d14b0f2 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Fri, 31 Aug 2012 11:46:24 +0200 Subject: pwm: Move AB8500 PWM driver to PWM framework This commit moves the driver to drivers/pwm and converts it to the new PWM framework. Signed-off-by: Thierry Reding Acked-by: Linus Walleij Acked-by: Arnd Bergmann Acked-by: Arun Murthy --- drivers/misc/Kconfig | 10 --- drivers/misc/Makefile | 1 - drivers/misc/ab8500-pwm.c | 169 ---------------------------------------------- drivers/pwm/Kconfig | 9 +++ drivers/pwm/Makefile | 1 + drivers/pwm/pwm-ab8500.c | 153 +++++++++++++++++++++++++++++++++++++++++ 6 files changed, 163 insertions(+), 180 deletions(-) delete mode 100644 drivers/misc/ab8500-pwm.c create mode 100644 drivers/pwm/pwm-ab8500.c (limited to 'drivers/pwm') diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 98a442da892..041b656c4ca 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -60,16 +60,6 @@ config ATMEL_PWM purposes including software controlled power-efficient backlights on LCD displays, motor control, and waveform generation. -config AB8500_PWM - bool "AB8500 PWM support" - depends on AB8500_CORE && ARCH_U8500 - select HAVE_PWM - depends on !PWM - help - This driver exports functions to enable/disble/config/free Pulse - Width Modulation in the Analog Baseband Chip AB8500. - It is used by led and backlight driver to control the intensity. - config ATMEL_TCLIB bool "Atmel AT32/AT91 Timer/Counter Library" depends on (AVR32 || ARCH_AT91) diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index b88df7a350b..2129377c0de 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -44,7 +44,6 @@ obj-$(CONFIG_VMWARE_BALLOON) += vmw_balloon.o obj-$(CONFIG_ARM_CHARLCD) += arm-charlcd.o obj-$(CONFIG_PCH_PHUB) += pch_phub.o obj-y += ti-st/ -obj-$(CONFIG_AB8500_PWM) += ab8500-pwm.o obj-y += lis3lv02d/ obj-y += carma/ obj-$(CONFIG_USB_SWITCH_FSA9480) += fsa9480.o diff --git a/drivers/misc/ab8500-pwm.c b/drivers/misc/ab8500-pwm.c deleted file mode 100644 index d7a9aa14e5d..00000000000 --- a/drivers/misc/ab8500-pwm.c +++ /dev/null @@ -1,169 +0,0 @@ -/* - * Copyright (C) ST-Ericsson SA 2010 - * - * Author: Arun R Murthy - * License terms: GNU General Public License (GPL) version 2 - */ -#include -#include -#include -#include -#include -#include -#include - -/* - * PWM Out generators - * Bank: 0x10 - */ -#define AB8500_PWM_OUT_CTRL1_REG 0x60 -#define AB8500_PWM_OUT_CTRL2_REG 0x61 -#define AB8500_PWM_OUT_CTRL7_REG 0x66 - -/* backlight driver constants */ -#define ENABLE_PWM 1 -#define DISABLE_PWM 0 - -struct pwm_device { - struct device *dev; - struct list_head node; - const char *label; - unsigned int pwm_id; -}; - -static LIST_HEAD(pwm_list); - -int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns) -{ - int ret = 0; - unsigned int higher_val, lower_val; - u8 reg; - - /* - * get the first 8 bits that are be written to - * AB8500_PWM_OUT_CTRL1_REG[0:7] - */ - lower_val = duty_ns & 0x00FF; - /* - * get bits [9:10] that are to be written to - * AB8500_PWM_OUT_CTRL2_REG[0:1] - */ - higher_val = ((duty_ns & 0x0300) >> 8); - - reg = AB8500_PWM_OUT_CTRL1_REG + ((pwm->pwm_id - 1) * 2); - - ret = abx500_set_register_interruptible(pwm->dev, AB8500_MISC, - reg, (u8)lower_val); - if (ret < 0) - return ret; - ret = abx500_set_register_interruptible(pwm->dev, AB8500_MISC, - (reg + 1), (u8)higher_val); - - return ret; -} -EXPORT_SYMBOL(pwm_config); - -int pwm_enable(struct pwm_device *pwm) -{ - int ret; - - ret = abx500_mask_and_set_register_interruptible(pwm->dev, - AB8500_MISC, AB8500_PWM_OUT_CTRL7_REG, - 1 << (pwm->pwm_id-1), ENABLE_PWM); - if (ret < 0) - dev_err(pwm->dev, "%s: Failed to disable PWM, Error %d\n", - pwm->label, ret); - return ret; -} -EXPORT_SYMBOL(pwm_enable); - -void pwm_disable(struct pwm_device *pwm) -{ - int ret; - - ret = abx500_mask_and_set_register_interruptible(pwm->dev, - AB8500_MISC, AB8500_PWM_OUT_CTRL7_REG, - 1 << (pwm->pwm_id-1), DISABLE_PWM); - if (ret < 0) - dev_err(pwm->dev, "%s: Failed to disable PWM, Error %d\n", - pwm->label, ret); - return; -} -EXPORT_SYMBOL(pwm_disable); - -struct pwm_device *pwm_request(int pwm_id, const char *label) -{ - struct pwm_device *pwm; - - list_for_each_entry(pwm, &pwm_list, node) { - if (pwm->pwm_id == pwm_id) { - pwm->label = label; - pwm->pwm_id = pwm_id; - return pwm; - } - } - - return ERR_PTR(-ENOENT); -} -EXPORT_SYMBOL(pwm_request); - -void pwm_free(struct pwm_device *pwm) -{ - pwm_disable(pwm); -} -EXPORT_SYMBOL(pwm_free); - -static int __devinit ab8500_pwm_probe(struct platform_device *pdev) -{ - struct pwm_device *pwm; - /* - * Nothing to be done in probe, this is required to get the - * device which is required for ab8500 read and write - */ - pwm = kzalloc(sizeof(struct pwm_device), GFP_KERNEL); - if (pwm == NULL) { - dev_err(&pdev->dev, "failed to allocate memory\n"); - return -ENOMEM; - } - pwm->dev = &pdev->dev; - pwm->pwm_id = pdev->id; - list_add_tail(&pwm->node, &pwm_list); - platform_set_drvdata(pdev, pwm); - dev_dbg(pwm->dev, "pwm probe successful\n"); - return 0; -} - -static int __devexit ab8500_pwm_remove(struct platform_device *pdev) -{ - struct pwm_device *pwm = platform_get_drvdata(pdev); - list_del(&pwm->node); - dev_dbg(&pdev->dev, "pwm driver removed\n"); - kfree(pwm); - return 0; -} - -static struct platform_driver ab8500_pwm_driver = { - .driver = { - .name = "ab8500-pwm", - .owner = THIS_MODULE, - }, - .probe = ab8500_pwm_probe, - .remove = __devexit_p(ab8500_pwm_remove), -}; - -static int __init ab8500_pwm_init(void) -{ - return platform_driver_register(&ab8500_pwm_driver); -} - -static void __exit ab8500_pwm_exit(void) -{ - platform_driver_unregister(&ab8500_pwm_driver); -} - -subsys_initcall(ab8500_pwm_init); -module_exit(ab8500_pwm_exit); -MODULE_AUTHOR("Arun MURTHY "); -MODULE_DESCRIPTION("AB8500 Pulse Width Modulation Driver"); -MODULE_ALIAS("platform:ab8500-pwm"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 90c5c7357a5..99a738182bf 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -29,6 +29,15 @@ menuconfig PWM if PWM +config PWM_AB8500 + tristate "AB8500 PWM support" + depends on AB8500_CORE && ARCH_U8500 + help + Generic PWM framework driver for Analog Baseband AB8500. + + To compile this driver as a module, choose M here: the module + will be called pwm-ab8500. + config PWM_BFIN tristate "Blackfin PWM support" depends on BFIN_GPTIMERS diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index e4b2c898964..bebc6ff904b 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -1,4 +1,5 @@ obj-$(CONFIG_PWM) += core.o +obj-$(CONFIG_PWM_AB8500) += pwm-ab8500.o obj-$(CONFIG_PWM_BFIN) += pwm-bfin.o obj-$(CONFIG_PWM_IMX) += pwm-imx.o obj-$(CONFIG_PWM_LPC32XX) += pwm-lpc32xx.o diff --git a/drivers/pwm/pwm-ab8500.c b/drivers/pwm/pwm-ab8500.c new file mode 100644 index 00000000000..cfb72ca873d --- /dev/null +++ b/drivers/pwm/pwm-ab8500.c @@ -0,0 +1,153 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * + * Author: Arun R Murthy + * License terms: GNU General Public License (GPL) version 2 + */ +#include +#include +#include +#include +#include +#include +#include + +/* + * PWM Out generators + * Bank: 0x10 + */ +#define AB8500_PWM_OUT_CTRL1_REG 0x60 +#define AB8500_PWM_OUT_CTRL2_REG 0x61 +#define AB8500_PWM_OUT_CTRL7_REG 0x66 + +/* backlight driver constants */ +#define ENABLE_PWM 1 +#define DISABLE_PWM 0 + +struct ab8500_pwm_chip { + struct pwm_chip chip; +}; + +static int ab8500_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + int ret = 0; + unsigned int higher_val, lower_val; + u8 reg; + + /* + * get the first 8 bits that are be written to + * AB8500_PWM_OUT_CTRL1_REG[0:7] + */ + lower_val = duty_ns & 0x00FF; + /* + * get bits [9:10] that are to be written to + * AB8500_PWM_OUT_CTRL2_REG[0:1] + */ + higher_val = ((duty_ns & 0x0300) >> 8); + + reg = AB8500_PWM_OUT_CTRL1_REG + ((chip->base - 1) * 2); + + ret = abx500_set_register_interruptible(chip->dev, AB8500_MISC, + reg, (u8)lower_val); + if (ret < 0) + return ret; + ret = abx500_set_register_interruptible(chip->dev, AB8500_MISC, + (reg + 1), (u8)higher_val); + + return ret; +} + +static int ab8500_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + int ret; + + ret = abx500_mask_and_set_register_interruptible(chip->dev, + AB8500_MISC, AB8500_PWM_OUT_CTRL7_REG, + 1 << (chip->base - 1), ENABLE_PWM); + if (ret < 0) + dev_err(chip->dev, "%s: Failed to disable PWM, Error %d\n", + pwm->label, ret); + return ret; +} + +static void ab8500_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + int ret; + + ret = abx500_mask_and_set_register_interruptible(chip->dev, + AB8500_MISC, AB8500_PWM_OUT_CTRL7_REG, + 1 << (chip->base - 1), DISABLE_PWM); + if (ret < 0) + dev_err(chip->dev, "%s: Failed to disable PWM, Error %d\n", + pwm->label, ret); + return; +} + +static const struct pwm_ops ab8500_pwm_ops = { + .config = ab8500_pwm_config, + .enable = ab8500_pwm_enable, + .disable = ab8500_pwm_disable, +}; + +static int __devinit ab8500_pwm_probe(struct platform_device *pdev) +{ + struct ab8500_pwm_chip *ab8500; + int err; + + /* + * Nothing to be done in probe, this is required to get the + * device which is required for ab8500 read and write + */ + ab8500 = kzalloc(sizeof(*ab8500), GFP_KERNEL); + if (ab8500 == NULL) { + dev_err(&pdev->dev, "failed to allocate memory\n"); + return -ENOMEM; + } + + ab8500->chip.dev = &pdev->dev; + ab8500->chip.ops = &ab8500_pwm_ops; + ab8500->chip.base = pdev->id; + ab8500->chip.npwm = 1; + + err = pwmchip_add(&ab8500->chip); + if (err < 0) { + kfree(ab8500); + return err; + } + + dev_dbg(&pdev->dev, "pwm probe successful\n"); + platform_set_drvdata(pdev, ab8500); + + return 0; +} + +static int __devexit ab8500_pwm_remove(struct platform_device *pdev) +{ + struct ab8500_pwm_chip *ab8500 = platform_get_drvdata(pdev); + int err; + + err = pwmchip_remove(&ab8500->chip); + if (err < 0) + return err; + + dev_dbg(&pdev->dev, "pwm driver removed\n"); + kfree(ab8500); + + return 0; +} + +static struct platform_driver ab8500_pwm_driver = { + .driver = { + .name = "ab8500-pwm", + .owner = THIS_MODULE, + }, + .probe = ab8500_pwm_probe, + .remove = __devexit_p(ab8500_pwm_remove), +}; +module_platform_driver(ab8500_pwm_driver); + +MODULE_AUTHOR("Arun MURTHY "); +MODULE_DESCRIPTION("AB8500 Pulse Width Modulation Driver"); +MODULE_ALIAS("platform:ab8500-pwm"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3-70-g09d2 From 79c11b6fa0b225ac165e79e821d50e70f563645f Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Fri, 31 Aug 2012 08:29:24 +0200 Subject: pwm: Move PUV3 PWM driver to PWM framework This commit moves the driver to drivers/pwm and converts it to the new PWM framework. Signed-off-by: Thierry Reding Tested-by: Qin Rui Acked-by: Guan Xuetao --- arch/unicore32/Kconfig | 12 +-- arch/unicore32/kernel/Makefile | 1 - arch/unicore32/kernel/pwm.c | 219 ----------------------------------------- drivers/pwm/Kconfig | 11 ++- drivers/pwm/Makefile | 1 + drivers/pwm/pwm-puv3.c | 161 ++++++++++++++++++++++++++++++ 6 files changed, 174 insertions(+), 231 deletions(-) delete mode 100644 arch/unicore32/kernel/pwm.c create mode 100644 drivers/pwm/pwm-puv3.c (limited to 'drivers/pwm') diff --git a/arch/unicore32/Kconfig b/arch/unicore32/Kconfig index b0a47433341..91e20373564 100644 --- a/arch/unicore32/Kconfig +++ b/arch/unicore32/Kconfig @@ -20,9 +20,6 @@ config UNICORE32 designs licensed by PKUnity Ltd. Please see web page at . -config HAVE_PWM - bool - config GENERIC_GPIO def_bool y @@ -105,7 +102,8 @@ config PUV3_DB0913 config PUV3_NB0916 bool "NetBook board (0916)" - select HAVE_PWM + select PWM + select PWM_PUV3 config PUV3_SMW0919 bool "Security Mini-Workstation board (0919)" @@ -219,12 +217,6 @@ config PUV3_GPIO select GPIO_SYSFS if EXPERIMENTAL default y -config PUV3_PWM - tristate - default BACKLIGHT_PWM - help - Enable support for NB0916 PWM controllers - if PUV3_NB0916 menu "PKUnity NetBook-0916 Features" diff --git a/arch/unicore32/kernel/Makefile b/arch/unicore32/kernel/Makefile index 32401015695..fa497e0efe5 100644 --- a/arch/unicore32/kernel/Makefile +++ b/arch/unicore32/kernel/Makefile @@ -16,7 +16,6 @@ obj-$(CONFIG_UNICORE_FPU_F64) += fpu-ucf64.o obj-$(CONFIG_ARCH_PUV3) += clock.o irq.o time.o obj-$(CONFIG_PUV3_GPIO) += gpio.o -obj-$(CONFIG_PUV3_PWM) += pwm.o obj-$(CONFIG_PUV3_PM) += pm.o sleep.o obj-$(CONFIG_HIBERNATION) += hibernate.o hibernate_asm.o diff --git a/arch/unicore32/kernel/pwm.c b/arch/unicore32/kernel/pwm.c deleted file mode 100644 index 724e8603120..00000000000 --- a/arch/unicore32/kernel/pwm.c +++ /dev/null @@ -1,219 +0,0 @@ -/* - * linux/arch/unicore32/kernel/pwm.c - * - * Code specific to PKUnity SoC and UniCore ISA - * - * Maintained by GUAN Xue-tao - * Copyright (C) 2001-2010 Guan Xuetao - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -struct pwm_device { - struct list_head node; - struct platform_device *pdev; - - void __iomem *base; - - const char *label; - struct clk *clk; - int clk_enabled; - - unsigned int use_count; - unsigned int pwm_id; -}; - -/* - * period_ns = 10^9 * (PRESCALE + 1) * (PV + 1) / PWM_CLK_RATE - * duty_ns = 10^9 * (PRESCALE + 1) * DC / PWM_CLK_RATE - */ -int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns) -{ - unsigned long long c; - unsigned long period_cycles, prescale, pv, dc; - - if (pwm == NULL || period_ns == 0 || duty_ns > period_ns) - return -EINVAL; - - c = clk_get_rate(pwm->clk); - c = c * period_ns; - do_div(c, 1000000000); - period_cycles = c; - - if (period_cycles < 1) - period_cycles = 1; - prescale = (period_cycles - 1) / 1024; - pv = period_cycles / (prescale + 1) - 1; - - if (prescale > 63) - return -EINVAL; - - if (duty_ns == period_ns) - dc = OST_PWMDCCR_FDCYCLE; - else - dc = (pv + 1) * duty_ns / period_ns; - - /* NOTE: the clock to PWM has to be enabled first - * before writing to the registers - */ - clk_enable(pwm->clk); - - writel(prescale, pwm->base + OST_PWM_PWCR); - writel(pv - dc, pwm->base + OST_PWM_DCCR); - writel(pv, pwm->base + OST_PWM_PCR); - - clk_disable(pwm->clk); - - return 0; -} -EXPORT_SYMBOL(pwm_config); - -int pwm_enable(struct pwm_device *pwm) -{ - int rc = 0; - - if (!pwm->clk_enabled) { - rc = clk_enable(pwm->clk); - if (!rc) - pwm->clk_enabled = 1; - } - return rc; -} -EXPORT_SYMBOL(pwm_enable); - -void pwm_disable(struct pwm_device *pwm) -{ - if (pwm->clk_enabled) { - clk_disable(pwm->clk); - pwm->clk_enabled = 0; - } -} -EXPORT_SYMBOL(pwm_disable); - -static DEFINE_MUTEX(pwm_lock); -static LIST_HEAD(pwm_list); - -struct pwm_device *pwm_request(int pwm_id, const char *label) -{ - struct pwm_device *pwm; - int found = 0; - - mutex_lock(&pwm_lock); - - list_for_each_entry(pwm, &pwm_list, node) { - if (pwm->pwm_id == pwm_id) { - found = 1; - break; - } - } - - if (found) { - if (pwm->use_count == 0) { - pwm->use_count++; - pwm->label = label; - } else - pwm = ERR_PTR(-EBUSY); - } else - pwm = ERR_PTR(-ENOENT); - - mutex_unlock(&pwm_lock); - return pwm; -} -EXPORT_SYMBOL(pwm_request); - -void pwm_free(struct pwm_device *pwm) -{ - mutex_lock(&pwm_lock); - - if (pwm->use_count) { - pwm->use_count--; - pwm->label = NULL; - } else - pr_warning("PWM device already freed\n"); - - mutex_unlock(&pwm_lock); -} -EXPORT_SYMBOL(pwm_free); - -static inline void __add_pwm(struct pwm_device *pwm) -{ - mutex_lock(&pwm_lock); - list_add_tail(&pwm->node, &pwm_list); - mutex_unlock(&pwm_lock); -} - -static int __devinit pwm_probe(struct platform_device *pdev) -{ - struct pwm_device *pwm; - struct resource *r; - - pwm = devm_kzalloc(&pdev->dev, sizeof(struct pwm_device), GFP_KERNEL); - if (pwm == NULL) { - dev_err(&pdev->dev, "failed to allocate memory\n"); - return -ENOMEM; - } - - pwm->clk = devm_clk_get(&pdev->dev, "OST_CLK"); - if (IS_ERR(pwm->clk)) - return PTR_ERR(pwm->clk); - - pwm->clk_enabled = 0; - - pwm->use_count = 0; - pwm->pwm_id = pdev->id; - pwm->pdev = pdev; - - r = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (r == NULL) { - dev_err(&pdev->dev, "no memory resource defined\n"); - return -ENODEV; - } - - pwm->base = devm_request_and_ioremap(&pdev->dev, r); - if (pwm->base == NULL) - return -EADDRNOTAVAIL; - - __add_pwm(pwm); - platform_set_drvdata(pdev, pwm); - return 0; -} - -static int __devexit pwm_remove(struct platform_device *pdev) -{ - struct pwm_device *pwm; - - pwm = platform_get_drvdata(pdev); - if (pwm == NULL) - return -ENODEV; - - mutex_lock(&pwm_lock); - list_del(&pwm->node); - mutex_unlock(&pwm_lock); - - return 0; -} - -static struct platform_driver puv3_pwm_driver = { - .driver = { - .name = "PKUnity-v3-PWM", - }, - .probe = pwm_probe, - .remove = __devexit_p(pwm_remove), -}; -module_platform_driver(puv3_pwm_driver); - -MODULE_LICENSE("GPL v2"); diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index 99a738182bf..d954c72e242 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -1,6 +1,6 @@ menuconfig PWM bool "Pulse-Width Modulation (PWM) Support" - depends on !MACH_JZ4740 && !PUV3_PWM + depends on !MACH_JZ4740 help Generic Pulse-Width Modulation (PWM) support. @@ -76,6 +76,15 @@ config PWM_MXS To compile this driver as a module, choose M here: the module will be called pwm-mxs. +config PWM_PUV3 + tristate "PKUnity NetBook-0916 PWM support" + depends on ARCH_PUV3 + help + Generic PWM framework driver for PKUnity NetBook-0916. + + To compile this driver as a module, choose M here: the module + will be called pwm-puv3. + config PWM_PXA tristate "PXA PWM support" depends on ARCH_PXA diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index bebc6ff904b..360aad8fa3a 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -4,6 +4,7 @@ obj-$(CONFIG_PWM_BFIN) += pwm-bfin.o obj-$(CONFIG_PWM_IMX) += pwm-imx.o obj-$(CONFIG_PWM_LPC32XX) += pwm-lpc32xx.o obj-$(CONFIG_PWM_MXS) += pwm-mxs.o +obj-$(CONFIG_PWM_PUV3) += pwm-puv3.o obj-$(CONFIG_PWM_PXA) += pwm-pxa.o obj-$(CONFIG_PWM_SAMSUNG) += pwm-samsung.o obj-$(CONFIG_PWM_TEGRA) += pwm-tegra.o diff --git a/drivers/pwm/pwm-puv3.c b/drivers/pwm/pwm-puv3.c new file mode 100644 index 00000000000..2a93f37c46a --- /dev/null +++ b/drivers/pwm/pwm-puv3.c @@ -0,0 +1,161 @@ +/* + * linux/arch/unicore32/kernel/pwm.c + * + * Code specific to PKUnity SoC and UniCore ISA + * + * Maintained by GUAN Xue-tao + * Copyright (C) 2001-2010 Guan Xuetao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +struct puv3_pwm_chip { + struct pwm_chip chip; + void __iomem *base; + struct clk *clk; + bool enabled; +}; + +static inline struct puv3_pwm_chip *to_puv3(struct pwm_chip *chip) +{ + return container_of(chip, struct puv3_pwm_chip, chip); +} + +/* + * period_ns = 10^9 * (PRESCALE + 1) * (PV + 1) / PWM_CLK_RATE + * duty_ns = 10^9 * (PRESCALE + 1) * DC / PWM_CLK_RATE + */ +static int puv3_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + unsigned long period_cycles, prescale, pv, dc; + struct puv3_pwm_chip *puv3 = to_puv3(chip); + unsigned long long c; + + c = clk_get_rate(puv3->clk); + c = c * period_ns; + do_div(c, 1000000000); + period_cycles = c; + + if (period_cycles < 1) + period_cycles = 1; + + prescale = (period_cycles - 1) / 1024; + pv = period_cycles / (prescale + 1) - 1; + + if (prescale > 63) + return -EINVAL; + + if (duty_ns == period_ns) + dc = OST_PWMDCCR_FDCYCLE; + else + dc = (pv + 1) * duty_ns / period_ns; + + /* + * NOTE: the clock to PWM has to be enabled first + * before writing to the registers + */ + clk_prepare_enable(puv3->clk); + + writel(prescale, puv3->base + OST_PWM_PWCR); + writel(pv - dc, puv3->base + OST_PWM_DCCR); + writel(pv, puv3->base + OST_PWM_PCR); + + clk_disable_unprepare(puv3->clk); + + return 0; +} + +static int puv3_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct puv3_pwm_chip *puv3 = to_puv3(chip); + + return clk_prepare_enable(puv3->clk); +} + +static void puv3_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + struct puv3_pwm_chip *puv3 = to_puv3(chip); + + clk_disable_unprepare(puv3->clk); +} + +static const struct pwm_ops puv3_pwm_ops = { + .config = puv3_pwm_config, + .enable = puv3_pwm_enable, + .disable = puv3_pwm_disable, + .owner = THIS_MODULE, +}; + +static int __devinit pwm_probe(struct platform_device *pdev) +{ + struct puv3_pwm_chip *puv3; + struct resource *r; + int ret; + + puv3 = devm_kzalloc(&pdev->dev, sizeof(*puv3), GFP_KERNEL); + if (puv3 == NULL) { + dev_err(&pdev->dev, "failed to allocate memory\n"); + return -ENOMEM; + } + + puv3->clk = devm_clk_get(&pdev->dev, "OST_CLK"); + if (IS_ERR(puv3->clk)) + return PTR_ERR(puv3->clk); + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (r == NULL) { + dev_err(&pdev->dev, "no memory resource defined\n"); + return -ENODEV; + } + + puv3->base = devm_request_and_ioremap(&pdev->dev, r); + if (puv3->base == NULL) + return -EADDRNOTAVAIL; + + puv3->chip.dev = &pdev->dev; + puv3->chip.ops = &puv3_pwm_ops; + puv3->chip.base = -1; + puv3->chip.npwm = 1; + + ret = pwmchip_add(&puv3->chip); + if (ret < 0) { + dev_err(&pdev->dev, "pwmchip_add() failed: %d\n", ret); + return ret; + } + + platform_set_drvdata(pdev, puv3); + return 0; +} + +static int __devexit pwm_remove(struct platform_device *pdev) +{ + struct puv3_pwm_chip *puv3 = platform_get_drvdata(pdev); + + return pwmchip_remove(&puv3->chip); +} + +static struct platform_driver puv3_pwm_driver = { + .driver = { + .name = "PKUnity-v3-PWM", + }, + .probe = pwm_probe, + .remove = __devexit_p(pwm_remove), +}; +module_platform_driver(puv3_pwm_driver); + +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3-70-g09d2 From f6b8a5700057cc1b531c2f9b7806428a6f83b467 Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Wed, 22 Aug 2012 10:01:24 +0200 Subject: pwm: Add Ingenic JZ4740 support This commit moves the driver to drivers/pwm and converts it to the new PWM framework. Signed-off-by: Thierry Reding Acked-by: Lars-Peter Clausen Tested-by: Lars-Peter Clausen Acked-by: Ralf Baechle --- arch/mips/include/asm/mach-jz4740/platform.h | 1 + arch/mips/jz4740/Kconfig | 3 - arch/mips/jz4740/Makefile | 2 +- arch/mips/jz4740/board-qi_lb60.c | 1 + arch/mips/jz4740/platform.c | 6 + arch/mips/jz4740/pwm.c | 177 --------------------- drivers/pwm/Kconfig | 11 +- drivers/pwm/Makefile | 1 + drivers/pwm/pwm-jz4740.c | 221 +++++++++++++++++++++++++++ 9 files changed, 241 insertions(+), 182 deletions(-) delete mode 100644 arch/mips/jz4740/pwm.c create mode 100644 drivers/pwm/pwm-jz4740.c (limited to 'drivers/pwm') diff --git a/arch/mips/include/asm/mach-jz4740/platform.h b/arch/mips/include/asm/mach-jz4740/platform.h index 564ab81d6cd..163e81db880 100644 --- a/arch/mips/include/asm/mach-jz4740/platform.h +++ b/arch/mips/include/asm/mach-jz4740/platform.h @@ -31,6 +31,7 @@ extern struct platform_device jz4740_pcm_device; extern struct platform_device jz4740_codec_device; extern struct platform_device jz4740_adc_device; extern struct platform_device jz4740_wdt_device; +extern struct platform_device jz4740_pwm_device; void jz4740_serial_device_register(void); diff --git a/arch/mips/jz4740/Kconfig b/arch/mips/jz4740/Kconfig index 3e7141f0746..46890305388 100644 --- a/arch/mips/jz4740/Kconfig +++ b/arch/mips/jz4740/Kconfig @@ -7,6 +7,3 @@ config JZ4740_QI_LB60 bool "Qi Hardware Ben NanoNote" endchoice - -config HAVE_PWM - bool diff --git a/arch/mips/jz4740/Makefile b/arch/mips/jz4740/Makefile index e44abea9c20..63bad0e491d 100644 --- a/arch/mips/jz4740/Makefile +++ b/arch/mips/jz4740/Makefile @@ -5,7 +5,7 @@ # Object file lists. obj-y += prom.o irq.o time.o reset.o setup.o dma.o \ - gpio.o clock.o platform.o timer.o pwm.o serial.o + gpio.o clock.o platform.o timer.o serial.o obj-$(CONFIG_DEBUG_FS) += clock-debugfs.o diff --git a/arch/mips/jz4740/board-qi_lb60.c b/arch/mips/jz4740/board-qi_lb60.c index 9a3d9de4d04..43d964d3628 100644 --- a/arch/mips/jz4740/board-qi_lb60.c +++ b/arch/mips/jz4740/board-qi_lb60.c @@ -437,6 +437,7 @@ static struct platform_device *jz_platform_devices[] __initdata = { &jz4740_codec_device, &jz4740_rtc_device, &jz4740_adc_device, + &jz4740_pwm_device, &qi_lb60_gpio_keys, &qi_lb60_pwm_beeper, &qi_lb60_charger_device, diff --git a/arch/mips/jz4740/platform.c b/arch/mips/jz4740/platform.c index e342ed4cbd4..6d14dcdbd90 100644 --- a/arch/mips/jz4740/platform.c +++ b/arch/mips/jz4740/platform.c @@ -323,3 +323,9 @@ struct platform_device jz4740_wdt_device = { .num_resources = ARRAY_SIZE(jz4740_wdt_resources), .resource = jz4740_wdt_resources, }; + +/* PWM */ +struct platform_device jz4740_pwm_device = { + .name = "jz4740-pwm", + .id = -1, +}; diff --git a/arch/mips/jz4740/pwm.c b/arch/mips/jz4740/pwm.c deleted file mode 100644 index a26a6faec9a..00000000000 --- a/arch/mips/jz4740/pwm.c +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright (C) 2010, Lars-Peter Clausen - * JZ4740 platform PWM support - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the - * Free Software Foundation; either version 2 of the License, or (at your - * option) any later version. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, write to the Free Software Foundation, Inc., - * 675 Mass Ave, Cambridge, MA 02139, USA. - * - */ - -#include - -#include -#include -#include -#include - -#include -#include "timer.h" - -static struct clk *jz4740_pwm_clk; - -DEFINE_MUTEX(jz4740_pwm_mutex); - -struct pwm_device { - unsigned int id; - unsigned int gpio; - bool used; -}; - -static struct pwm_device jz4740_pwm_list[] = { - { 2, JZ_GPIO_PWM2, false }, - { 3, JZ_GPIO_PWM3, false }, - { 4, JZ_GPIO_PWM4, false }, - { 5, JZ_GPIO_PWM5, false }, - { 6, JZ_GPIO_PWM6, false }, - { 7, JZ_GPIO_PWM7, false }, -}; - -struct pwm_device *pwm_request(int id, const char *label) -{ - int ret = 0; - struct pwm_device *pwm; - - if (id < 2 || id > 7 || !jz4740_pwm_clk) - return ERR_PTR(-ENODEV); - - mutex_lock(&jz4740_pwm_mutex); - - pwm = &jz4740_pwm_list[id - 2]; - if (pwm->used) - ret = -EBUSY; - else - pwm->used = true; - - mutex_unlock(&jz4740_pwm_mutex); - - if (ret) - return ERR_PTR(ret); - - ret = gpio_request(pwm->gpio, label); - - if (ret) { - printk(KERN_ERR "Failed to request pwm gpio: %d\n", ret); - pwm->used = false; - return ERR_PTR(ret); - } - - jz_gpio_set_function(pwm->gpio, JZ_GPIO_FUNC_PWM); - - jz4740_timer_start(id); - - return pwm; -} - -void pwm_free(struct pwm_device *pwm) -{ - pwm_disable(pwm); - jz4740_timer_set_ctrl(pwm->id, 0); - - jz_gpio_set_function(pwm->gpio, JZ_GPIO_FUNC_NONE); - gpio_free(pwm->gpio); - - jz4740_timer_stop(pwm->id); - - pwm->used = false; -} - -int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns) -{ - unsigned long long tmp; - unsigned long period, duty; - unsigned int prescaler = 0; - unsigned int id = pwm->id; - uint16_t ctrl; - bool is_enabled; - - if (duty_ns < 0 || duty_ns > period_ns) - return -EINVAL; - - tmp = (unsigned long long)clk_get_rate(jz4740_pwm_clk) * period_ns; - do_div(tmp, 1000000000); - period = tmp; - - while (period > 0xffff && prescaler < 6) { - period >>= 2; - ++prescaler; - } - - if (prescaler == 6) - return -EINVAL; - - tmp = (unsigned long long)period * duty_ns; - do_div(tmp, period_ns); - duty = period - tmp; - - if (duty >= period) - duty = period - 1; - - is_enabled = jz4740_timer_is_enabled(id); - if (is_enabled) - pwm_disable(pwm); - - jz4740_timer_set_count(id, 0); - jz4740_timer_set_duty(id, duty); - jz4740_timer_set_period(id, period); - - ctrl = JZ_TIMER_CTRL_PRESCALER(prescaler) | JZ_TIMER_CTRL_SRC_EXT | - JZ_TIMER_CTRL_PWM_ABBRUPT_SHUTDOWN; - - jz4740_timer_set_ctrl(id, ctrl); - - if (is_enabled) - pwm_enable(pwm); - - return 0; -} - -int pwm_enable(struct pwm_device *pwm) -{ - uint32_t ctrl = jz4740_timer_get_ctrl(pwm->id); - - ctrl |= JZ_TIMER_CTRL_PWM_ENABLE; - jz4740_timer_set_ctrl(pwm->id, ctrl); - jz4740_timer_enable(pwm->id); - - return 0; -} - -void pwm_disable(struct pwm_device *pwm) -{ - uint32_t ctrl = jz4740_timer_get_ctrl(pwm->id); - - ctrl &= ~JZ_TIMER_CTRL_PWM_ENABLE; - jz4740_timer_disable(pwm->id); - jz4740_timer_set_ctrl(pwm->id, ctrl); -} - -static int __init jz4740_pwm_init(void) -{ - int ret = 0; - - jz4740_pwm_clk = clk_get(NULL, "ext"); - - if (IS_ERR(jz4740_pwm_clk)) { - ret = PTR_ERR(jz4740_pwm_clk); - jz4740_pwm_clk = NULL; - } - - return ret; -} -subsys_initcall(jz4740_pwm_init); diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig index d954c72e242..e67800581d6 100644 --- a/drivers/pwm/Kconfig +++ b/drivers/pwm/Kconfig @@ -1,6 +1,5 @@ menuconfig PWM bool "Pulse-Width Modulation (PWM) Support" - depends on !MACH_JZ4740 help Generic Pulse-Width Modulation (PWM) support. @@ -56,6 +55,16 @@ config PWM_IMX To compile this driver as a module, choose M here: the module will be called pwm-imx. +config PWM_JZ4740 + tristate "Ingenic JZ4740 PWM support" + depends on MACH_JZ4740 + help + Generic PWM framework driver for Ingenic JZ4740 based + machines. + + To compile this driver as a module, choose M here: the module + will be called pwm-jz4740. + config PWM_LPC32XX tristate "LPC32XX PWM support" depends on ARCH_LPC32XX diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile index 360aad8fa3a..29cf57e2c25 100644 --- a/drivers/pwm/Makefile +++ b/drivers/pwm/Makefile @@ -2,6 +2,7 @@ obj-$(CONFIG_PWM) += core.o obj-$(CONFIG_PWM_AB8500) += pwm-ab8500.o obj-$(CONFIG_PWM_BFIN) += pwm-bfin.o obj-$(CONFIG_PWM_IMX) += pwm-imx.o +obj-$(CONFIG_PWM_JZ4740) += pwm-jz4740.o obj-$(CONFIG_PWM_LPC32XX) += pwm-lpc32xx.o obj-$(CONFIG_PWM_MXS) += pwm-mxs.o obj-$(CONFIG_PWM_PUV3) += pwm-puv3.o diff --git a/drivers/pwm/pwm-jz4740.c b/drivers/pwm/pwm-jz4740.c new file mode 100644 index 00000000000..10250fcefb9 --- /dev/null +++ b/drivers/pwm/pwm-jz4740.c @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2010, Lars-Peter Clausen + * JZ4740 platform PWM support + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define NUM_PWM 8 + +static const unsigned int jz4740_pwm_gpio_list[NUM_PWM] = { + JZ_GPIO_PWM0, + JZ_GPIO_PWM1, + JZ_GPIO_PWM2, + JZ_GPIO_PWM3, + JZ_GPIO_PWM4, + JZ_GPIO_PWM5, + JZ_GPIO_PWM6, + JZ_GPIO_PWM7, +}; + +struct jz4740_pwm_chip { + struct pwm_chip chip; + struct clk *clk; +}; + +static inline struct jz4740_pwm_chip *to_jz4740(struct pwm_chip *chip) +{ + return container_of(chip, struct jz4740_pwm_chip, chip); +} + +static int jz4740_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) +{ + unsigned int gpio = jz4740_pwm_gpio_list[pwm->hwpwm]; + int ret; + + /* + * Timers 0 and 1 are used for system tasks, so they are unavailable + * for use as PWMs. + */ + if (pwm->hwpwm < 2) + return -EBUSY; + + ret = gpio_request(gpio, pwm->label); + if (ret) { + dev_err(chip->dev, "Failed to request GPIO#%u for PWM: %d\n", + gpio, ret); + return ret; + } + + jz_gpio_set_function(gpio, JZ_GPIO_FUNC_PWM); + + jz4740_timer_start(pwm->hwpwm); + + return 0; +} + +static void jz4740_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm) +{ + unsigned int gpio = jz4740_pwm_gpio_list[pwm->hwpwm]; + + jz4740_timer_set_ctrl(pwm->hwpwm, 0); + + jz_gpio_set_function(gpio, JZ_GPIO_FUNC_NONE); + gpio_free(gpio); + + jz4740_timer_stop(pwm->hwpwm); +} + +static int jz4740_pwm_enable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + uint32_t ctrl = jz4740_timer_get_ctrl(pwm->pwm); + + ctrl |= JZ_TIMER_CTRL_PWM_ENABLE; + jz4740_timer_set_ctrl(pwm->hwpwm, ctrl); + jz4740_timer_enable(pwm->hwpwm); + + return 0; +} + +static void jz4740_pwm_disable(struct pwm_chip *chip, struct pwm_device *pwm) +{ + uint32_t ctrl = jz4740_timer_get_ctrl(pwm->hwpwm); + + ctrl &= ~JZ_TIMER_CTRL_PWM_ENABLE; + jz4740_timer_disable(pwm->hwpwm); + jz4740_timer_set_ctrl(pwm->hwpwm, ctrl); +} + +static int jz4740_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, + int duty_ns, int period_ns) +{ + struct jz4740_pwm_chip *jz4740 = to_jz4740(pwm->chip); + unsigned long long tmp; + unsigned long period, duty; + unsigned int prescaler = 0; + uint16_t ctrl; + bool is_enabled; + + tmp = (unsigned long long)clk_get_rate(jz4740->clk) * period_ns; + do_div(tmp, 1000000000); + period = tmp; + + while (period > 0xffff && prescaler < 6) { + period >>= 2; + ++prescaler; + } + + if (prescaler == 6) + return -EINVAL; + + tmp = (unsigned long long)period * duty_ns; + do_div(tmp, period_ns); + duty = period - tmp; + + if (duty >= period) + duty = period - 1; + + is_enabled = jz4740_timer_is_enabled(pwm->hwpwm); + if (is_enabled) + jz4740_pwm_disable(chip, pwm); + + jz4740_timer_set_count(pwm->hwpwm, 0); + jz4740_timer_set_duty(pwm->hwpwm, duty); + jz4740_timer_set_period(pwm->hwpwm, period); + + ctrl = JZ_TIMER_CTRL_PRESCALER(prescaler) | JZ_TIMER_CTRL_SRC_EXT | + JZ_TIMER_CTRL_PWM_ABBRUPT_SHUTDOWN; + + jz4740_timer_set_ctrl(pwm->hwpwm, ctrl); + + if (is_enabled) + jz4740_pwm_enable(chip, pwm); + + return 0; +} + +static const struct pwm_ops jz4740_pwm_ops = { + .request = jz4740_pwm_request, + .free = jz4740_pwm_free, + .config = jz4740_pwm_config, + .enable = jz4740_pwm_enable, + .disable = jz4740_pwm_disable, + .owner = THIS_MODULE, +}; + +static int __devinit jz4740_pwm_probe(struct platform_device *pdev) +{ + struct jz4740_pwm_chip *jz4740; + int ret; + + jz4740 = devm_kzalloc(&pdev->dev, sizeof(*jz4740), GFP_KERNEL); + if (!jz4740) + return -ENOMEM; + + jz4740->clk = clk_get(NULL, "ext"); + if (IS_ERR(jz4740->clk)) + return PTR_ERR(jz4740->clk); + + jz4740->chip.dev = &pdev->dev; + jz4740->chip.ops = &jz4740_pwm_ops; + jz4740->chip.npwm = NUM_PWM; + jz4740->chip.base = -1; + + ret = pwmchip_add(&jz4740->chip); + if (ret < 0) { + clk_put(jz4740->clk); + return ret; + } + + platform_set_drvdata(pdev, jz4740); + + return 0; +} + +static int __devexit jz4740_pwm_remove(struct platform_device *pdev) +{ + struct jz4740_pwm_chip *jz4740 = platform_get_drvdata(pdev); + int ret; + + ret = pwmchip_remove(&jz4740->chip); + if (ret < 0) + return ret; + + clk_put(jz4740->clk); + + return 0; +} + +static struct platform_driver jz4740_pwm_driver = { + .driver = { + .name = "jz4740-pwm", + .owner = THIS_MODULE, + }, + .probe = jz4740_pwm_probe, + .remove = __devexit_p(jz4740_pwm_remove), +}; +module_platform_driver(jz4740_pwm_driver); + +MODULE_AUTHOR("Lars-Peter Clausen "); +MODULE_DESCRIPTION("Ingenic JZ4740 PWM driver"); +MODULE_ALIAS("platform:jz4740-pwm"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3-70-g09d2 From c2d476a98f71c55e9acdca1d5a1080a22c0622af Mon Sep 17 00:00:00 2001 From: Thierry Reding Date: Sun, 2 Sep 2012 22:13:40 +0200 Subject: pwm: Check for negative duty-cycle and period Make sure the duty-cycle and period passed in are not negative. This should eventually be made implicit by making them unsigned. While at it, the drivers' .config() implementations can have the equivalent checks removed. Signed-off-by: Thierry Reding Cc: Shawn Guo Cc: Mark Brown Cc: Arnd Bergmann Cc: Sachin Kamat Cc: Axel Lin Cc: Kukjin Kim Cc: Jingoo Han Cc: Jonghwan Choi Cc: Sascha Hauer Cc: "Philip, Avinash" Cc: Vaibhav Bedia Acked-by: Jingoo Han --- drivers/pwm/core.c | 2 +- drivers/pwm/pwm-bfin.c | 3 --- drivers/pwm/pwm-pxa.c | 3 --- drivers/pwm/pwm-samsung.c | 3 --- drivers/pwm/pwm-tiecap.c | 2 +- drivers/pwm/pwm-tiehrpwm.c | 2 +- 6 files changed, 3 insertions(+), 12 deletions(-) (limited to 'drivers/pwm') diff --git a/drivers/pwm/core.c b/drivers/pwm/core.c index 92b1782d0d8..f5acdaa5270 100644 --- a/drivers/pwm/core.c +++ b/drivers/pwm/core.c @@ -371,7 +371,7 @@ EXPORT_SYMBOL_GPL(pwm_free); */ int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns) { - if (!pwm || period_ns == 0 || duty_ns > period_ns) + if (!pwm || duty_ns < 0 || period_ns <= 0 || duty_ns > period_ns) return -EINVAL; return pwm->chip->ops->config(pwm->chip, pwm, duty_ns, period_ns); diff --git a/drivers/pwm/pwm-bfin.c b/drivers/pwm/pwm-bfin.c index d53c4e7941e..5da8e185e83 100644 --- a/drivers/pwm/pwm-bfin.c +++ b/drivers/pwm/pwm-bfin.c @@ -69,9 +69,6 @@ static int bfin_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, unsigned long period, duty; unsigned long long val; - if (duty_ns < 0 || duty_ns > period_ns) - return -EINVAL; - val = (unsigned long long)get_sclk() * period_ns; do_div(val, NSEC_PER_SEC); period = val; diff --git a/drivers/pwm/pwm-pxa.c b/drivers/pwm/pwm-pxa.c index bd5867a1c70..260c3a88564 100644 --- a/drivers/pwm/pwm-pxa.c +++ b/drivers/pwm/pwm-pxa.c @@ -70,9 +70,6 @@ static int pxa_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, unsigned long offset; int rc; - if (period_ns == 0 || duty_ns > period_ns) - return -EINVAL; - offset = pwm->hwpwm ? 0x10 : 0; c = clk_get_rate(pc->clk); diff --git a/drivers/pwm/pwm-samsung.c b/drivers/pwm/pwm-samsung.c index e5187c0ade9..023a3bee76e 100644 --- a/drivers/pwm/pwm-samsung.c +++ b/drivers/pwm/pwm-samsung.c @@ -126,9 +126,6 @@ static int s3c_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, if (period_ns > NS_IN_HZ || duty_ns > NS_IN_HZ) return -ERANGE; - if (duty_ns > period_ns) - return -EINVAL; - if (period_ns == s3c->period_ns && duty_ns == s3c->duty_ns) return 0; diff --git a/drivers/pwm/pwm-tiecap.c b/drivers/pwm/pwm-tiecap.c index 081471fbb09..d6d4cf05565 100644 --- a/drivers/pwm/pwm-tiecap.c +++ b/drivers/pwm/pwm-tiecap.c @@ -60,7 +60,7 @@ static int ecap_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, unsigned long period_cycles, duty_cycles; unsigned int reg_val; - if (period_ns < 0 || duty_ns < 0 || period_ns > NSEC_PER_SEC) + if (period_ns > NSEC_PER_SEC) return -ERANGE; c = pc->clk_rate; diff --git a/drivers/pwm/pwm-tiehrpwm.c b/drivers/pwm/pwm-tiehrpwm.c index caf00feadc6..d3c1dff0a0d 100644 --- a/drivers/pwm/pwm-tiehrpwm.c +++ b/drivers/pwm/pwm-tiehrpwm.c @@ -221,7 +221,7 @@ static int ehrpwm_pwm_config(struct pwm_chip *chip, struct pwm_device *pwm, unsigned short ps_divval, tb_divval; int i, cmp_reg; - if (period_ns < 0 || duty_ns < 0 || period_ns > NSEC_PER_SEC) + if (period_ns > NSEC_PER_SEC) return -ERANGE; c = pc->clk_rate; -- cgit v1.2.3-70-g09d2