Add the generic PWM api from Bill Gatliff (experimental). Ignore the leds trigger part at the moment

SVN-Revision: 21800
lede-17.01
Claudio Mignanti 2010-06-14 18:01:11 +00:00
parent 1a4e4a3850
commit 46de802c0e
9 changed files with 2042 additions and 0 deletions

View File

@ -667,6 +667,7 @@ CONFIG_GENERIC_HARDIRQS=y
CONFIG_GENERIC_HWEIGHT=y
CONFIG_GENERIC_IRQ_PROBE=y
CONFIG_GENERIC_TIME=y
# CONFIG_GENERIC_PWM is not set
# CONFIG_GIGASET_CAPI is not set
# CONFIG_GIGASET_DEBUG is not set
# CONFIG_GFS2_FS is not set

View File

@ -0,0 +1,260 @@
Generic PWM Device API
February 1, 2010
Bill Gatliff
<bgat@billgatliff.com>
The code in drivers/pwm and include/linux/pwm/ implements an API for
applications involving pulse-width-modulation signals. This document
describes how the API implementation facilitates both PWM-generating
devices, and users of those devices.
Motivation
The primary goals for implementing the "generic PWM API" are to
consolidate the various PWM implementations within a consistent and
redundancy-reducing framework, and to facilitate the use of
hotpluggable PWM devices.
Previous PWM-related implementations within the Linux kernel achieved
their consistency via cut-and-paste, but did not need to (and didn't)
facilitate more than one PWM-generating device within the system---
hotplug or otherwise. The Generic PWM Device API might be most
appropriately viewed as an update to those implementations, rather
than a complete rewrite.
Challenges
One of the difficulties in implementing a generic PWM framework is the
fact that pulse-width-modulation applications involve real-world
signals, which often must be carefully managed to prevent destruction
of hardware that is linked to those signals. A DC motor that
experiences a brief interruption in the PWM signal controlling it
might destructively overheat; it could suddenly change speed, losing
synchronization with a sensor; it could even suddenly change direction
or torque, breaking the mechanical device connected to it.
(A generic PWM device framework is not directly responsible for
preventing the above scenarios: that responsibility lies with the
hardware designer, and the application and driver authors. But it
must to the greatest extent possible make it easy to avoid such
problems).
A generic PWM device framework must accommodate the substantial
differences between available PWM-generating hardware devices, without
becoming sub-optimal for any of them.
Finally, a generic PWM device framework must be relatively
lightweight, computationally speaking. Some PWM users demand
high-speed outputs, plus the ability to regulate those outputs
quickly. A device framework must be able to "keep up" with such
hardware, while still leaving time to do real work.
The Generic PWM Device API is an attempt to meet all of the above
requirements. At its initial publication, the API was already in use
managing small DC motors, sensors and solenoids through a
custom-designed, optically-isolated H-bridge driver.
Functional Overview
The Generic PWM Device API framework is implemented in
include/linux/pwm/pwm.h and drivers/pwm/pwm.c. The functions therein
use information from pwm_device, pwm_channel and pwm_channel_config
structures to invoke services in PWM peripheral device drivers.
Consult drivers/pwm/atmel-pwm.c for an example driver.
There are two classes of adopters of the PWM framework:
"Users" -- those wishing to employ the API merely to produce PWM
signals; once they have identified the appropriate physical output
on the platform in question, they don't care about the details of
the underlying hardware
"Driver authors" -- those wishing to bind devices that can generate
PWM signals to the Generic PWM Device API, so that the services of
those devices become available to users. Assuming the hardware can
support the needs of a user, driver authors don't care about the
details of the user's application
Generally speaking, users will first invoke pwm_request() to obtain a
handle to a PWM device. They will then pass that handle to functions
like pwm_duty_ns() and pwm_period_ns() to set the duty cycle and
period of the PWM signal, respectively. They will also invoke
pwm_start() and pwm_stop() to turn the signal on and off.
The Generic PWM API framework also provides a sysfs interface to PWM
devices, which is adequate for basic application needs and testing.
Driver authors fill out a pwm_device structure, which describes the
capabilities of the PWM hardware being constructed--- including the
number of distinct output "channels" the peripheral offers. They then
invoke pwm_register() (usually from within their device's probe()
handler) to make the PWM API aware of their device. The framework
will call back to the methods described in the pwm_device structure as
users begin to configure and utilize the hardware.
Note that PWM signals can be produced by a variety of peripherals,
beyond the true "PWM hardware" offered by many system-on-chip devices.
Other possibilities include timer/counters with compare-match
capabilities, carefully-programmed synchronous serial ports
(e.g. SPI), and GPIO pins driven by kernel interval timers. With a
proper pwm_device structure, these devices and pseudo-devices can all
be accommodated by the Generic PWM Device API framework.
Using the API to Generate PWM Signals -- Basic Functions for Users
pwm_request() -- Returns a pwm_channel pointer, which is subsequently
passed to the other user-related PWM functions. Once requested, a PWM
channel is marked as in-use and subsequent requests prior to
pwm_free() will fail.
The names used to refer to PWM devices are defined by driver authors.
Typically they are platform device bus identifiers, and this
convention is encouraged for consistency.
pwm_free() -- Marks a PWM channel as no longer in use. The PWM device
is stopped before it is released by the API.
pwm_period_ns() -- Specifies the PWM signal's period, in nanoseconds.
pwm_duty_ns() -- Specifies the PWM signal's active duration, in nanoseconds.
pwm_duty_percent() -- Specifies the PWM signal's active duration, as a
percentage of the current period of the signal. NOTE: this value is
not recalculated if the period of the signal is subsequently changed.
pwm_start(), pwm_stop() -- Turns the PWM signal on and off. Except
where stated otherwise by a driver author, signals are stopped at the
end of the current period, at which time the output is set to its
inactive state.
pwm_polarity() -- Defines whether the PWM signal output's active
region is "1" or "0". A 10% duty-cycle, polarity=1 signal will
conventionally be at 5V (or 3.3V, or 1000V, or whatever the platform
hardware does) for 10% of the period. The same configuration of a
polarity=0 signal will be at 5V (or 3.3V, or ...) for 90% of the
period.
Using the API to Generate PWM Signals -- Advanced Functions
pwm_config() -- Passes a pwm_channel_config structure to the
associated device driver. This function is invoked by pwm_start(),
pwm_duty_ns(), etc. and is one of two main entry points to the PWM
driver for the hardware being used. The configuration change is
guaranteed atomic if multiple configuration changes are specified.
This function might sleep, depending on what the device driver has to
do to satisfy the request. All PWM device drivers must support this
entry point.
pwm_config_nosleep() -- Passes a pwm_channel_config structure to the
associated device driver. If the driver must sleep in order to
implement the requested configuration change, -EWOULDBLOCK is
returned. Users may call this function from interrupt handlers, for
example. This is the other main entry point into the PWM hardware
driver, but not all device drivers support this entry point.
pwm_synchronize(), pwm_unsynchronize() -- "Synchronizes" two or more
PWM channels, if the underlying hardware permits. (If it doesn't, the
framework facilitates emulating this capability but it is not yet
implemented). Synchronized channels will start and stop
simultaneously when any single channel in the group is started or
stopped. Use pwm_unsynchronize(..., NULL) to completely detach a
channel from any other synchronized channels. By default, all PWM
channels are unsynchronized.
pwm_set_handler() -- Defines an end-of-period callback. The indicated
function will be invoked in a worker thread at the end of each PWM
period, and can subsequently invoke pwm_config(), etc. Must be used
with extreme care for high-speed PWM outputs. Set the handler
function to NULL to un-set the handler.
Implementing a PWM Device API Driver -- Functions for Driver Authors
Fill out the appropriate fields in a pwm_device structure, and submit
to pwm_register():
bus_id -- the plain-text name of the device. Users will bind to a
channel on the device using this name plus the channel number. For
example, the Atmel PWMC's bus_id is "atmel_pwmc", the same as used by
the platform device driver (recommended). The first device registered
thereby receives bus_id "atmel_pwmc.0", which is what you put in
pwm_device.bus_id. Channels are then named "atmel_pwmc.0:[0-3]".
(Hint: just use pdev->dev.bus_id in your probe() method).
nchan -- the number of distinct output channels provided by the device.
request -- (optional) Invoked each time a user requests a channel.
Use to turn on clocks, clean up register states, etc. The framework
takes care of device locking/unlocking; you will see only successful
requests.
free -- (optional) Callback for each time a user relinquishes a
channel. The framework will have already stopped, unsynchronized and
un-handled the channel. Use to turn off clocks, etc. as necessary.
synchronize, unsynchronize -- (optional) Callbacks to
synchronize/unsynchronize channels. Some devices provide this
capability in hardware; for others, it can be emulated (see
atmel_pwmc.c's sync_mask for an example).
set_callback -- (optional) Invoked when a user requests a handler. If
the hardware supports an end-of-period interrupt, invoke the function
indicated during your interrupt handler. The callback function itself
is always internal to the API, and does not map directly to the user's
callback function.
config -- Invoked to change the device configuration, always from a
sleep-capable context. All the changes indicated must be performed
atomically, ideally synchronized to an end-of-period event (so that
you avoid short or long output pulses). You may sleep, etc. as
necessary within this function.
config_nosleep -- (optional) Invoked to change device configuration
from within a context that is not allowed to sleep. If you cannot
perform the requested configuration changes without sleeping, return
-EWOULDBLOCK.
Acknowledgements
The author expresses his gratitude to the countless developers who
have reviewed and submitted feedback on the various versions of the
Generic PWM Device API code, and those who have submitted drivers and
applications that use the framework. You know who you are. ;)

View File

@ -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

View File

@ -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

View File

@ -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");

View File

@ -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");

View File

@ -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);

