[ARM] pxa: Add PXA support for PWM API
authoreric miao <eric.miao@marvell.com>
Sun, 13 Apr 2008 20:44:04 +0000 (21:44 +0100)
committerRussell King <rmk+kernel@arm.linux.org.uk>
Mon, 19 May 2008 15:25:39 +0000 (16:25 +0100)
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 <eric.miao@marvell.com>
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
arch/arm/mach-pxa/Kconfig
arch/arm/mach-pxa/Makefile
arch/arm/mach-pxa/devices.c
arch/arm/mach-pxa/devices.h
arch/arm/mach-pxa/pwm.c [new file with mode: 0644]
arch/arm/mach-pxa/pxa25x.c
arch/arm/mach-pxa/pxa27x.c
arch/arm/mach-pxa/pxa3xx.c

index 5da7a68204920c5c67a26f8003f80ffd168e48cc..9c01505b182583087216e28c5726f4028605a5a7 100644 (file)
@@ -273,4 +273,9 @@ config PXA_SSP
        tristate
        help
          Enable support for PXA2xx SSP ports
+
+config PXA_PWM
+       tristate
+       help
+         Enable support for PXA2xx/PXA3xx PWM controllers
 endif
index 0e6d05bb81aa9ae806ea6461078dea57679e1445..02cbc3cfbe01155647f5c2a2a471724e6648e8c1 100644 (file)
@@ -10,6 +10,7 @@ obj-$(CONFIG_CPU_FREQ)                += cpu-pxa.o
 
 # Generic drivers that other drivers may depend upon
 obj-$(CONFIG_PXA_SSP)          += ssp.o
+obj-$(CONFIG_PXA_PWM)          += pwm.o
 
 # SoC-specific code
 obj-$(CONFIG_PXA25x)           += mfp-pxa2xx.o pxa25x.o
