| /* |
| * 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"); |