eCAP:PWM: Add device driver support for eCAP module
authorNatarajan Sugumar <sugumar@ti.com>
Wed, 9 Feb 2011 11:20:23 +0000 (16:50 +0530)
committerVaibhav Hiremath <hvaibhav@ti.com>
Mon, 23 Jan 2012 19:14:52 +0000 (00:44 +0530)
OMAPL138/DA850 contains three instances of eCAP module.
Each eCAP module has one dedicated pin that can be used either
in capture mode(input) or in PWM mode.
This patch adds eCAP driver support for PWM signal generation.

Signed-off-by: Natarajan Sugumar <sugumar@ti.com>
Signed-off-by: Philip, Avinash <avinashphilip@ti.com>
drivers/pwm/Kconfig
drivers/pwm/Makefile
drivers/pwm/ecap.c [new file with mode: 0644]

index 361b5da614cbf6021f0ad4930cb63dc2214afe86..c2205e36cfbfbaa365f08ff9578bb7b459b0a435 100644 (file)
@@ -16,3 +16,14 @@ config DAVINCI_EHRPWM
        help
          This option enables support for eHRPWM driver. If
          unsure, say N.
+
+config ECAP_PWM
+       tristate "eCAP PWM support"
+       select HAVE_PWM
+       depends on GENERIC_PWM && ARCH_DAVINCI_DA850
+       help
+         This option enables device driver support for eCAP module found
+         on DA8xx Processors. eCAP module is used to geenrate wide range
+         of PWM waveforms. Maximum frequency generated is equal to half
+         the system clock frequency.
+         Say Y to enable the eCAP support. If unsure, say N.
index 0efd604bd3ec14447dde398a86f0fb17e723a1a6..e888fada868237f6f095e666b8c5128d4dc57884 100644 (file)
@@ -4,3 +4,5 @@
 obj-$(CONFIG_GENERIC_PWM) := pwm.o
 
 obj-$(CONFIG_DAVINCI_EHRPWM)   += ehrpwm.o