index d6c05b6eab359af09d97051bec813ccf67d8e61b..794a1076db731164ab81998459dcaa7b9902bab7 100644 (file)
@@ -280,6 +280,36 @@ struct platform_device pxa_device_rtc = {
 
 #ifdef CONFIG_PXA25x
 
+static struct resource pxa25x_resource_pwm0[] = {
+       [0] = {
+               .start  = 0x40b00000,
+               .end    = 0x40b0000f,
+               .flags  = IORESOURCE_MEM,
+       },
+};
+
+struct platform_device pxa25x_device_pwm0 = {
+       .name           = "pxa25x-pwm",
+       .id             = 0,
+       .resource       = pxa25x_resource_pwm0,
+       .num_resources  = ARRAY_SIZE(pxa25x_resource_pwm0),
+};
+
+static struct resource pxa25x_resource_pwm1[] = {
+       [0] = {
+               .start  = 0x40c00000,
+               .end    = 0x40c0000f,
+               .flags  = IORESOURCE_MEM,
+       },
+};
+
+struct platform_device pxa25x_device_pwm1 = {
+       .name           = "pxa25x-pwm",
+       .id             = 1,
+       .resource       = pxa25x_resource_pwm1,
+       .num_resources  = ARRAY_SIZE(pxa25x_resource_pwm1),
+};
+
 static u64 pxa25x_ssp_dma_mask = DMA_BIT_MASK(32);
 
 static struct resource pxa25x_resource_ssp[] = {
@@ -568,6 +598,36 @@ struct platform_device pxa27x_device_ssp3 = {
        .num_resources  = ARRAY_SIZE(pxa27x_resource_ssp3),
 };
 
+static struct resource pxa27x_resource_pwm0[] = {
+       [0] = {
+               .start  = 0x40b00000,
+               .end    = 0x40b0001f,
+               .flags  = IORESOURCE_MEM,
+       },
+};
+
+struct platform_device pxa27x_device_pwm0 = {
+       .name           = "pxa27x-pwm",
+       .id             = 0,
+       .resource       = pxa27x_resource_pwm0,
+       .num_resources  = ARRAY_SIZE(pxa27x_resource_pwm0),
+};
+
+static struct resource pxa27x_resource_pwm1[] = {
+       [0] = {
+               .start  = 0x40c00000,
+               .end    = 0x40c0001f,
+               .flags  = IORESOURCE_MEM,
+       },
+};
+
+struct platform_device pxa27x_device_pwm1 = {
+       .name           = "pxa27x-pwm",
+       .id             = 1,
+       .resource       = pxa27x_resource_pwm1,
+       .num_resources  = ARRAY_SIZE(pxa27x_resource_pwm1),
+};
+
 static struct resource pxa27x_resource_camera[] = {
        [0] = {
                .start  = 0x50000000,
index fcab017f27eef62b4d03203b8667d15df926caaf..e620a3373d42bf7b1b2e2727ecb5af9bdd34b95e 100644 (file)
@@ -24,4 +24,9 @@ extern struct platform_device pxa27x_device_ssp2;
 extern struct platform_device pxa27x_device_ssp3;
 extern struct platform_device pxa3xx_device_ssp4;
 
+extern struct platform_device pxa25x_device_pwm0;
+extern struct platform_device pxa25x_device_pwm1;
+extern struct platform_device pxa27x_device_pwm0;
+extern struct platform_device pxa27x_device_pwm1;
+
 void __init pxa_register_device(struct platform_device *dev, void *data);
diff --git a/arch/arm/mach-pxa/pwm.c b/arch/arm/mach-pxa/pwm.c
new file mode 100644 (file)
index 0000000..70fb3ca
--- /dev/null
@@ -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 <eric.miao@marvell.com>
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/pwm.h>
+
+#include <asm/div64.h>
+#include <asm/arch/pxa-regs.h>
+
+/* 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);
index e5b417d14bb04b772689b431631994c7985899d5..2bed3f98d41c9efccfbe7bf20ff11ef40e558ef8 100644 (file)
@@ -129,12 +129,12 @@ static struct clk pxa25x_clks[] = {
        INIT_CKEN("SSPCLK",  SSP, 3686400, 0, &pxa25x_device_ssp.dev),
        INIT_CKEN("SSPCLK", NSSP, 3686400, 0, &pxa25x_device_nssp.dev),
        INIT_CKEN("SSPCLK", ASSP, 3686400, 0, &pxa25x_device_assp.dev),
+       INIT_CKEN("PWMCLK", PWM0, 3686400, 0, &pxa25x_device_pwm0.dev),
+       INIT_CKEN("PWMCLK", PWM1, 3686400, 0, &pxa25x_device_pwm1.dev),
 
        INIT_CKEN("AC97CLK",     AC97,     24576000, 0, NULL),
 
        /*
-       INIT_CKEN("PWMCLK",  PWM0, 3686400,  0, NULL),
-       INIT_CKEN("PWMCLK",  PWM0, 3686400,  0, NULL),
        INIT_CKEN("I2SCLK",  I2S,  14745600, 0, NULL),
        */
        INIT_CKEN("FICPCLK", FICP, 47923000, 0, NULL),
@@ -269,6 +269,8 @@ static struct platform_device *pxa25x_devices[] __initdata = {
        &pxa25x_device_ssp,
        &pxa25x_device_nssp,
        &pxa25x_device_assp,
+       &pxa25x_device_pwm0,
+       &pxa25x_device_pwm1,
 };
 
 static struct sys_device pxa25x_sysdev[] = {
index 7e945836e12924e9e4a5b89a4edd3331f339710a..bc2e80f69673b30f9f0669d165844e3c8bf25e50 100644 (file)
@@ -157,12 +157,13 @@ static struct clk pxa27x_clks[] = {
        INIT_CKEN("SSPCLK", SSP1, 13000000, 0, &pxa27x_device_ssp1.dev),
        INIT_CKEN("SSPCLK", SSP2, 13000000, 0, &pxa27x_device_ssp2.dev),
        INIT_CKEN("SSPCLK", SSP3, 13000000, 0, &pxa27x_device_ssp3.dev),
+       INIT_CKEN("PWMCLK", PWM0, 13000000, 0, &pxa27x_device_pwm0.dev),
+       INIT_CKEN("PWMCLK", PWM1, 13000000, 0, &pxa27x_device_pwm1.dev),
 
        INIT_CKEN("AC97CLK",     AC97,     24576000, 0, NULL),
        INIT_CKEN("AC97CONFCLK", AC97CONF, 24576000, 0, NULL),
 
        /*
-       INIT_CKEN("PWMCLK",  PWM0, 13000000, 0, NULL),
        INIT_CKEN("MSLCLK",  MSL,  48000000, 0, NULL),
        INIT_CKEN("USIMCLK", USIM, 48000000, 0, NULL),
        INIT_CKEN("MSTKCLK", MEMSTK, 19500000, 0, NULL),
@@ -363,6 +364,8 @@ static struct platform_device *devices[] __initdata = {
        &pxa27x_device_ssp1,
        &pxa27x_device_ssp2,
        &pxa27x_device_ssp3,
+       &pxa27x_device_pwm0,
+       &pxa27x_device_pwm1,
 };
 
 static struct sys_device pxa27x_sysdev[] = {
index 644550bfa330fb8607dc89e3f192f864f5adc95a..0f717df1fdb28492c68abd32efb1ed16a229a678 100644 (file)
@@ -239,6 +239,8 @@ static struct clk pxa3xx_clks[] = {
        PXA3xx_CKEN("SSPCLK", SSP2, 13000000, 0, &pxa27x_device_ssp2.dev),
        PXA3xx_CKEN("SSPCLK", SSP3, 13000000, 0, &pxa27x_device_ssp3.dev),
        PXA3xx_CKEN("SSPCLK", SSP4, 13000000, 0, &pxa3xx_device_ssp4.dev),
+       PXA3xx_CKEN("PWMCLK", PWM0, 13000000, 0, &pxa27x_device_pwm0.dev),
+       PXA3xx_CKEN("PWMCLK", PWM1, 13000000, 0, &pxa27x_device_pwm1.dev),
 
        PXA3xx_CKEN("MMCCLK", MMC1, 19500000, 0, &pxa_device_mci.dev),
        PXA3xx_CKEN("MMCCLK", MMC2, 19500000, 0, &pxa3xx_device_mci2.dev),
@@ -530,6 +532,8 @@ static struct platform_device *devices[] __initdata = {
        &pxa27x_device_ssp2,
        &pxa27x_device_ssp3,
        &pxa3xx_device_ssp4,
+       &pxa27x_device_pwm0,
+       &pxa27x_device_pwm1,
 };
 
 static struct sys_device pxa3xx_sysdev[] = {