[PATCH 2/3] driver/misc: Add Pulse Width Modulator (PWM) driver for freescale
Chunhe Lan
Chunhe.Lan at freescale.com
Tue Jan 10 21:26:42 EST 2012
The PSC913x PWM with the following features:
* 12-bit prescaler for division of clock
* Active-high or active-low configured output
* Interrupts at compare and roll-over
* Programmable pulse width (duty cycle) and interval (period cycle)
A sysfs interface is provided to control the PWM output:
* Set duty cycle and period cycle
echo 1000 > /sys/devices/soc.0/e500.2/ff713000.pwm/duty_ns
echo 5000 > /sys/devices/soc.0/e500.2/ff713000.pwm/period_ns
* Show duty cycle and period cycle
cat /sys/devices/soc.0/e500.2/ff713000.pwm/duty_ns
cat /sys/devices/soc.0/e500.2/ff713000.pwm/period_ns
Signed-off-by: Chunhe Lan <Chunhe.Lan at freescale.com>
---
arch/powerpc/include/asm/fsl_pwm.h | 111 +++++++++
drivers/misc/Kconfig | 11 +
drivers/misc/Makefile | 1 +
drivers/misc/fsl_pwm.c | 471 ++++++++++++++++++++++++++++++++++++
4 files changed, 594 insertions(+), 0 deletions(-)
create mode 100644 arch/powerpc/include/asm/fsl_pwm.h
create mode 100644 drivers/misc/fsl_pwm.c
diff --git a/arch/powerpc/include/asm/fsl_pwm.h b/arch/powerpc/include/asm/fsl_pwm.h
new file mode 100644
index 0000000..a6d3bf5
--- /dev/null
+++ b/arch/powerpc/include/asm/fsl_pwm.h
@@ -0,0 +1,111 @@
+/*
+ * Freescale PWM Register Definitions
+ *
+ * Copyright 2012 Freescale Semiconductor, Inc.
+ *
+ * Author: Chunhe Lan <Chunhe.Lan at freescale.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; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * 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.
+ */
+
+#ifndef __ARCH_FSL_PWM_H
+#define __ARCH_FSL_PWM_H
+
+#define FSL_PWMCR_STOPEN (1 << 25)
+#define FSL_PWMCR_DOZEEN (1 << 24)
+#define FSL_PWMCR_WAITEN (1 << 23)
+#define FSL_PWMCR_DEBUGEN (1 << 22)
+#define FSL_PWMCR_BCTR (1 << 21)
+#define FSL_PWMCR_HCTR (1 << 20)
+#define FSL_PWMCR_POUTC_HIGHT (0 << 18)
+#define FSL_PWMCR_POUTC_LOW (1 << 18)
+#define FSL_PWMCR_CLKSRC (1 << 16)
+#define FSL_PWMCR_PRESCALER(x) (((x - 1) & 0xFFF) << 4)
+#define FSL_MAX_PRESCALER 0x00000FFF
+#define FSL_PWMCR_SWR (1 << 3)
+#define FSL_PWMCR_REPEAT_ONE (0 << 1)
+#define FSL_PWMCR_REPEAT_TWO (1 << 1)
+#define FSL_PWMCR_REPEAT_FOUR (2 << 1)
+#define FSL_PWMCR_REPEAT_EIGHT (3 << 1)
+#define FSL_PWMCR_EN (1 << 0)
+
+#define FSL_PWMSR_ALL_MASK 0x0000007F
+#define FSL_PWMSR_FWE_CMP_ROV_MASK 0x00000070
+#define FSL_PWMSR_FWE (1 << 6)
+#define FSL_PWMSR_CMP (1 << 5)
+#define FSL_PWMSR_ROV (1 << 4)
+#define FSL_PWMSR_FE (1 << 3)
+#define FSL_PWMSR_FIFOAV (7 << 0)
+
+#define FSL_PWMIR (7 << 0)
+#define FSL_PWMIR_CIE (1 << 2)
+#define FSL_PWMIR_RIE (1 << 1)
+#define FSL_PWMIR_FIE (1 << 0)
+
+#define FSL_PMUXCR1_SPI1_ANT_TCXO_PWM_GPIO (3 << 0)
+#define FSL_PMUXCR1_ANT_TCXO_PWM_GPIO (1 << 0)
+#define FSL_PMUXCR2_UART_PWM_GPIO (3 << 28)
+#define FSL_PMUXCR2_PWM_GPIO (1 << 28)
+
+#define FSL_DEVDISR2_PWM1 (1 << 23)
+#define FSL_DEVDISR2_PWM1_EN (0 << 23)
+#define FSL_DEVDISR2_PWM2 (1 << 22)
+#define FSL_DEVDISR2_PWM2_EN (0 << 22)
+
+#define FSL_DEFAULT_IPG_CLK 500000000 /* 500MHz */
+
+struct pwm_reg {
+ u32 pwmcr; /* PWM Control Register */
+ u32 pwmsr; /* PWM Status Register */
+ u32 pwmir; /* PWM Interrupt Register */
+ u32 pwmsar; /* PWM Sample Register */
+ u32 pwmpr; /* PWM Period Register */
+ u32 pwmcnr; /* PWM Counter Register */
+};
+
+struct pwm_device {
+ struct list_head node;
+ struct device dev;
+
+ const char *label;
+ struct clk *clk;
+ int clk_enabled;
+ struct pwm_reg __iomem *regs;
+ int irq;
+
+ unsigned int use_count;
+ unsigned int pwm_id;
+ int duty;
+ int period;
+ int pwmo_invert;
+ void (*enable_pwm_pad)(void);
+ void (*disable_pwm_pad)(void);
+};
+
+struct gubr {
+ u32 res24[0x18];
+ u32 pmuxcr1;
+ u32 pmuxcr2;
+ u32 res3[0x03];
+ u32 devdisr2;
+};
+
+extern int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns);
+extern int pwm_enable(struct pwm_device *pwm);
+extern void pwm_disable(struct pwm_device *pwm);
+extern struct pwm_device *pwm_request(int pwm_id, const char *label);
+extern void pwm_free(struct pwm_device *pwm);
+
+#endif /* __ARCH_FSL_PWM_H */
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 5664696..fbb72df 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -509,4 +509,15 @@ source "drivers/misc/lis3lv02d/Kconfig"
source "drivers/misc/carma/Kconfig"
source "drivers/misc/altera-stapl/Kconfig"
+ config FSL_PWM
+ bool "Freescale PWM support"
+ select PPC_CLOCK
+ default n
+ help
+ This option enables device driver support for the PWM channels
+ on certain Freescale processors(e.g. PSC9131RDB). Pulse Width
+ Modulation is used for purposes including software controlled
+ power-efficient backlights on LCD displays, motor control, and
+ waveform generation and so on.
+
endif # MISC_DEVICES
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index b26495a..b3e0dd4 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -8,6 +8,7 @@ obj-$(CONFIG_AD525X_DPOT_I2C) += ad525x_dpot-i2c.o
obj-$(CONFIG_AD525X_DPOT_SPI) += ad525x_dpot-spi.o
obj-$(CONFIG_INTEL_MID_PTI) += pti.o
obj-$(CONFIG_ATMEL_PWM) += atmel_pwm.o
+obj-$(CONFIG_FSL_PWM) += fsl_pwm.o
obj-$(CONFIG_ATMEL_SSC) += atmel-ssc.o
obj-$(CONFIG_ATMEL_TCLIB) += atmel_tclib.o
obj-$(CONFIG_BMP085) += bmp085.o
diff --git a/drivers/misc/fsl_pwm.c b/drivers/misc/fsl_pwm.c
new file mode 100644
index 0000000..cfe2002
--- /dev/null
+++ b/drivers/misc/fsl_pwm.c
@@ -0,0 +1,471 @@
+/*
+ * Copyright 2012 Freescale Semiconductor, Inc.
+ *
+ * PWM (Pulse Width Modulator) controller driver
+ *
+ * Author: Chunhe Lan <Chunhe.Lan at freescale.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; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * 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 <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/interrupt.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/pwm.h>
+#include <linux/of_platform.h>
+#include <sysdev/fsl_soc.h>
+#include <asm/fsl_pwm.h>
+#include <asm/clock.h>
+#include <asm/prom.h>
+
+static DEFINE_MUTEX(pwm_lock);
+static LIST_HEAD(pwm_list);
+
+static ssize_t show_duty_ns(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct pwm_device *pwm = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%u\n", pwm->duty);
+}
+
+static ssize_t show_period_ns(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct pwm_device *pwm = dev_get_drvdata(dev);
+
+ return sprintf(buf, "%u\n", pwm->period);
+}
+
+static ssize_t store_duty_ns(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct pwm_device *pwm = dev_get_drvdata(dev);
+ unsigned long val;
+
+ if (kstrtoul(buf, 10, &val))
+ return -EINVAL;
+
+ mutex_lock(&pwm_lock);
+
+ pwm->duty = (int)val;
+ if ((pwm->duty < pwm->period) || (pwm->duty == pwm->period)) {
+ pwm_disable(pwm);
+ pwm_config(pwm, pwm->duty, pwm->period);
+ pwm_enable(pwm);
+ }
+
+ mutex_unlock(&pwm_lock);
+
+ return count;
+}
+
+static ssize_t store_period_ns(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct pwm_device *pwm = dev_get_drvdata(dev);
+ unsigned long val;
+
+ if (kstrtoul(buf, 10, &val))
+ return -EINVAL;
+
+ mutex_lock(&pwm_lock);
+
+ pwm->period = (int)val;
+ if ((pwm->duty < pwm->period) || (pwm->duty == pwm->period)) {
+ pwm_disable(pwm);
+ pwm_config(pwm, pwm->duty, pwm->period);
+ pwm_enable(pwm);
+ }
+
+ mutex_unlock(&pwm_lock);
+
+ return count;
+}
+
+static DEVICE_ATTR(duty_ns, S_IRUGO | S_IWUSR,
+ show_duty_ns, store_duty_ns);
+static DEVICE_ATTR(period_ns, S_IRUGO | S_IWUSR,
+ show_period_ns, store_period_ns);
+
+static struct attribute *pwm_attrs[] = {
+ &dev_attr_duty_ns.attr,
+ &dev_attr_period_ns.attr,
+ NULL
+};
+
+static const struct attribute_group pwm_group = {
+ .attrs = pwm_attrs,
+};
+
+int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns)
+{
+ unsigned long long c;
+ unsigned long period_cycles, duty_cycles, prescale;
+ u32 cr;
+
+ if (pwm == NULL || period_ns == 0 || duty_ns > period_ns)
+ return -EINVAL;
+
+ if (pwm->pwmo_invert)
+ duty_ns = period_ns - duty_ns;
+
+ c = clk_get_rate(pwm->clk);
+ c = c * period_ns;
+ do_div(c, 1000000000);
+ period_cycles = c;
+
+ prescale = period_cycles / 0x10000 + 1;
+ if (prescale > FSL_MAX_PRESCALER)
+ return -EINVAL;
+
+ period_cycles /= prescale;
+ c = (unsigned long long)period_cycles * duty_ns;
+ do_div(c, period_ns);
+ duty_cycles = c;
+
+ out_be32(&pwm->regs->pwmsar, duty_cycles);
+ out_be32(&pwm->regs->pwmpr, period_cycles);
+
+ cr = FSL_PWMCR_POUTC_HIGHT | FSL_PWMCR_CLKSRC | FSL_PWMCR_DOZEEN |
+ FSL_PWMCR_WAITEN | FSL_PWMCR_DEBUGEN | FSL_PWMCR_STOPEN;
+ cr |= FSL_PWMCR_PRESCALER(prescale);
+ out_be32(&pwm->regs->pwmcr, cr);
+
+ return 0;
+}
+EXPORT_SYMBOL(pwm_config);
+
+int pwm_enable(struct pwm_device *pwm)
+{
+ unsigned int reg;
+ int rc = 0;
+
+ if (!pwm->clk_enabled) {
+ rc = clk_enable(pwm->clk);
+ if (!rc)
+ pwm->clk_enabled = 1;
+ }
+
+ reg = in_be32(&pwm->regs->pwmcr);
+ reg |= FSL_PWMCR_EN;
+ out_be32(&pwm->regs->pwmcr, reg);
+
+ if (pwm->enable_pwm_pad)
+ pwm->enable_pwm_pad();
+
+ return rc;
+}
+EXPORT_SYMBOL(pwm_enable);
+
+void pwm_disable(struct pwm_device *pwm)
+{
+ unsigned int reg;
+
+ if (pwm->disable_pwm_pad)
+ pwm->disable_pwm_pad();
+
+ reg = in_be32(&pwm->regs->pwmcr);
+ reg &= ~FSL_PWMCR_EN;
+ out_be32(&pwm->regs->pwmcr, reg);
+
+ if (pwm->clk_enabled) {
+ clk_disable(pwm->clk);
+ pwm->clk_enabled = 0;
+ }
+}
+EXPORT_SYMBOL(pwm_disable);
+
+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 irqreturn_t fsl_pwm_irq(int irq, void *context_data)
+{
+ struct pwm_device *fsl_pwm = context_data;
+ u32 status;
+
+ /* Get interrupt events */
+ status = in_be32(&fsl_pwm->regs->pwmsr);
+
+ if (status) {
+ if (status & FSL_PWMSR_FWE)
+ dev_err(&fsl_pwm->dev, "FIFO write error occurred: "
+ "PWMSR 0x%08X\n", status);
+ if (status & FSL_PWMSR_CMP)
+ dev_err(&fsl_pwm->dev, "Compare event occurred: "
+ "PWMSR 0x%08X\n", status);
+ if (status & FSL_PWMSR_ROV)
+ dev_err(&fsl_pwm->dev, "Roll-over event occurred: "
+ "PWMSR 0x%08X\n", status);
+
+ if (status & ~FSL_PWMSR_ALL_MASK)
+ dev_err(&fsl_pwm->dev, "Unknown error: "
+ "PWMSR 0x%08X\n", status);
+
+ /* Clear the events */
+ out_be32(&fsl_pwm->regs->pwmsr, status &
+ FSL_PWMSR_FWE_CMP_ROV_MASK);
+ return IRQ_HANDLED;
+ }
+
+ return IRQ_NONE;
+}
+
+static int __devinit fsl_pwm_probe(struct platform_device *dev)
+{
+ struct device_node *np;
+ struct gubr __iomem *gubr;
+ struct pwm_device *fsl_pwm;
+ struct resource res_mem;
+ struct resource res_irq;
+ struct resource *res = &res_mem;
+ struct resource *irq = &res_irq;
+ const u32 *id;
+ int ret = 0;
+ unsigned long rate;
+
+ fsl_pwm = kzalloc(sizeof(struct pwm_device), GFP_KERNEL);
+ if (fsl_pwm == NULL) {
+ dev_err(&dev->dev, "failed to allocate memory\n");
+ return -ENOMEM;
+ }
+
+ ret = of_address_to_resource(dev->dev.of_node, 0, res);
+ if (ret) {
+ dev_err(&dev->dev, "invalid address\n");
+ goto err_free;
+ }
+
+ if (!request_mem_region(res->start,
+ res->end - res->start + 1, "fsl-pwm")) {
+ dev_err(&dev->dev, "memory request failure\n");
+ ret = -ENXIO;
+ goto err_free;
+ }
+
+ /* IOMAP the entire PWM region */
+ fsl_pwm->regs = ioremap(res->start, res->end - res->start + 1);
+ if (fsl_pwm->regs == NULL) {
+ dev_err(&dev->dev, "failed to ioremap memory region\n");
+ ret = -ENOMEM;
+ goto err_release;
+ }
+
+ fsl_pwm->clk = clk_get(&dev->dev, "pwm-clk");
+ if (IS_ERR(fsl_pwm->clk)) {
+ dev_err(&dev->dev, "failed to get clock\n");
+ ret = PTR_ERR(fsl_pwm->clk);
+ goto err_unmap;
+ }
+
+ rate = fsl_get_sys_freq();
+ if (rate)
+ fsl_pwm->clk->rate_hz = rate;
+ else
+ fsl_pwm->clk->rate_hz = FSL_DEFAULT_IPG_CLK;
+
+ /* Find the id of the pwm */
+ id = of_get_property(dev->dev.of_node, "cell-index", NULL);
+ if (!id) {
+ dev_err(&dev->dev, "failed to get cell-index\n");
+ goto err_unmap;
+ }
+
+ fsl_pwm->pwm_id = *id;
+ fsl_pwm->clk_enabled = 0;
+ fsl_pwm->use_count = 0;
+ fsl_pwm->duty = 0;
+ fsl_pwm->period = 0;
+ fsl_pwm->dev = dev->dev;
+
+ np = of_find_node_by_name(NULL, "global-utilities");
+ if (np) {
+ gubr = of_iomap(np, 0);
+
+ if (fsl_pwm->pwm_id) {
+ clrsetbits_be32(&gubr->pmuxcr2,
+ FSL_PMUXCR2_UART_PWM_GPIO,
+ FSL_PMUXCR2_PWM_GPIO);
+ clrsetbits_be32(&gubr->devdisr2, FSL_DEVDISR2_PWM2,
+ FSL_DEVDISR2_PWM2_EN);
+ } else {
+ clrsetbits_be32(&gubr->pmuxcr1,
+ FSL_PMUXCR1_SPI1_ANT_TCXO_PWM_GPIO,
+ FSL_PMUXCR1_ANT_TCXO_PWM_GPIO);
+ clrsetbits_be32(&gubr->devdisr2, FSL_DEVDISR2_PWM1,
+ FSL_DEVDISR2_PWM1_EN);
+ }
+
+ of_node_put(np);
+ } else {
+ printk(KERN_EMERG "Error: Global Utilities Block Register "
+ "node is not found!\n");
+ goto err_unmap;
+ }
+
+ ret = of_irq_to_resource(dev->dev.of_node, 0, irq);
+ if (ret == NO_IRQ) {
+ dev_warn(&dev->dev, "no IRQ found\n");
+ goto err_unmap;
+ }
+
+ fsl_pwm->irq = irq->start;
+ if (fsl_pwm->irq < 0) {
+ ret = -ENXIO;
+ goto err_unmap;
+ }
+
+ /* Register for PWM Interrupt */
+ ret = request_irq(fsl_pwm->irq, fsl_pwm_irq, 0, "fsl-pwm", fsl_pwm);
+ if (ret != 0)
+ goto err_unmap;
+ else
+ dev_info(&dev->dev,
+ "Freescale PWM Controller driver at 0x%p (irq = %d)\n",
+ fsl_pwm->regs, fsl_pwm->irq);
+
+ /* Register sysfs hooks */
+ ret = sysfs_create_group(&dev->dev.kobj, &pwm_group);
+ if (ret != 0)
+ goto err_unmap;
+
+ dev_set_drvdata(&dev->dev, fsl_pwm);
+
+ mutex_lock(&pwm_lock);
+ list_add_tail(&fsl_pwm->node, &pwm_list);
+ mutex_unlock(&pwm_lock);
+
+ return 0;
+
+err_unmap:
+ iounmap(fsl_pwm->regs);
+err_release:
+ release_mem_region(res->start, res->end - res->start + 1);
+err_free:
+ kfree(fsl_pwm);
+ return ret;
+}
+
+static int __devexit fsl_pwm_remove(struct platform_device *dev)
+{
+ struct pwm_device *fsl_pwm;
+
+ fsl_pwm = dev_get_drvdata(&dev->dev);
+ if (fsl_pwm == NULL)
+ return -ENODEV;
+
+ mutex_lock(&pwm_lock);
+ list_del(&fsl_pwm->node);
+ mutex_unlock(&pwm_lock);
+
+ if (fsl_pwm->regs)
+ iounmap(fsl_pwm->regs);
+
+ dev_set_drvdata(&dev->dev, NULL);
+ clk_put(fsl_pwm->clk);
+ sysfs_remove_group(&dev->dev.kobj, &pwm_group);
+ kfree(fsl_pwm);
+
+ return 0;
+}
+
+static const struct of_device_id fsl_pwm_match[] = {
+ {
+ .compatible = "fsl,psc9131-pwm",
+ },
+ {},
+};
+
+static struct platform_driver fsl_pwm_driver = {
+ .driver = {
+ .name = "fsl-pwm",
+ .of_match_table = fsl_pwm_match,
+ },
+ .probe = fsl_pwm_probe,
+ .remove = fsl_pwm_remove,
+};
+
+static int __init fsl_pwm_init(void)
+{
+ int ret;
+
+ ret = platform_driver_register(&fsl_pwm_driver);
+ if (ret)
+ printk(KERN_ERR "fsl-pwm: Failed to register platform "
+ "driver\n");
+
+ return ret;
+}
+
+static void __exit fsl_pwm_exit(void)
+{
+ platform_driver_unregister(&fsl_pwm_driver);
+}
+
+module_init(fsl_pwm_init);
+module_exit(fsl_pwm_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Chunhe Lan <Chunhe.Lan at freescale.com>");
+MODULE_DESCRIPTION("Freescale PWM Controller driver");
--
1.5.6.5
More information about the Linuxppc-dev
mailing list