View File

@ -0,0 +1,165 @@
/*
* include/linux/pwm.h
*
* Copyright (C) 2008 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
*/
#ifndef __LINUX_PWM_H
#define __LINUX_PWM_H
enum {
PWM_CONFIG_DUTY_TICKS = BIT(0),
PWM_CONFIG_PERIOD_TICKS = BIT(1),
PWM_CONFIG_POLARITY = BIT(2),
PWM_CONFIG_START = BIT(3),
PWM_CONFIG_STOP = BIT(4),
PWM_CONFIG_HANDLER = BIT(5),
PWM_CONFIG_DUTY_NS = BIT(6),
PWM_CONFIG_DUTY_PERCENT = BIT(7),
PWM_CONFIG_PERIOD_NS = BIT(8),
};
struct pwm_channel;
struct work_struct;
typedef int (*pwm_handler_t)(struct pwm_channel *p, void *data);
typedef void (*pwm_callback_t)(struct pwm_channel *p);
struct pwm_channel_config {
int config_mask;
unsigned long duty_ticks;
unsigned long period_ticks;
int polarity;
pwm_handler_t handler;
unsigned long duty_ns;
unsigned long period_ns;
int duty_percent;
};
struct pwm_device {
struct list_head list;
spinlock_t list_lock;
struct device *dev;
struct module *owner;
struct pwm_channel *channels;
const char *bus_id;
int nchan;
int (*request) (struct pwm_channel *p);
void (*free) (struct pwm_channel *p);
int (*config) (struct pwm_channel *p,
struct pwm_channel_config *c);
int (*config_nosleep)(struct pwm_channel *p,
struct pwm_channel_config *c);
int (*synchronize) (struct pwm_channel *p,
struct pwm_channel *to_p);
int (*unsynchronize)(struct pwm_channel *p,
struct pwm_channel *from_p);
int (*set_callback) (struct pwm_channel *p,
pwm_callback_t callback);
};
int pwm_register(struct pwm_device *pwm);
int pwm_unregister(struct pwm_device *pwm);
enum {
FLAG_REQUESTED = 0,
FLAG_STOP = 1,
};
struct pwm_channel {
struct list_head list;
struct pwm_device *pwm;
const char *requester;
pid_t pid;
int chan;
unsigned long flags;
unsigned long tick_hz;
spinlock_t lock;
struct completion complete;
pwm_callback_t callback;
struct work_struct handler_work;
pwm_handler_t handler;
void *handler_data;
int active_high;
unsigned long period_ticks;
unsigned long duty_ticks;
};
struct gpio_pwm_platform_data {
int gpio;
};
struct pwm_channel *
pwm_request(const char *bus_id, int chan,
const char *requester);
void pwm_free(struct pwm_channel *pwm);
int pwm_config_nosleep(struct pwm_channel *pwm,
struct pwm_channel_config *c);
int pwm_config(struct pwm_channel *pwm,
struct pwm_channel_config *c);
unsigned long pwm_ns_to_ticks(struct pwm_channel *pwm,
unsigned long nsecs);
unsigned long pwm_ticks_to_ns(struct pwm_channel *pwm,
unsigned long ticks);
int pwm_set_period_ns(struct pwm_channel *pwm,
unsigned long period_ns);
unsigned long int pwm_get_period_ns(struct pwm_channel *pwm);
int pwm_set_duty_ns(struct pwm_channel *pwm,
unsigned long duty_ns);
int pwm_set_duty_percent(struct pwm_channel *pwm,
int percent);
unsigned long pwm_get_duty_ns(struct pwm_channel *pwm);
int pwm_set_polarity(struct pwm_channel *pwm,
int active_high);
int pwm_start(struct pwm_channel *pwm);
int pwm_stop(struct pwm_channel *pwm);
int pwm_set_handler(struct pwm_channel *pwm,
pwm_handler_t handler,
void *data);
int pwm_synchronize(struct pwm_channel *p,
struct pwm_channel *to_p);
int pwm_unsynchronize(struct pwm_channel *p,
struct pwm_channel *from_p);
#endif /* __LINUX_PWM_H */

