diff options
author | Felix Fietkau <nbd@openwrt.org> | 2010-06-26 20:42:58 +0000 |
---|---|---|
committer | Felix Fietkau <nbd@openwrt.org> | 2010-06-26 20:42:58 +0000 |
commit | a082943b09f4f707990ad0ac6326df8480507f02 (patch) | |
tree | de32e4def600e56134cd085a7447cb6620542078 /target/linux/generic/files/drivers/pwm | |
parent | 67fbcc7bd42cc07b1f3c6b5c9f2db37647178f25 (diff) | |
download | upstream-a082943b09f4f707990ad0ac6326df8480507f02.tar.gz upstream-a082943b09f4f707990ad0ac6326df8480507f02.tar.bz2 upstream-a082943b09f4f707990ad0ac6326df8480507f02.zip |
rename target/linux/generic-2.6 to generic
git-svn-id: svn://svn.openwrt.org/openwrt/trunk@21952 3c298f89-4303-0410-b956-a3cf2f4a3e73
Diffstat (limited to 'target/linux/generic/files/drivers/pwm')
-rw-r--r-- | target/linux/generic/files/drivers/pwm/Kconfig | 31 | ||||
-rw-r--r-- | target/linux/generic/files/drivers/pwm/Makefile | 6 | ||||
-rw-r--r-- | target/linux/generic/files/drivers/pwm/atmel-pwm.c | 592 | ||||
-rw-r--r-- | target/linux/generic/files/drivers/pwm/gpio.c | 298 | ||||
-rw-r--r-- | target/linux/generic/files/drivers/pwm/pwm.c | 643 |
5 files changed, 1570 insertions, 0 deletions
diff --git a/target/linux/generic/files/drivers/pwm/Kconfig b/target/linux/generic/files/drivers/pwm/Kconfig new file mode 100644 index 0000000000..1c24e11077 --- /dev/null +++ b/target/linux/generic/files/drivers/pwm/Kconfig @@ -0,0 +1,31 @@ +# +# PWM infrastructure and devices +# + +menuconfig GENERIC_PWM + tristate "PWM Support" + depends on SYSFS + help + This enables PWM support through the generic PWM library. + If unsure, say N. + +if GENERIC_PWM + +config ATMEL_PWM + tristate "Atmel AT32/AT91 PWM support" + depends on AVR32 || ARCH_AT91 + help + This option enables device driver support for the PWMC + peripheral channels found on certain Atmel processors. + Pulse Width Modulation is used many for purposes, including + software controlled power-efficient backlights on LCD + displays, motor control, and waveform generation. If + unsure, say N. + +config GPIO_PWM + tristate "PWM emulation using GPIO" + help + This option enables a single-channel PWM device using + a kernel interval timer and a GPIO pin. If unsure, say N. + +endif diff --git a/target/linux/generic/files/drivers/pwm/Makefile b/target/linux/generic/files/drivers/pwm/Makefile new file mode 100644 index 0000000000..e8cacc5740 --- /dev/null +++ b/target/linux/generic/files/drivers/pwm/Makefile @@ -0,0 +1,6 @@ +# +# Makefile for pwm devices +# +obj-y := pwm.o +obj-$(CONFIG_ATMEL_PWM) += atmel-pwm.o +obj-$(CONFIG_GPIO_PWM) += gpio.o diff --git a/target/linux/generic/files/drivers/pwm/atmel-pwm.c b/target/linux/generic/files/drivers/pwm/atmel-pwm.c new file mode 100644 index 0000000000..158bb922c1 --- /dev/null +++ b/target/linux/generic/files/drivers/pwm/atmel-pwm.c @@ -0,0 +1,592 @@ +/* + * drivers/pwm/atmel-pwm.c + * + * Copyright (C) 2010 Bill Gatliff <bgat@billgatliff.com> + * Copyright (C) 2007 David Brownell + * + * This program is free software; you may redistribute and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/pwm/pwm.h> + +enum { + /* registers common to the PWMC peripheral */ + PWMC_MR = 0, + PWMC_ENA = 4, + PWMC_DIS = 8, + PWMC_SR = 0xc, + PWMC_IER = 0x10, + PWMC_IDR = 0x14, + PWMC_IMR = 0x18, + PWMC_ISR = 0x1c, + + /* registers per each PWMC channel */ + PWMC_CMR = 0, + PWMC_CDTY = 4, + PWMC_CPRD = 8, + PWMC_CCNT = 0xc, + PWMC_CUPD = 0x10, + + /* how to find each channel */ + PWMC_CHAN_BASE = 0x200, + PWMC_CHAN_STRIDE = 0x20, + + /* CMR bits of interest */ + PWMC_CMR_CPD = 10, + PWMC_CMR_CPOL = 9, + PWMC_CMR_CALG = 8, + PWMC_CMR_CPRE_MASK = 0xf, +}; + +struct atmel_pwm { + struct pwm_device pwm; + spinlock_t lock; + void __iomem *iobase; + struct clk *clk; + u32 *sync_mask; + int irq; + u32 ccnt_mask; +}; + +static inline struct atmel_pwm *to_atmel_pwm(const struct pwm_channel *p) +{ + return container_of(p->pwm, struct atmel_pwm, pwm); +} + +static inline void +pwmc_writel(const struct atmel_pwm *p, + unsigned offset, u32 val) +{ + __raw_writel(val, p->iobase + offset); +} + +static inline u32 +pwmc_readl(const struct atmel_pwm *p, + unsigned offset) +{ + return __raw_readl(p->iobase + offset); +} + +static inline void +pwmc_chan_writel(const struct pwm_channel *p, + u32 offset, u32 val) +{ + const struct atmel_pwm *ap = to_atmel_pwm(p); + + if (PWMC_CMR == offset) + val &= ((1 << PWMC_CMR_CPD) + | (1 << PWMC_CMR_CPOL) + | (1 << PWMC_CMR_CALG) + | (PWMC_CMR_CPRE_MASK)); + else + val &= ap->ccnt_mask; + + pwmc_writel(ap, offset + PWMC_CHAN_BASE + + (p->chan * PWMC_CHAN_STRIDE), val); +} + +static inline u32 +pwmc_chan_readl(const struct pwm_channel *p, + u32 offset) +{ + const struct atmel_pwm *ap = to_atmel_pwm(p); + + return pwmc_readl(ap, offset + PWMC_CHAN_BASE + + (p->chan * PWMC_CHAN_STRIDE)); +} + +static inline int +__atmel_pwm_is_on(struct pwm_channel *p) +{ + struct atmel_pwm *ap = to_atmel_pwm(p); + return (pwmc_readl(ap, PWMC_SR) & (1 << p->chan)) ? 1 : 0; +} + +static inline void +__atmel_pwm_unsynchronize(struct pwm_channel *p, + struct pwm_channel *to_p) +{ + const struct atmel_pwm *ap = to_atmel_pwm(p); + int wchan; + + if (to_p) { + ap->sync_mask[p->chan] &= ~(1 << to_p->chan); + ap->sync_mask[to_p->chan] &= ~(1 << p->chan); + goto done; + } + + ap->sync_mask[p->chan] = 0; + for (wchan = 0; wchan < ap->pwm.nchan; wchan++) + ap->sync_mask[wchan] &= ~(1 << p->chan); +done: + dev_dbg(p->pwm->dev, "sync_mask %x\n", ap->sync_mask[p->chan]); +} + +static inline void +__atmel_pwm_synchronize(struct pwm_channel *p, + struct pwm_channel *to_p) +{ + const struct atmel_pwm *ap = to_atmel_pwm(p); + + if (!to_p) + return; + + ap->sync_mask[p->chan] |= (1 << to_p->chan); + ap->sync_mask[to_p->chan] |= (1 << p->chan); + + dev_dbg(p->pwm->dev, "sync_mask %x\n", ap->sync_mask[p->chan]); +} + +static inline void +__atmel_pwm_stop(struct pwm_channel *p) +{ + struct atmel_pwm *ap = to_atmel_pwm(p); + u32 chid = 1 << p->chan; + + pwmc_writel(ap, PWMC_DIS, ap->sync_mask[p->chan] | chid); +} + +static inline void +__atmel_pwm_start(struct pwm_channel *p) +{ + struct atmel_pwm *ap = to_atmel_pwm(p); + u32 chid = 1 << p->chan; + + pwmc_writel(ap, PWMC_ENA, ap->sync_mask[p->chan] | chid); +} + +static int +atmel_pwm_synchronize(struct pwm_channel *p, + struct pwm_channel *to_p) +{ + unsigned long flags; + spin_lock_irqsave(&p->lock, flags); + __atmel_pwm_synchronize(p, to_p); + spin_unlock_irqrestore(&p->lock, flags); + return 0; +} + +static int +atmel_pwm_unsynchronize(struct pwm_channel *p, + struct pwm_channel *from_p) +{ + unsigned long flags; + spin_lock_irqsave(&p->lock, flags); + __atmel_pwm_unsynchronize(p, from_p); + spin_unlock_irqrestore(&p->lock, flags); + return 0; +} + +static inline int +__atmel_pwm_config_polarity(struct pwm_channel *p, + struct pwm_channel_config *c) +{ + u32 cmr = pwmc_chan_readl(p, PWMC_CMR); + + if (c->polarity) + cmr &= ~BIT(PWMC_CMR_CPOL); + else + cmr |= BIT(PWMC_CMR_CPOL); + pwmc_chan_writel(p, PWMC_CMR, cmr); + p->active_high = c->polarity ? 1 : 0; + + dev_dbg(p->pwm->dev, "polarity %d\n", c->polarity); + return 0; +} + +static inline int +__atmel_pwm_config_duty_ticks(struct pwm_channel *p, + struct pwm_channel_config *c) +{ + u32 cmr, cprd, cpre, cdty; + + cmr = pwmc_chan_readl(p, PWMC_CMR); + cprd = pwmc_chan_readl(p, PWMC_CPRD); + + cpre = cmr & PWMC_CMR_CPRE_MASK; + cmr &= ~BIT(PWMC_CMR_CPD); + + cdty = cprd - (c->duty_ticks >> cpre); + + p->duty_ticks = c->duty_ticks; + + if (__atmel_pwm_is_on(p)) { + pwmc_chan_writel(p, PWMC_CMR, cmr); + pwmc_chan_writel(p, PWMC_CUPD, cdty); + } else + pwmc_chan_writel(p, PWMC_CDTY, cdty); + + dev_dbg(p->pwm->dev, "duty_ticks = %lu cprd = %x" + " cdty = %x cpre = %x\n", p->duty_ticks, + cprd, cdty, cpre); + + return 0; +} + +static inline int +__atmel_pwm_config_period_ticks(struct pwm_channel *p, + struct pwm_channel_config *c) +{ + u32 cmr, cprd, cpre; + + cpre = fls(c->period_ticks); + if (cpre < 16) + cpre = 0; + else { + cpre -= 15; + if (cpre > 10) + return -EINVAL; + } + + cmr = pwmc_chan_readl(p, PWMC_CMR); + cmr &= ~PWMC_CMR_CPRE_MASK; + cmr |= cpre; + + cprd = c->period_ticks >> cpre; + + pwmc_chan_writel(p, PWMC_CMR, cmr); + pwmc_chan_writel(p, PWMC_CPRD, cprd); + p->period_ticks = c->period_ticks; + + dev_dbg(p->pwm->dev, "period_ticks = %lu cprd = %x cpre = %x\n", + p->period_ticks, cprd, cpre); + + return 0; +} + +static int +atmel_pwm_config_nosleep(struct pwm_channel *p, + struct pwm_channel_config *c) +{ + int ret = 0; + unsigned long flags; + + spin_lock_irqsave(&p->lock, flags); + + switch (c->config_mask) { + + case PWM_CONFIG_DUTY_TICKS: + __atmel_pwm_config_duty_ticks(p, c); + break; + + case PWM_CONFIG_STOP: + __atmel_pwm_stop(p); + break; + + case PWM_CONFIG_START: + __atmel_pwm_start(p); + break; + + case PWM_CONFIG_POLARITY: + __atmel_pwm_config_polarity(p, c); + break; + + default: + ret = -EINVAL; + break; + } + + spin_unlock_irqrestore(&p->lock, flags); + return ret; +} + +static int +atmel_pwm_stop_sync(struct pwm_channel *p) +{ + struct atmel_pwm *ap = container_of(p->pwm, struct atmel_pwm, pwm); + int ret; + int was_on = __atmel_pwm_is_on(p); + + if (was_on) { + do { + init_completion(&p->complete); + set_bit(FLAG_STOP, &p->flags); + pwmc_writel(ap, PWMC_IER, 1 << p->chan); + + dev_dbg(p->pwm->dev, "waiting on stop_sync completion...\n"); + + ret = wait_for_completion_interruptible(&p->complete); + + dev_dbg(p->pwm->dev, "stop_sync complete (%d)\n", ret); + + if (ret) + return ret; + } while (p->flags & BIT(FLAG_STOP)); + } + + return was_on; +} + +static int +atmel_pwm_config(struct pwm_channel *p, + struct pwm_channel_config *c) +{ + int was_on = 0; + + if (p->pwm->config_nosleep) { + if (!p->pwm->config_nosleep(p, c)) + return 0; + } + + might_sleep(); + + dev_dbg(p->pwm->dev, "config_mask %x\n", c->config_mask); + + was_on = atmel_pwm_stop_sync(p); + if (was_on < 0) + return was_on; + + if (c->config_mask & PWM_CONFIG_PERIOD_TICKS) { + __atmel_pwm_config_period_ticks(p, c); + if (!(c->config_mask & PWM_CONFIG_DUTY_TICKS)) { + struct pwm_channel_config d = { + .config_mask = PWM_CONFIG_DUTY_TICKS, + .duty_ticks = p->duty_ticks, + }; + __atmel_pwm_config_duty_ticks(p, &d); + } + } + + if (c->config_mask & PWM_CONFIG_DUTY_TICKS) + __atmel_pwm_config_duty_ticks(p, c); + + if (c->config_mask & PWM_CONFIG_POLARITY) + __atmel_pwm_config_polarity(p, c); + + if ((c->config_mask & PWM_CONFIG_START) + || (was_on && !(c->config_mask & PWM_CONFIG_STOP))) + __atmel_pwm_start(p); + + return 0; +} + +static void +__atmel_pwm_set_callback(struct pwm_channel *p, + pwm_callback_t callback) +{ + struct atmel_pwm *ap = container_of(p->pwm, struct atmel_pwm, pwm); + + p->callback = callback; + pwmc_writel(ap, p->callback ? PWMC_IER : PWMC_IDR, 1 << p->chan); +} + +static int +atmel_pwm_set_callback(struct pwm_channel *p, + pwm_callback_t callback) +{ + struct atmel_pwm *ap = to_atmel_pwm(p); + unsigned long flags; + + spin_lock_irqsave(&ap->lock, flags); + __atmel_pwm_set_callback(p, callback); + spin_unlock_irqrestore(&ap->lock, flags); + + return 0; +} + +static int +atmel_pwm_request(struct pwm_channel *p) +{ + struct atmel_pwm *ap = to_atmel_pwm(p); + unsigned long flags; + + spin_lock_irqsave(&p->lock, flags); + clk_enable(ap->clk); + p->tick_hz = clk_get_rate(ap->clk); + __atmel_pwm_unsynchronize(p, NULL); + __atmel_pwm_stop(p); + spin_unlock_irqrestore(&p->lock, flags); + + return 0; +} + +static void +atmel_pwm_free(struct pwm_channel *p) +{ + struct atmel_pwm *ap = to_atmel_pwm(p); + clk_disable(ap->clk); +} + +static irqreturn_t +atmel_pwmc_irq(int irq, void *data) +{ + struct atmel_pwm *ap = data; + struct pwm_channel *p; + u32 isr; + int chid; + unsigned long flags; + + spin_lock_irqsave(&ap->lock, flags); + + isr = pwmc_readl(ap, PWMC_ISR); + for (chid = 0; isr; chid++, isr >>= 1) { + p = &ap->pwm.channels[chid]; + if (isr & 1) { + if (p->callback) + p->callback(p); + if (p->flags & BIT(FLAG_STOP)) { + __atmel_pwm_stop(p); + clear_bit(FLAG_STOP, &p->flags); + } + complete_all(&p->complete); + } + } + + spin_unlock_irqrestore(&ap->lock, flags); + + return IRQ_HANDLED; +} + +static int __devinit +atmel_pwmc_probe(struct platform_device *pdev) +{ + struct atmel_pwm *ap; + struct resource *r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + int ret = 0; + + ap = kzalloc(sizeof(*ap), GFP_KERNEL); + if (!ap) { + ret = -ENOMEM; + goto err_atmel_pwm_alloc; + } + + spin_lock_init(&ap->lock); + platform_set_drvdata(pdev, ap); + + ap->pwm.dev = &pdev->dev; + ap->pwm.bus_id = dev_name(&pdev->dev); + + ap->pwm.nchan = 4; /* TODO: true only for SAM9263 and AP7000 */ + ap->ccnt_mask = 0xffffUL; /* TODO: true only for SAM9263 */ + + ap->sync_mask = kzalloc(ap->pwm.nchan * sizeof(u32), GFP_KERNEL); + if (!ap->sync_mask) { + ret = -ENOMEM; + goto err_alloc_sync_masks; + } + + ap->pwm.owner = THIS_MODULE; + ap->pwm.request = atmel_pwm_request; + ap->pwm.free = atmel_pwm_free; + ap->pwm.config_nosleep = atmel_pwm_config_nosleep; + ap->pwm.config = atmel_pwm_config; + ap->pwm.synchronize = atmel_pwm_synchronize; + ap->pwm.unsynchronize = atmel_pwm_unsynchronize; + ap->pwm.set_callback = atmel_pwm_set_callback; + + ap->clk = clk_get(&pdev->dev, "pwm_clk"); + if (PTR_ERR(ap->clk)) { + ret = -ENODEV; + goto err_clk_get; + } + + ap->iobase = ioremap_nocache(r->start, r->end - r->start + 1); + if (!ap->iobase) { + ret = -ENODEV; + goto err_ioremap; + } + + clk_enable(ap->clk); + pwmc_writel(ap, PWMC_DIS, -1); + pwmc_writel(ap, PWMC_IDR, -1); + clk_disable(ap->clk); + + ap->irq = platform_get_irq(pdev, 0); + if (ap->irq != -ENXIO) { + ret = request_irq(ap->irq, atmel_pwmc_irq, 0, + ap->pwm.bus_id, ap); + if (ret) + goto err_request_irq; + } + + ret = pwm_register(&ap->pwm); + if (ret) + goto err_pwm_register; + + return 0; + +err_pwm_register: + if (ap->irq != -ENXIO) + free_irq(ap->irq, ap); +err_request_irq: + iounmap(ap->iobase); +err_ioremap: + clk_put(ap->clk); +err_clk_get: + platform_set_drvdata(pdev, NULL); +err_alloc_sync_masks: + kfree(ap); +err_atmel_pwm_alloc: + return ret; +} + +static int __devexit +atmel_pwmc_remove(struct platform_device *pdev) +{ + struct atmel_pwm *ap = platform_get_drvdata(pdev); + int ret; + + /* TODO: what can we do if this fails? */ + ret = pwm_unregister(&ap->pwm); + + clk_enable(ap->clk); + pwmc_writel(ap, PWMC_IDR, -1); + pwmc_writel(ap, PWMC_DIS, -1); + clk_disable(ap->clk); + + if (ap->irq != -ENXIO) + free_irq(ap->irq, ap); + + clk_put(ap->clk); + iounmap(ap->iobase); + + kfree(ap); + + return 0; +} + +static struct platform_driver atmel_pwm_driver = { + .driver = { + .name = "atmel_pwmc", + .owner = THIS_MODULE, + }, + .probe = atmel_pwmc_probe, + .remove = __devexit_p(atmel_pwmc_remove), +}; + +static int __init atmel_pwm_init(void) +{ + return platform_driver_register(&atmel_pwm_driver); +} +module_init(atmel_pwm_init); + +static void __exit atmel_pwm_exit(void) +{ + platform_driver_unregister(&atmel_pwm_driver); +} +module_exit(atmel_pwm_exit); + +MODULE_AUTHOR("Bill Gatliff <bgat@billgatliff.com>"); +MODULE_DESCRIPTION("Driver for Atmel PWMC peripheral"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:atmel_pwmc"); diff --git a/target/linux/generic/files/drivers/pwm/gpio.c b/target/linux/generic/files/drivers/pwm/gpio.c new file mode 100644 index 0000000000..dff5d1d627 --- /dev/null +++ b/target/linux/generic/files/drivers/pwm/gpio.c @@ -0,0 +1,298 @@ +/* + * drivers/pwm/gpio.c + * + * Models a single-channel PWM device using a timer and a GPIO pin. + * + * Copyright (C) 2010 Bill Gatliff <bgat@billgatliff.com> + * + * This program is free software; you may redistribute and/or modify + * it under the terms of the GNU General Public License Version 2, as + * published by the Free Software Foundation. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/hrtimer.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/workqueue.h> +#include <linux/gpio.h> +#include <linux/slab.h> +#include <linux/pwm/pwm.h> + +struct gpio_pwm { + struct pwm_device pwm; + struct hrtimer timer; + struct work_struct work; + pwm_callback_t callback; + int gpio; + unsigned long polarity : 1; + unsigned long active : 1; +}; + +static inline struct gpio_pwm *to_gpio_pwm(const struct pwm_channel *p) +{ + return container_of(p->pwm, struct gpio_pwm, pwm); +} + +static void +gpio_pwm_work (struct work_struct *work) +{ + struct gpio_pwm *gp = container_of(work, struct gpio_pwm, work); + + if (gp->active) + gpio_direction_output(gp->gpio, gp->polarity ? 1 : 0); + else + gpio_direction_output(gp->gpio, gp->polarity ? 0 : 1); +} + +static enum hrtimer_restart +gpio_pwm_timeout(struct hrtimer *t) +{ + struct gpio_pwm *gp = container_of(t, struct gpio_pwm, timer); + ktime_t tnew; + + if (unlikely(gp->pwm.channels[0].duty_ticks == 0)) + gp->active = 0; + else if (unlikely(gp->pwm.channels[0].duty_ticks + == gp->pwm.channels[0].period_ticks)) + gp->active = 1; + else + gp->active ^= 1; + + if (gpio_cansleep(gp->gpio)) + schedule_work(&gp->work); + else + gpio_pwm_work(&gp->work); + + if (!gp->active && gp->pwm.channels[0].callback) + gp->pwm.channels[0].callback(&gp->pwm.channels[0]); + + if (unlikely(!gp->active && + (gp->pwm.channels[0].flags & BIT(FLAG_STOP)))) { + clear_bit(FLAG_STOP, &gp->pwm.channels[0].flags); + complete_all(&gp->pwm.channels[0].complete); + return HRTIMER_NORESTART; + } + + if (gp->active) + tnew = ktime_set(0, gp->pwm.channels[0].duty_ticks); + else + tnew = ktime_set(0, gp->pwm.channels[0].period_ticks + - gp->pwm.channels[0].duty_ticks); + hrtimer_start(&gp->timer, tnew, HRTIMER_MODE_REL); + + return HRTIMER_NORESTART; +} + +static void gpio_pwm_start(struct pwm_channel *p) +{ + struct gpio_pwm *gp = to_gpio_pwm(p); + + gp->active = 0; + gpio_pwm_timeout(&gp->timer); +} + +static int +gpio_pwm_config_nosleep(struct pwm_channel *p, + struct pwm_channel_config *c) +{ + struct gpio_pwm *gp = to_gpio_pwm(p); + int ret = 0; + unsigned long flags; + + spin_lock_irqsave(&p->lock, flags); + + switch (c->config_mask) { + + case PWM_CONFIG_DUTY_TICKS: + p->duty_ticks = c->duty_ticks; + break; + + case PWM_CONFIG_START: + if (!hrtimer_active(&gp->timer)) { + gpio_pwm_start(p); + } + break; + default: + ret = -EINVAL; + break; + } + + spin_unlock_irqrestore(&p->lock, flags); + return ret; +} + +static int +gpio_pwm_stop_sync(struct pwm_channel *p) +{ + struct gpio_pwm *gp = to_gpio_pwm(p); + int ret; + int was_on = hrtimer_active(&gp->timer); + + if (was_on) { + do { + init_completion(&p->complete); + set_bit(FLAG_STOP, &p->flags); + ret = wait_for_completion_interruptible(&p->complete); + if (ret) + return ret; + } while (p->flags & BIT(FLAG_STOP)); + } + + return was_on; +} + +static int +gpio_pwm_config(struct pwm_channel *p, + struct pwm_channel_config *c) +{ + struct gpio_pwm *gp = to_gpio_pwm(p); + int was_on = 0; + + if (p->pwm->config_nosleep) { + if (!p->pwm->config_nosleep(p, c)) + return 0; + } + + might_sleep(); + + was_on = gpio_pwm_stop_sync(p); + if (was_on < 0) + return was_on; + + if (c->config_mask & PWM_CONFIG_PERIOD_TICKS) + p->period_ticks = c->period_ticks; + + if (c->config_mask & PWM_CONFIG_DUTY_TICKS) + p->duty_ticks = c->duty_ticks; + + if (c->config_mask & PWM_CONFIG_POLARITY) { + gp->polarity = c->polarity ? 1 : 0; + p->active_high = gp->polarity; + } + + if ((c->config_mask & PWM_CONFIG_START) + || (was_on && !(c->config_mask & PWM_CONFIG_STOP))) + gpio_pwm_start(p); + + return 0; +} + +static int +gpio_pwm_set_callback(struct pwm_channel *p, + pwm_callback_t callback) +{ + struct gpio_pwm *gp = to_gpio_pwm(p); + gp->callback = callback; + return 0; +} + +static int +gpio_pwm_request(struct pwm_channel *p) +{ + p->tick_hz = 1000000000UL; + return 0; +} + +static int __devinit +gpio_pwm_probe(struct platform_device *pdev) +{ + struct gpio_pwm *gp; + struct gpio_pwm_platform_data *gpd = pdev->dev.platform_data; + int ret = 0; + + /* TODO: create configfs entries, so users can assign GPIOs to + * PWMs at runtime instead of creating a platform_device + * specification and rebuilding their kernel */ + + if (!gpd || gpio_request(gpd->gpio, dev_name(&pdev->dev))) + return -EINVAL; + + gp = kzalloc(sizeof(*gp), GFP_KERNEL); + if (!gp) { + ret = -ENOMEM; + goto err_alloc; + } + + platform_set_drvdata(pdev, gp); + + gp->pwm.dev = &pdev->dev; + gp->pwm.bus_id = dev_name(&pdev->dev); + gp->pwm.nchan = 1; + gp->gpio = gpd->gpio; + + INIT_WORK(&gp->work, gpio_pwm_work); + + hrtimer_init(&gp->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + gp->timer.function = gpio_pwm_timeout; + + gp->pwm.owner = THIS_MODULE; + gp->pwm.config_nosleep = gpio_pwm_config_nosleep; + gp->pwm.config = gpio_pwm_config; + gp->pwm.request = gpio_pwm_request; + gp->pwm.set_callback = gpio_pwm_set_callback; + + ret = pwm_register(&gp->pwm); + if (ret) + goto err_pwm_register; + + return 0; + +err_pwm_register: + platform_set_drvdata(pdev, 0); + kfree(gp); +err_alloc: + return ret; +} + +static int __devexit +gpio_pwm_remove(struct platform_device *pdev) +{ + struct gpio_pwm *gp = platform_get_drvdata(pdev); + int ret; + + ret = pwm_unregister(&gp->pwm); + hrtimer_cancel(&gp->timer); + cancel_work_sync(&gp->work); + platform_set_drvdata(pdev, 0); + kfree(gp); + + return 0; +} + +static struct platform_driver gpio_pwm_driver = { + .driver = { + .name = "gpio_pwm", + .owner = THIS_MODULE, + }, + .probe = gpio_pwm_probe, + .remove = __devexit_p(gpio_pwm_remove), +}; + +static int __init gpio_pwm_init(void) +{ + return platform_driver_register(&gpio_pwm_driver); +} +module_init(gpio_pwm_init); + +static void __exit gpio_pwm_exit(void) +{ + platform_driver_unregister(&gpio_pwm_driver); +} +module_exit(gpio_pwm_exit); + +MODULE_AUTHOR("Bill Gatliff <bgat@billgatliff.com>"); +MODULE_DESCRIPTION("PWM output using GPIO and a high-resolution timer"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:gpio_pwm"); diff --git a/target/linux/generic/files/drivers/pwm/pwm.c b/target/linux/generic/files/drivers/pwm/pwm.c new file mode 100644 index 0000000000..c1596e9e7e --- /dev/null +++ b/target/linux/generic/files/drivers/pwm/pwm.c @@ -0,0 +1,643 @@ +/* + * drivers/pwm/pwm.c + * + * Copyright (C) 2010 Bill Gatliff <bgat@billgatliff.com> + * + * This program is free software; you may redistribute and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/spinlock.h> +#include <linux/fs.h> +#include <linux/completion.h> +#include <linux/workqueue.h> +#include <linux/list.h> +#include <linux/sched.h> +#include <linux/slab.h> /*kcalloc, kfree since 2.6.34 */ +#include <linux/pwm/pwm.h> + +static int __pwm_create_sysfs(struct pwm_device *pwm); + +static const char *REQUEST_SYSFS = "sysfs"; +static LIST_HEAD(pwm_device_list); +static DEFINE_MUTEX(device_list_mutex); +static struct class pwm_class; +static struct workqueue_struct *pwm_handler_workqueue; + +int pwm_register(struct pwm_device *pwm) +{ + struct pwm_channel *p; + int wchan; + int ret; + + spin_lock_init(&pwm->list_lock); + + p = kcalloc(pwm->nchan, sizeof(*p), GFP_KERNEL); + if (!p) + return -ENOMEM; + + for (wchan = 0; wchan < pwm->nchan; wchan++) { + spin_lock_init(&p[wchan].lock); + init_completion(&p[wchan].complete); + p[wchan].chan = wchan; + p[wchan].pwm = pwm; + } + + pwm->channels = p; + + mutex_lock(&device_list_mutex); + + list_add_tail(&pwm->list, &pwm_device_list); + ret = __pwm_create_sysfs(pwm); + if (ret) { + mutex_unlock(&device_list_mutex); + goto err_create_sysfs; + } + + mutex_unlock(&device_list_mutex); + + dev_info(pwm->dev, "%d channel%s\n", pwm->nchan, + pwm->nchan > 1 ? "s" : ""); + return 0; + +err_create_sysfs: + kfree(p); + + return ret; +} +EXPORT_SYMBOL(pwm_register); + +static int __match_device(struct device *dev, void *data) +{ + return dev_get_drvdata(dev) == data; +} + +int pwm_unregister(struct pwm_device *pwm) +{ + int wchan; + struct device *dev; + + mutex_lock(&device_list_mutex); + + for (wchan = 0; wchan < pwm->nchan; wchan++) { + if (pwm->channels[wchan].flags & BIT(FLAG_REQUESTED)) { + mutex_unlock(&device_list_mutex); + return -EBUSY; + } + } + + for (wchan = 0; wchan < pwm->nchan; wchan++) { + dev = class_find_device(&pwm_class, NULL, + &pwm->channels[wchan], + __match_device); + if (dev) { + put_device(dev); + device_unregister(dev); + } + } + + kfree(pwm->channels); + list_del(&pwm->list); + mutex_unlock(&device_list_mutex); + + return 0; +} +EXPORT_SYMBOL(pwm_unregister); + +static struct pwm_device * +__pwm_find_device(const char *bus_id) +{ + struct pwm_device *p; + + list_for_each_entry(p, &pwm_device_list, list) { + if (!strcmp(bus_id, p->bus_id)) + return p; + } + return NULL; +} + +static int +__pwm_request_channel(struct pwm_channel *p, + const char *requester) +{ + int ret; + + if (test_and_set_bit(FLAG_REQUESTED, &p->flags)) + return -EBUSY; + + if (p->pwm->request) { + ret = p->pwm->request(p); + if (ret) { + clear_bit(FLAG_REQUESTED, &p->flags); + return ret; + } + } + + p->requester = requester; + if (!strcmp(requester, REQUEST_SYSFS)) + p->pid = current->pid; + + return 0; +} + +struct pwm_channel * +pwm_request(const char *bus_id, + int chan, + const char *requester) +{ + struct pwm_device *p; + int ret; + + mutex_lock(&device_list_mutex); + + p = __pwm_find_device(bus_id); + if (!p || chan >= p->nchan) + goto err_no_device; + + if (!try_module_get(p->owner)) + goto err_module_get_failed; + + ret = __pwm_request_channel(&p->channels[chan], requester); + if (ret) + goto err_request_failed; + + mutex_unlock(&device_list_mutex); + return &p->channels[chan]; + +err_request_failed: + module_put(p->owner); +err_module_get_failed: +err_no_device: + mutex_unlock(&device_list_mutex); + return NULL; +} +EXPORT_SYMBOL(pwm_request); + +void pwm_free(struct pwm_channel *p) +{ + mutex_lock(&device_list_mutex); + + if (!test_and_clear_bit(FLAG_REQUESTED, &p->flags)) + goto done; + + pwm_stop(p); + pwm_unsynchronize(p, NULL); + pwm_set_handler(p, NULL, NULL); + + if (p->pwm->free) + p->pwm->free(p); + module_put(p->pwm->owner); +done: + mutex_unlock(&device_list_mutex); +} +EXPORT_SYMBOL(pwm_free); + +unsigned long pwm_ns_to_ticks(struct pwm_channel *p, + unsigned long nsecs) +{ + unsigned long long ticks; + + ticks = nsecs; + ticks *= p->tick_hz; + do_div(ticks, 1000000000); + return ticks; +} +EXPORT_SYMBOL(pwm_ns_to_ticks); + +unsigned long pwm_ticks_to_ns(struct pwm_channel *p, + unsigned long ticks) +{ + unsigned long long ns; + + if (!p->tick_hz) + return 0; + + ns = ticks; + ns *= 1000000000UL; + do_div(ns, p->tick_hz); + return ns; +} +EXPORT_SYMBOL(pwm_ticks_to_ns); + +static void +pwm_config_ns_to_ticks(struct pwm_channel *p, + struct pwm_channel_config *c) +{ + if (c->config_mask & PWM_CONFIG_PERIOD_NS) { + c->period_ticks = pwm_ns_to_ticks(p, c->period_ns); + c->config_mask &= ~PWM_CONFIG_PERIOD_NS; + c->config_mask |= PWM_CONFIG_PERIOD_TICKS; + } + + if (c->config_mask & PWM_CONFIG_DUTY_NS) { + c->duty_ticks = pwm_ns_to_ticks(p, c->duty_ns); + c->config_mask &= ~PWM_CONFIG_DUTY_NS; + c->config_mask |= PWM_CONFIG_DUTY_TICKS; + } +} + +static void +pwm_config_percent_to_ticks(struct pwm_channel *p, + struct pwm_channel_config *c) +{ + if (c->config_mask & PWM_CONFIG_DUTY_PERCENT) { + if (c->config_mask & PWM_CONFIG_PERIOD_TICKS) + c->duty_ticks = c->period_ticks; + else + c->duty_ticks = p->period_ticks; + + c->duty_ticks *= c->duty_percent; + c->duty_ticks /= 100; + c->config_mask &= ~PWM_CONFIG_DUTY_PERCENT; + c->config_mask |= PWM_CONFIG_DUTY_TICKS; + } +} + +int pwm_config_nosleep(struct pwm_channel *p, + struct pwm_channel_config *c) +{ + if (!p->pwm->config_nosleep) + return -EINVAL; + + pwm_config_ns_to_ticks(p, c); + pwm_config_percent_to_ticks(p, c); + + return p->pwm->config_nosleep(p, c); +} +EXPORT_SYMBOL(pwm_config_nosleep); + +int pwm_config(struct pwm_channel *p, + struct pwm_channel_config *c) +{ + int ret = 0; + + if (unlikely(!p->pwm->config)) + return -EINVAL; + + pwm_config_ns_to_ticks(p, c); + pwm_config_percent_to_ticks(p, c); + + switch (c->config_mask & (PWM_CONFIG_PERIOD_TICKS + | PWM_CONFIG_DUTY_TICKS)) { + case PWM_CONFIG_PERIOD_TICKS: + if (p->duty_ticks > c->period_ticks) { + ret = -EINVAL; + goto err; + } + break; + case PWM_CONFIG_DUTY_TICKS: + if (p->period_ticks < c->duty_ticks) { + ret = -EINVAL; + goto err; + } + break; + case PWM_CONFIG_DUTY_TICKS | PWM_CONFIG_PERIOD_TICKS: + if (c->duty_ticks > c->period_ticks) { + ret = -EINVAL; + goto err; + } + break; + default: + break; + } + +err: + dev_dbg(p->pwm->dev, "%s: config_mask %d period_ticks %lu duty_ticks %lu" + " polarity %d duty_ns %lu period_ns %lu duty_percent %d\n", + __func__, c->config_mask, c->period_ticks, c->duty_ticks, + c->polarity, c->duty_ns, c->period_ns, c->duty_percent); + + if (ret) + return ret; + return p->pwm->config(p, c); +} +EXPORT_SYMBOL(pwm_config); + +int pwm_set_period_ns(struct pwm_channel *p, + unsigned long period_ns) +{ + struct pwm_channel_config c = { + .config_mask = PWM_CONFIG_PERIOD_TICKS, + .period_ticks = pwm_ns_to_ticks(p, period_ns), + }; + + return pwm_config(p, &c); +} +EXPORT_SYMBOL(pwm_set_period_ns); + +unsigned long pwm_get_period_ns(struct pwm_channel *p) +{ + return pwm_ticks_to_ns(p, p->period_ticks); +} +EXPORT_SYMBOL(pwm_get_period_ns); + +int pwm_set_duty_ns(struct pwm_channel *p, + unsigned long duty_ns) +{ + struct pwm_channel_config c = { + .config_mask = PWM_CONFIG_DUTY_TICKS, + .duty_ticks = pwm_ns_to_ticks(p, duty_ns), + }; + return pwm_config(p, &c); +} +EXPORT_SYMBOL(pwm_set_duty_ns); + +unsigned long pwm_get_duty_ns(struct pwm_channel *p) +{ + return pwm_ticks_to_ns(p, p->duty_ticks); +} +EXPORT_SYMBOL(pwm_get_duty_ns); + +int pwm_set_duty_percent(struct pwm_channel *p, + int percent) +{ + struct pwm_channel_config c = { + .config_mask = PWM_CONFIG_DUTY_PERCENT, + .duty_percent = percent, + }; + return pwm_config(p, &c); +} +EXPORT_SYMBOL(pwm_set_duty_percent); + +int pwm_set_polarity(struct pwm_channel *p, + int active_high) +{ + struct pwm_channel_config c = { + .config_mask = PWM_CONFIG_POLARITY, + .polarity = active_high, + }; + return pwm_config(p, &c); +} +EXPORT_SYMBOL(pwm_set_polarity); + +int pwm_start(struct pwm_channel *p) +{ + struct pwm_channel_config c = { + .config_mask = PWM_CONFIG_START, + }; + return pwm_config(p, &c); +} +EXPORT_SYMBOL(pwm_start); + +int pwm_stop(struct pwm_channel *p) +{ + struct pwm_channel_config c = { + .config_mask = PWM_CONFIG_STOP, + }; + return pwm_config(p, &c); +} +EXPORT_SYMBOL(pwm_stop); + +int pwm_synchronize(struct pwm_channel *p, + struct pwm_channel *to_p) +{ + if (p->pwm != to_p->pwm) { + /* TODO: support cross-device synchronization */ + return -EINVAL; + } + + if (!p->pwm->synchronize) + return -EINVAL; + + return p->pwm->synchronize(p, to_p); +} +EXPORT_SYMBOL(pwm_synchronize); + +int pwm_unsynchronize(struct pwm_channel *p, + struct pwm_channel *from_p) +{ + if (from_p && (p->pwm != from_p->pwm)) { + /* TODO: support cross-device synchronization */ + return -EINVAL; + } + + if (!p->pwm->unsynchronize) + return -EINVAL; + + return p->pwm->unsynchronize(p, from_p); +} +EXPORT_SYMBOL(pwm_unsynchronize); + +static void pwm_handler(struct work_struct *w) +{ + struct pwm_channel *p = container_of(w, struct pwm_channel, + handler_work); + if (p->handler && p->handler(p, p->handler_data)) + pwm_stop(p); +} + +static void __pwm_callback(struct pwm_channel *p) +{ + queue_work(pwm_handler_workqueue, &p->handler_work); + dev_dbg(p->pwm->dev, "handler %p scheduled with data %p\n", + p->handler, p->handler_data); +} + +int pwm_set_handler(struct pwm_channel *p, + pwm_handler_t handler, + void *data) +{ + if (p->pwm->set_callback) { + p->handler_data = data; + p->handler = handler; + INIT_WORK(&p->handler_work, pwm_handler); + return p->pwm->set_callback(p, handler ? __pwm_callback : NULL); + } + return -EINVAL; +} +EXPORT_SYMBOL(pwm_set_handler); + +static ssize_t pwm_run_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct pwm_channel *p = dev_get_drvdata(dev); + if (sysfs_streq(buf, "1")) + pwm_start(p); + else if (sysfs_streq(buf, "0")) + pwm_stop(p); + return len; +} +static DEVICE_ATTR(run, 0200, NULL, pwm_run_store); + +static ssize_t pwm_duty_ns_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct pwm_channel *p = dev_get_drvdata(dev); + return sprintf(buf, "%lu\n", pwm_get_duty_ns(p)); +} + +static ssize_t pwm_duty_ns_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + unsigned long duty_ns; + struct pwm_channel *p = dev_get_drvdata(dev); + + if (1 == sscanf(buf, "%lu", &duty_ns)) + pwm_set_duty_ns(p, duty_ns); + return len; +} +static DEVICE_ATTR(duty_ns, 0644, pwm_duty_ns_show, pwm_duty_ns_store); + +static ssize_t pwm_period_ns_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct pwm_channel *p = dev_get_drvdata(dev); + return sprintf(buf, "%lu\n", pwm_get_period_ns(p)); +} + +static ssize_t pwm_period_ns_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + unsigned long period_ns; + struct pwm_channel *p = dev_get_drvdata(dev); + + if (1 == sscanf(buf, "%lu", &period_ns)) + pwm_set_period_ns(p, period_ns); + return len; +} +static DEVICE_ATTR(period_ns, 0644, pwm_period_ns_show, pwm_period_ns_store); + +static ssize_t pwm_polarity_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct pwm_channel *p = dev_get_drvdata(dev); + return sprintf(buf, "%d\n", p->active_high ? 1 : 0); +} + +static ssize_t pwm_polarity_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + int polarity; + struct pwm_channel *p = dev_get_drvdata(dev); + + if (1 == sscanf(buf, "%d", &polarity)) + pwm_set_polarity(p, polarity); + return len; +} +static DEVICE_ATTR(polarity, 0644, pwm_polarity_show, pwm_polarity_store); + +static ssize_t pwm_request_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct pwm_channel *p = dev_get_drvdata(dev); + mutex_lock(&device_list_mutex); + __pwm_request_channel(p, REQUEST_SYSFS); + mutex_unlock(&device_list_mutex); + + if (p->pid) + return sprintf(buf, "%s %d\n", p->requester, p->pid); + else + return sprintf(buf, "%s\n", p->requester); +} + +static ssize_t pwm_request_store(struct device *dev, + struct device_attribute *attr, + const char *buf, + size_t len) +{ + struct pwm_channel *p = dev_get_drvdata(dev); + pwm_free(p); + return len; +} +static DEVICE_ATTR(request, 0644, pwm_request_show, pwm_request_store); + +static const struct attribute *pwm_attrs[] = +{ + &dev_attr_run.attr, + &dev_attr_polarity.attr, + &dev_attr_duty_ns.attr, + &dev_attr_period_ns.attr, + &dev_attr_request.attr, + NULL, +}; + +static const struct attribute_group pwm_device_attr_group = { + .attrs = (struct attribute **)pwm_attrs, +}; + +static int __pwm_create_sysfs(struct pwm_device *pwm) +{ + int ret = 0; + struct device *dev; + int wchan; + + for (wchan = 0; wchan < pwm->nchan; wchan++) { + dev = device_create(&pwm_class, pwm->dev, MKDEV(0, 0), + pwm->channels + wchan, + "%s:%d", pwm->bus_id, wchan); + if (!dev) + goto err_dev_create; + ret = sysfs_create_group(&dev->kobj, &pwm_device_attr_group); + if (ret) + goto err_dev_create; + } + + return ret; + +err_dev_create: + for (wchan = 0; wchan < pwm->nchan; wchan++) { + dev = class_find_device(&pwm_class, NULL, + &pwm->channels[wchan], + __match_device); + if (dev) { + put_device(dev); + device_unregister(dev); + } + } + + return ret; +} + +static struct class_attribute pwm_class_attrs[] = { + __ATTR_NULL, +}; + +static struct class pwm_class = { + .name = "pwm", + .owner = THIS_MODULE, + + .class_attrs = pwm_class_attrs, +}; + +static int __init pwm_init(void) +{ + int ret; + + /* TODO: how to deal with devices that register very early? */ + pr_err("%s\n", __func__); + ret = class_register(&pwm_class); + if (ret < 0) + return ret; + + pwm_handler_workqueue = create_workqueue("pwmd"); + + return 0; +} +postcore_initcall(pwm_init); |