[kernel] Add the generic PWM api from Bill Gatliff (experimental). Ignore the leds...
authorClaudio Mignanti <c.mignanti@gmail.com>
Mon, 14 Jun 2010 18:01:11 +0000 (18:01 +0000)
committerClaudio Mignanti <c.mignanti@gmail.com>
Mon, 14 Jun 2010 18:01:11 +0000 (18:01 +0000)
SVN-Revision: 21800

target/linux/generic-2.6/config-2.6.34
target/linux/generic-2.6/files/Documentation/pwm.txt [new file with mode: 0644]
target/linux/generic-2.6/files/drivers/pwm/Kconfig [new file with mode: 0644]
target/linux/generic-2.6/files/drivers/pwm/Makefile [new file with mode: 0644]
target/linux/generic-2.6/files/drivers/pwm/atmel-pwm.c [new file with mode: 0644]
target/linux/generic-2.6/files/drivers/pwm/gpio.c [new file with mode: 0644]
target/linux/generic-2.6/files/drivers/pwm/pwm.c [new file with mode: 0644]
target/linux/generic-2.6/files/include/linux/pwm/pwm.h [new file with mode: 0644]
target/linux/generic-2.6/patches-2.6.34/890-generic_pwm_api.patch [new file with mode: 0644]

index 14013ae..1d16630 100644 (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
diff --git a/target/linux/generic-2.6/files/Documentation/pwm.txt b/target/linux/generic-2.6/files/Documentation/pwm.txt
new file mode 100644 (file)
index 0000000..2c41ca5
--- /dev/null
@@ -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.  ;)
+
diff --git a/target/linux/generic-2.6/files/drivers/pwm/Kconfig b/target/linux/generic-2.6/files/drivers/pwm/Kconfig
new file mode 100644 (file)
index 0000000..1c24e11
--- /dev/null
@@ -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-2.6/files/drivers/pwm/Makefile b/target/linux/generic-2.6/files/drivers/pwm/Makefile
new file mode 100644 (file)
index 0000000..e8cacc5
--- /dev/null
@@ -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-2.6/files/drivers/pwm/atmel-pwm.c b/target/linux/generic-2.6/files/drivers/pwm/atmel-pwm.c
new file mode 100644 (file)
index 0000000..158bb92
--- /dev/null
@@ -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-2.6/files/drivers/pwm/gpio.c b/target/linux/generic-2.6/files/drivers/pwm/gpio.c
new file mode 100644 (file)
index 0000000..dff5d1d
--- /dev/null
@@ -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-2.6/files/drivers/pwm/pwm.c b/target/linux/generic-2.6/files/drivers/pwm/pwm.c
new file mode 100644 (file)
index 0000000..c1596e9
--- /dev/null
@@ -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);
diff --git a/target/linux/generic-2.6/files/include/linux/pwm/pwm.h b/target/linux/generic-2.6/files/include/linux/pwm/pwm.h
new file mode 100644 (file)
index 0000000..e01cca9
--- /dev/null
@@ -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 */
diff --git a/target/linux/generic-2.6/patches-2.6.34/890-generic_pwm_api.patch b/target/linux/generic-2.6/patches-2.6.34/890-generic_pwm_api.patch
new file mode 100644 (file)
index 0000000..6c58e53
--- /dev/null
@@ -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