View File

@ -0,0 +1,46 @@
Patches from: Bill Gatliff <bgat@billgatliff.com>
Based on:
* Expunge old Atmel PWMC driver, replacing it with one that conforms to the PWM API
* Incorporate PWM API code into KBuild
Ignore the LEDS part atm
---
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 049ff24..b94666c 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -5,7 +5,6 @@
obj-$(CONFIG_IBM_ASM) += ibmasm/
obj-$(CONFIG_HDPU_FEATURES) += hdpuftrs/
obj-$(CONFIG_AD525X_DPOT) += ad525x_dpot.o
-obj-$(CONFIG_ATMEL_PWM) += atmel_pwm.o
obj-$(CONFIG_ATMEL_SSC) += atmel-ssc.o
obj-$(CONFIG_ATMEL_TCLIB) += atmel_tclib.o
obj-$(CONFIG_ICS932S401) += ics932s401.o
diff --git a/drivers/Kconfig b/drivers/Kconfig
index 368ae6d..54d7b16 100644
--- a/drivers/Kconfig
+++ b/drivers/Kconfig
@@ -54,6 +54,8 @@ source "drivers/pps/Kconfig"
source "drivers/gpio/Kconfig"
+source "drivers/pwm/Kconfig"
+
source "drivers/w1/Kconfig"
source "drivers/power/Kconfig"
diff --git a/drivers/Makefile b/drivers/Makefile
index 6ee53c7..e6143f3 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -6,6 +6,8 @@
#
obj-y += gpio/
+obj-$(CONFIG_GENERIC_PWM) += pwm/
+
obj-$(CONFIG_PCI) += pci/
obj-$(CONFIG_PARISC) += parisc/
obj-$(CONFIG_RAPIDIO) += rapidio/
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig