From 75540c1ac3c7bd72ac8e092058f9714875239995 Mon Sep 17 00:00:00 2001 From: eric miao Date: Sun, 13 Apr 2008 21:44:04 +0100 Subject: [ARM] pxa: Add PXA support for PWM API Patch mainly from Eric Miao, with minor edits by rmk. Note: PWM0 and PWM2 share the same register I/O space and clock gating on pxa{27x, 3xx}, thus PWM2 is treated in the driver as a child PWM of PWM0. And this is also true for PWM1/3. Signed-off-by: eric miao Signed-off-by: Russell King --- arch/arm/mach-pxa/pwm.c | 299 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 299 insertions(+) create mode 100644 arch/arm/mach-pxa/pwm.c (limited to 'arch/arm/mach-pxa/pwm.c') diff --git a/arch/arm/mach-pxa/pwm.c b/arch/arm/mach-pxa/pwm.c new file mode 100644 index 000000000000..70fb3ca6a058 --- /dev/null +++ b/arch/arm/mach-pxa/pwm.c @@ -0,0 +1,299 @@ +/* + * linux/arch/arm/mach-pxa/pwm.c + * + * simple driver for PWM (Pulse Width Modulator) controller + * + * 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. + * + * 2008-02-13 initial version + * eric miao + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* PWM registers and bits definitions */ +#define PWMCR (0x00) +#define PWMDCR (0x04) +#define PWMPCR (0x08) + +#define PWMCR_SD (1 << 6) +#define PWMDCR_FD (1 << 10) + +struct pwm_device { + struct list_head node; + struct platform_device *pdev; + + const char *label; + struct clk *clk; + void __iomem *mmio_base; + + 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 < 0) + 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 = PWMDCR_FD; + 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); + __raw_writel(prescale, pwm->mmio_base + PWMCR); + __raw_writel(dc, pwm->mmio_base + PWMDCR); + __raw_writel(pv, pwm->mmio_base + PWMPCR); + clk_disable(pwm->clk); + + return 0; +} +EXPORT_SYMBOL(pwm_config); + +int pwm_enable(struct pwm_device *pwm) +{ + return clk_enable(pwm->clk); +} +EXPORT_SYMBOL(pwm_enable); + +void pwm_disable(struct pwm_device *pwm) +{ + clk_disable(pwm->clk); +} +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 && pwm->use_count == 0) { + pwm->use_count++; + pwm->label = label; + found = 1; + break; + } + } + + mutex_unlock(&pwm_lock); + + return (found) ? pwm : NULL; +} +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 struct pwm_device *pwm_probe(struct platform_device *pdev, + unsigned int pwm_id, struct pwm_device *parent_pwm) +{ + struct pwm_device *pwm; + struct resource *r; + int ret = 0; + + pwm = kzalloc(sizeof(struct pwm_device), GFP_KERNEL); + if (pwm == NULL) { + dev_err(&pdev->dev, "failed to allocate memory\n"); + return ERR_PTR(-ENOMEM); + } + + pwm->clk = clk_get(&pdev->dev, "PWMCLK"); + if (IS_ERR(pwm->clk)) { + ret = PTR_ERR(pwm->clk); + goto err_free; + } + + pwm->use_count = 0; + pwm->pwm_id = pwm_id; + pwm->pdev = pdev; + + if (parent_pwm != NULL) { + /* registers for the second PWM has offset of 0x10 */ + pwm->mmio_base = parent_pwm->mmio_base + 0x10; + __add_pwm(pwm); + return pwm; + } + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (r == NULL) { + dev_err(&pdev->dev, "no memory resource defined\n"); + ret = -ENODEV; + goto err_free_clk; + } + + r = request_mem_region(r->start, r->end - r->start + 1, pdev->name); + if (r == NULL) { + dev_err(&pdev->dev, "failed to request memory resource\n"); + ret = -EBUSY; + goto err_free_clk; + } + + pwm->mmio_base = ioremap(r->start, r->end - r->start + 1); + if (pwm->mmio_base == NULL) { + dev_err(&pdev->dev, "failed to ioremap() registers\n"); + ret = -ENODEV; + goto err_free_mem; + } + + __add_pwm(pwm); + platform_set_drvdata(pdev, pwm); + return pwm; + +err_free_mem: + release_mem_region(r->start, r->end - r->start + 1); +err_free_clk: + clk_put(pwm->clk); +err_free: + kfree(pwm); + return ERR_PTR(ret); +} + +static int __devinit pxa25x_pwm_probe(struct platform_device *pdev) +{ + struct pwm_device *pwm = pwm_probe(pdev, pdev->id, NULL); + + if (IS_ERR(pwm)) + return PTR_ERR(pwm); + + return 0; +} + +static int __devinit pxa27x_pwm_probe(struct platform_device *pdev) +{ + struct pwm_device *pwm; + + pwm = pwm_probe(pdev, pdev->id * 2, NULL); + if (IS_ERR(pwm)) + return PTR_ERR(pwm); + + pwm = pwm_probe(pdev, pdev->id * 2 + 1, pwm); + if (IS_ERR(pwm)) + return PTR_ERR(pwm); + + return 0; +} + +static int __devexit pwm_remove(struct platform_device *pdev) +{ + struct pwm_device *pwm; + struct resource *r; + + pwm = platform_get_drvdata(pdev); + if (pwm == NULL) + return -ENODEV; + + mutex_lock(&pwm_lock); + list_del(&pwm->node); + mutex_unlock(&pwm_lock); + + iounmap(pwm->mmio_base); + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + release_mem_region(r->start, r->end - r->start + 1); + + clk_put(pwm->clk); + kfree(pwm); + return 0; +} + +static struct platform_driver pxa25x_pwm_driver = { + .driver = { + .name = "pxa25x-pwm", + }, + .probe = pxa25x_pwm_probe, + .remove = __devexit_p(pwm_remove), +}; + +static struct platform_driver pxa27x_pwm_driver = { + .driver = { + .name = "pxa27x-pwm", + }, + .probe = pxa27x_pwm_probe, + .remove = __devexit_p(pwm_remove), +}; + +static int __init pwm_init(void) +{ + int ret = 0; + + ret = platform_driver_register(&pxa25x_pwm_driver); + if (ret) { + printk(KERN_ERR "failed to register pxa25x_pwm_driver\n"); + return ret; + } + + ret = platform_driver_register(&pxa27x_pwm_driver); + if (ret) { + printk(KERN_ERR "failed to register pxa27x_pwm_driver\n"); + return ret; + } + + return ret; +} +arch_initcall(pwm_init); + +static void __exit pwm_exit(void) +{ + platform_driver_unregister(&pxa25x_pwm_driver); + platform_driver_unregister(&pxa27x_pwm_driver); +} +module_exit(pwm_exit); -- cgit v1.2.1 From 0b1c25d2c0f35b4cdd3f04f365cff31fc5a21813 Mon Sep 17 00:00:00 2001 From: Philipp Zabel Date: Mon, 30 Jun 2008 18:09:03 +0100 Subject: [ARM] 5136/1: pxa: fix PWM device order for pxa27x Currently PWM0/2 (pxa27x_device_pwm0 at 0x40b00000 and 0x40b00010 are registered as as pwm_id 0 and 1, PWM1/3 (pxa27x_device_pwm1 at 0x40c00000 and 0x40c00010) are registered as pwm_id 2 and 3. This patch corrects the pwm_ids to match the documented register names. Signed-off-by: Philipp Zabel Acked-by: Eric Miao Signed-off-by: Russell King --- arch/arm/mach-pxa/pwm.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'arch/arm/mach-pxa/pwm.c') diff --git a/arch/arm/mach-pxa/pwm.c b/arch/arm/mach-pxa/pwm.c index 70fb3ca6a058..fa9323ee854c 100644 --- a/arch/arm/mach-pxa/pwm.c +++ b/arch/arm/mach-pxa/pwm.c @@ -221,11 +221,11 @@ static int __devinit pxa27x_pwm_probe(struct platform_device *pdev) { struct pwm_device *pwm; - pwm = pwm_probe(pdev, pdev->id * 2, NULL); + pwm = pwm_probe(pdev, pdev->id, NULL); if (IS_ERR(pwm)) return PTR_ERR(pwm); - pwm = pwm_probe(pdev, pdev->id * 2 + 1, pwm); + pwm = pwm_probe(pdev, pdev->id + 2, pwm); if (IS_ERR(pwm)) return PTR_ERR(pwm); -- cgit v1.2.1 From b5f0228afa7810f3cad1f2f741cc747a2378b890 Mon Sep 17 00:00:00 2001 From: Guennadi Liakhovetski Date: Thu, 5 Jun 2008 10:45:02 +0100 Subject: [ARM] 5078/1: pxa-pwm: Add missing MODULE_LICENSE to be able to build the driver as a module Without a GPL-compatible license this driver cannot be built as a module, because the platform_driver_* API is only exported to GPL modules. Signed-off-by: Guennadi Liakhovetski Acked-by: Eric Miao Signed-off-by: Russell King --- arch/arm/mach-pxa/pwm.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'arch/arm/mach-pxa/pwm.c') diff --git a/arch/arm/mach-pxa/pwm.c b/arch/arm/mach-pxa/pwm.c index fa9323ee854c..92fef391d990 100644 --- a/arch/arm/mach-pxa/pwm.c +++ b/arch/arm/mach-pxa/pwm.c @@ -297,3 +297,5 @@ static void __exit pwm_exit(void) platform_driver_unregister(&pxa27x_pwm_driver); } module_exit(pwm_exit); + +MODULE_LICENSE("GPL v2"); -- cgit v1.2.1 From c860d701cc5f77b74c481dcbd25b2d8b31c622fc Mon Sep 17 00:00:00 2001 From: Robert Jarzmik Date: Tue, 10 Jun 2008 23:02:31 +0100 Subject: [ARM] 5087/1: Get the PWM layer to handle clock enable/disable properly. Allow pwm_enable()/pwm_disable() to be called as many times as the driver wants (and not even count them). The PWM model is different from things like the clock API where we need enable counting, because PWMs have one exclusive user per PWM whereas the clock API can have multiple users of the same clock. Acked-by: eric miao Signed-off-by: Robert Jarzmik Signed-off-by: Russell King --- arch/arm/mach-pxa/pwm.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) (limited to 'arch/arm/mach-pxa/pwm.c') diff --git a/arch/arm/mach-pxa/pwm.c b/arch/arm/mach-pxa/pwm.c index 92fef391d990..7c86dd1d108f 100644 --- a/arch/arm/mach-pxa/pwm.c +++ b/arch/arm/mach-pxa/pwm.c @@ -36,6 +36,7 @@ struct pwm_device { const char *label; struct clk *clk; + int clk_enabled; void __iomem *mmio_base; unsigned int use_count; @@ -87,13 +88,23 @@ EXPORT_SYMBOL(pwm_config); int pwm_enable(struct pwm_device *pwm) { - return clk_enable(pwm->clk); + 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) { - clk_disable(pwm->clk); + if (pwm->clk_enabled) { + clk_disable(pwm->clk); + pwm->clk_enabled = 0; + } } EXPORT_SYMBOL(pwm_disable); @@ -161,6 +172,7 @@ static struct pwm_device *pwm_probe(struct platform_device *pdev, ret = PTR_ERR(pwm->clk); goto err_free; } + pwm->clk_enabled = 0; pwm->use_count = 0; pwm->pwm_id = pwm_id; -- cgit v1.2.1 From 43bda1a6d218744382547a2f8be3240d1c3a151b Mon Sep 17 00:00:00 2001 From: Ben Dooks Date: Tue, 1 Jul 2008 14:18:27 +0100 Subject: [ARM] 5141/1: PWM: pwm_request() should return an PTR_ERR() instead of NULL. Make the return of pwm_request() be more informative than just being NULL on error by using PTR_ERR() to respond with an approriate error. Signed-off-by: Ben Dooks Signed-off-by: Russell King --- arch/arm/mach-pxa/pwm.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) (limited to 'arch/arm/mach-pxa/pwm.c') diff --git a/arch/arm/mach-pxa/pwm.c b/arch/arm/mach-pxa/pwm.c index 7c86dd1d108f..ce28cd9fed16 100644 --- a/arch/arm/mach-pxa/pwm.c +++ b/arch/arm/mach-pxa/pwm.c @@ -119,17 +119,23 @@ struct pwm_device *pwm_request(int pwm_id, const char *label) mutex_lock(&pwm_lock); list_for_each_entry(pwm, &pwm_list, node) { - if (pwm->pwm_id == pwm_id && pwm->use_count == 0) { - pwm->use_count++; - pwm->label = label; + if (pwm->pwm_id == pwm_id) { found = 1; break; } } - mutex_unlock(&pwm_lock); + if (found) { + if (pwm->use_count == 0) { + pwm->use_count++; + pwm->label = label; + } else + pwm = ERR_PTR(-EBUSY); + } else + pwm = ERR_PTR(-ENOENT); - return (found) ? pwm : NULL; + mutex_unlock(&pwm_lock); + return pwm; } EXPORT_SYMBOL(pwm_request); -- cgit v1.2.1