+
+obj-$(CONFIG_ECAP_PWM)         += ecap.o
diff --git a/drivers/pwm/ecap.c b/drivers/pwm/ecap.c
new file mode 100644 (file)
index 0000000..351d88d
--- /dev/null
@@ -0,0 +1,343 @@
+/*
+ * eCAP driver for PWM output generation
+ *
+ * Copyright (C) 2010 Texas Instruments Incorporated - http://www.ti.com/
+ *
+ * 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 version 2.
+ *
+ * This program is distributed .as is. WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+#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/pwm.h>
+#include <linux/slab.h>
+
+#include <plat/clock.h>
+
+#define TIMER_CTR_REG                  0x0
+#define CAPTURE_2_REG                  0x0c
+#define CAPTURE_3_REG                  0x10
+#define CAPTURE_4_REG                  0x14
+#define CAPTURE_CTRL2_REG              0x2A
+
+#define ECTRL2_SYNCOSEL_MASK           (0x03 << 6)
+
+#define ECTRL2_MDSL_ECAP               BIT(9)
+#define ECTRL2_CTRSTP_FREERUN          BIT(4)
+#define ECTRL2_PLSL_LOW                        BIT(10)
+#define ECTRL2_SYNC_EN                 BIT(5)
+
+struct ecap_pwm {
+       struct pwm_device pwm;
+       struct pwm_device_ops ops;
+       spinlock_t      lock;
+       struct clk      *clk;
+       int     clk_enabled;
+       void __iomem    *mmio_base;
+};
+
+static inline struct ecap_pwm *to_ecap_pwm(const struct pwm_device *p)
+{
+       return pwm_get_drvdata(p);
+}
+
+static int ecap_pwm_stop(struct pwm_device *p)
+{
+       unsigned long flags;
+       struct ecap_pwm *ep = to_ecap_pwm(p);
+
+       clk_enable(ep->clk);
+
+       spin_lock_irqsave(&ep->lock, flags);
+       __raw_writew(__raw_readw(ep->mmio_base + CAPTURE_CTRL2_REG) &
+               ~BIT(4), ep->mmio_base + CAPTURE_CTRL2_REG);
+       spin_unlock_irqrestore(&ep->lock, flags);
+
+       clk_disable(ep->clk);
+       if (ep->clk_enabled) {
+               clk_disable(ep->clk);
+               ep->clk_enabled = 0;
+       }
+       clear_bit(FLAG_RUNNING, &p->flags);
+
+       return 0;
+}
+
+static int ecap_pwm_start(struct pwm_device *p)
+{
+       int ret = 0;
+       unsigned long flags;
+       struct ecap_pwm *ep = to_ecap_pwm(p);
+
+       clk_enable(ep->clk);
+       spin_lock_irqsave(&ep->lock, flags);
+       __raw_writew(__raw_readw(ep->mmio_base + CAPTURE_CTRL2_REG) |
+               BIT(4), ep->mmio_base + CAPTURE_CTRL2_REG);
+       spin_unlock_irqrestore(&ep->lock, flags);
+
+       clk_disable(ep->clk);
+       if (!ep->clk_enabled) {
+               ret = clk_enable(ep->clk);
+               if (ret)
+                       return ret;
+               ep->clk_enabled = 1;
+       }
+       set_bit(FLAG_RUNNING, &p->flags);
+
+       return ret;
+}
+
+static int ecap_pwm_set_polarity(struct pwm_device *p, char pol)
+{
+       unsigned long flags;
+       struct ecap_pwm *ep = to_ecap_pwm(p);
+
+       clk_enable(ep->clk);
+
+       spin_lock_irqsave(&ep->lock, flags);
+        __raw_writew((__raw_readw(ep->mmio_base + CAPTURE_CTRL2_REG) &
+                ~BIT(10)) | (!pol << 10), ep->mmio_base + CAPTURE_CTRL2_REG);
+       spin_unlock_irqrestore(&ep->lock, flags);
+
+       clk_disable(ep->clk);
+       return 0;
+}
+
+static int ecap_pwm_config_period(struct pwm_device *p)
+{
+       unsigned long flags;
+       struct ecap_pwm *ep = to_ecap_pwm(p);
+
+        clk_enable(ep->clk);
+
+       spin_lock_irqsave(&ep->lock, flags);
+       __raw_writel((p->period_ticks) - 1, ep->mmio_base + CAPTURE_3_REG);
+       __raw_writew(ECTRL2_MDSL_ECAP | ECTRL2_SYNCOSEL_MASK |
+                ECTRL2_CTRSTP_FREERUN, ep->mmio_base + CAPTURE_CTRL2_REG);
+       spin_unlock_irqrestore(&ep->lock, flags);
+
+       clk_disable(ep->clk);
+       return 0;
+}
+
+static int ecap_pwm_config_duty(struct pwm_device *p)
+{
+       unsigned long flags;
+       struct ecap_pwm *ep = to_ecap_pwm(p);
+
+       clk_enable(ep->clk);
+
+       spin_lock_irqsave(&ep->lock, flags);
+       __raw_writew(ECTRL2_MDSL_ECAP | ECTRL2_SYNCOSEL_MASK |
+        ECTRL2_CTRSTP_FREERUN, ep->mmio_base + CAPTURE_CTRL2_REG);
+       if (p->duty_ticks > 0) {
+               __raw_writel(p->duty_ticks, ep->mmio_base + CAPTURE_4_REG);
+       } else {
+       __raw_writel(p->duty_ticks, ep->mmio_base + CAPTURE_2_REG);
+       __raw_writel(0, ep->mmio_base + TIMER_CTR_REG);
+       }
+       spin_unlock_irqrestore(&ep->lock, flags);
+
+       clk_disable(ep->clk);
+       return 0;
+}
+
+static int ecap_pwm_config(struct pwm_device *p,
+                               struct pwm_config *c)
+{
+       int ret = 0;
+       switch (c->config_mask) {
+
+       case BIT(PWM_CONFIG_DUTY_TICKS):
+               p->duty_ticks = c->duty_ticks;
+               ret = ecap_pwm_config_duty(p);
+               break;
+
+       case BIT(PWM_CONFIG_PERIOD_TICKS):
+               p->period_ticks = c->period_ticks;
+               ret = ecap_pwm_config_period(p);
+               break;
+
+       case BIT(PWM_CONFIG_POLARITY):
+               ret = ecap_pwm_set_polarity(p, c->polarity);
+               break;
+
+       case BIT(PWM_CONFIG_START):
+               ret = ecap_pwm_start(p);
+               break;
+
+       case BIT(PWM_CONFIG_STOP):
+               ret = ecap_pwm_stop(p);
+               break;
+       }
+
+       return ret;
+}
+
+static int ecap_pwm_request(struct pwm_device *p)
+{
+       struct ecap_pwm *ep = to_ecap_pwm(p);
+
+       p->tick_hz = clk_get_rate(ep->clk);
+       return 0;
+}
+
+static int ecap_frequency_transition_cb(struct pwm_device *p)
+{
+       struct ecap_pwm *ep = to_ecap_pwm(p);
+       unsigned long duty_ns;
+
+       p->tick_hz = clk_get_rate(ep->clk);
+       duty_ns = p->duty_ns;
+       if (pwm_is_running(p)) {
+               pwm_stop(p);
+               pwm_set_duty_ns(p, 0);
+               pwm_set_period_ns(p, p->period_ns);
+               pwm_set_duty_ns(p, duty_ns);
+               pwm_start(p);
+       } else {
+               pwm_set_duty_ns(p, 0);
+               pwm_set_period_ns(p, p->period_ns);
+               pwm_set_duty_ns(p, duty_ns);
+       }
+               return 0;
+}
+
+static int __init ecap_probe(struct platform_device *pdev)
+{
+       struct ecap_pwm *ep = NULL;
+       struct resource *r;
+       int ret = 0;
+
+       ep = kzalloc(sizeof(*ep), GFP_KERNEL);
+       if (!ep) {
+               dev_err(&pdev->dev, "failed to allocate memory\n");
+               ret = -ENOMEM;
+               goto err_ecap_pwm_alloc;
+       }
+
+       ep->clk = clk_get(&pdev->dev, "ecap");
+       if (IS_ERR(ep->clk)) {
+               ret = PTR_ERR(ep->clk);
+               goto err_free;
+       }
+
+       spin_lock_init(&ep->lock);
+       ep->ops.config = ecap_pwm_config;
+       ep->ops.request = ecap_pwm_request;
+       ep->ops.freq_transition_notifier_cb = ecap_frequency_transition_cb;
+       r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       if (!r) {
+               dev_err(&pdev->dev, "no memory resource defined\n");
+               ret = -ENODEV;
+               goto err_free_clk;
+       }
+
+       r = request_mem_region(r->start, resource_size(r), pdev->name);
+       if (!r) {
+               dev_err(&pdev->dev, "failed to request memory resource\n");
+               ret = -EBUSY;
+               goto err_free_clk;
+       }
+
+       ep->mmio_base = ioremap(r->start, resource_size(r));
+       if (!ep->mmio_base) {
+               dev_err(&pdev->dev, "failed to ioremap() registers\n");
+               ret = -ENODEV;
+               goto err_free_mem;
+       }
+
+       ep->pwm.ops = &ep->ops;
+       pwm_set_drvdata(&ep->pwm, ep);
+       ret =  pwm_register(&ep->pwm, &pdev->dev, -1);
+       platform_set_drvdata(pdev, ep);
+       return 0;
+
+err_free_mem:
+       release_mem_region(r->start, resource_size(r));
+err_free_clk:
+       clk_put(ep->clk);
+err_free:
+       kfree(ep);
+err_ecap_pwm_alloc:
+       return ret;
+}
+
+#ifdef CONFIG_PM
+static int ecap_suspend(struct platform_device *pdev, pm_message_t state)
+{
+       struct ecap_pwm *ep = platform_get_drvdata(pdev);
+
+       if (ep->clk->usecount > 0)
+               clk_disable(ep->clk);
+
+       return 0;
+}
+
+static int ecap_resume(struct platform_device *pdev)
+{
+       struct ecap_pwm *ep = platform_get_drvdata(pdev);
+
+       clk_enable(ep->clk);
+
+       return 0;
+}
+
+#else
+#define ecap_suspend NULL
+#define ecap_resume NULL
+#endif
+
+static int __devexit ecap_remove(struct platform_device *pdev)
+{
+       struct ecap_pwm *ep = platform_get_drvdata(pdev);
+       struct resource *r;
+
+       pwm_unregister(&ep->pwm);
+       iounmap(ep->mmio_base);
+       r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       release_mem_region(r->start, resource_size(r));
+       platform_set_drvdata(pdev, NULL);
+       clk_put(ep->clk);
+       kfree(ep);
+
+       return 0;
+}
+
+static struct platform_driver ecap_driver = {
+       .driver = {
+               .name   = "ecap",
+               .owner  = THIS_MODULE,
+       },
+       .probe          = ecap_probe,
+       .remove         = __devexit_p(ecap_remove),
+       .suspend        = ecap_suspend,
+       .resume         = ecap_resume,
+};
+
+static int __init ecap_init(void)
+{
+       return platform_driver_register(&ecap_driver);
+}
+
+static void __exit ecap_exit(void)
+{
+       platform_driver_unregister(&ecap_driver);
+}
+
+module_init(ecap_init);
+module_exit(ecap_exit);
+
+MODULE_AUTHOR("Texas Instruments");
+MODULE_DESCRIPTION("Driver for Davinci eCAP peripheral");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:ecap");