* [PWM 01/10] API to consolidate PWM devices behind a common user and kernel interface
@ 2010-10-01 15:17 Bill Gatliff
2010-10-01 15:17 ` [PWM 02/10] Emulates PWM hardware using a high-resolution timer and a GPIO pin Bill Gatliff
` (13 more replies)
0 siblings, 14 replies; 33+ messages in thread
From: Bill Gatliff @ 2010-10-01 15:17 UTC (permalink / raw)
To: linux-embedded, linux-embedded; +Cc: Bill Gatliff
Signed-off-by: Bill Gatliff <bgat@billgatliff.com>
---
Documentation/pwm.txt | 260 +++++++++++++++++++
drivers/pwm/pwm.c | 635 +++++++++++++++++++++++++++++++++++++++++++++++
include/linux/pwm.h | 31 ---
include/linux/pwm/pwm.h | 128 ++++++++++
4 files changed, 1023 insertions(+), 31 deletions(-)
create mode 100644 Documentation/pwm.txt
create mode 100644 drivers/pwm/pwm.c
delete mode 100644 include/linux/pwm.h
create mode 100644 include/linux/pwm/pwm.h
diff --git a/Documentation/pwm.txt b/Documentation/pwm.txt
new file mode 100644
index 0000000..34b1e5a
--- /dev/null
+++ b/Documentation/pwm.txt
@@ -0,0 +1,260 @@
+ Generic PWM Device API
+
+ August 5, 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_release() 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_release() -- 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/drivers/pwm/pwm.c b/drivers/pwm/pwm.c
new file mode 100644
index 0000000..2774116
--- /dev/null
+++ b/drivers/pwm/pwm.c
@@ -0,0 +1,635 @@
+/*
+ * 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/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/slab.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/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_release(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->release)
+ p->pwm->release(p);
+ module_put(p->pwm->owner);
+done:
+ mutex_unlock(&device_list_mutex);
+}
+EXPORT_SYMBOL(pwm_release);
+
+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:
+ 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);
+}
+
+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);
+}
+
+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_release(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? */
+ 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/include/linux/pwm.h b/include/linux/pwm.h
deleted file mode 100644
index 7c77575..0000000
--- a/include/linux/pwm.h
+++ /dev/null
@@ -1,31 +0,0 @@
-#ifndef __LINUX_PWM_H
-#define __LINUX_PWM_H
-
-struct pwm_device;
-
-/*
- * pwm_request - request a PWM device
- */
-struct pwm_device *pwm_request(int pwm_id, const char *label);
-
-/*
- * pwm_free - free a PWM device
- */
-void pwm_free(struct pwm_device *pwm);
-
-/*
- * pwm_config - change a PWM device configuration
- */
-int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns);
-
-/*
- * pwm_enable - start a PWM output toggling
- */
-int pwm_enable(struct pwm_device *pwm);
-
-/*
- * pwm_disable - stop a PWM output toggling
- */
-void pwm_disable(struct pwm_device *pwm);
-
-#endif /* __LINUX_PWM_H */
diff --git a/include/linux/pwm/pwm.h b/include/linux/pwm/pwm.h
new file mode 100644
index 0000000..a10824c
--- /dev/null
+++ b/include/linux/pwm/pwm.h
@@ -0,0 +1,128 @@
+/*
+ * include/linux/pwm.h
+ *
+ * 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
+ */
+#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 (*release) (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_release(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 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 */
--
1.7.1
^ permalink raw reply related [flat|nested] 33+ messages in thread
* [PWM 02/10] Emulates PWM hardware using a high-resolution timer and a GPIO pin
2010-10-01 15:17 [PWM 01/10] API to consolidate PWM devices behind a common user and kernel interface Bill Gatliff
@ 2010-10-01 15:17 ` Bill Gatliff
2010-10-16 6:54 ` Grant Likely
2010-10-01 15:17 ` [PWM 03/10] Expunge old Atmel PWMC driver, replacing it with one that conforms to the PWM API Bill Gatliff
` (12 subsequent siblings)
13 siblings, 1 reply; 33+ messages in thread
From: Bill Gatliff @ 2010-10-01 15:17 UTC (permalink / raw)
To: linux-embedded, linux-embedded; +Cc: Bill Gatliff
Signed-off-by: Bill Gatliff <bgat@billgatliff.com>
---
drivers/pwm/gpio.c | 298 ++++++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 298 insertions(+), 0 deletions(-)
create mode 100644 drivers/pwm/gpio.c
diff --git a/drivers/pwm/gpio.c b/drivers/pwm/gpio.c
new file mode 100644
index 0000000..f4aab06
--- /dev/null
+++ b/drivers/pwm/gpio.c
@@ -0,0 +1,298 @@
+/*
+ * drivers/pwm/gpio.c
+ *
+ * Models a single-channel PWM device using a timer and a GPIO pin.
+ *
+ * Copyright (C) 2010 Bill Gatliff <bgat@billgatliff.com>
+ *
+ * This program is free software; you may redistribute and/or modify
+ * it under the terms of the GNU General Public License Version 2, as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/hrtimer.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/workqueue.h>
+#include <linux/gpio.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");
--
1.7.1
^ permalink raw reply related [flat|nested] 33+ messages in thread
* [PWM 03/10] Expunge old Atmel PWMC driver, replacing it with one that conforms to the PWM API
2010-10-01 15:17 [PWM 01/10] API to consolidate PWM devices behind a common user and kernel interface Bill Gatliff
2010-10-01 15:17 ` [PWM 02/10] Emulates PWM hardware using a high-resolution timer and a GPIO pin Bill Gatliff
@ 2010-10-01 15:17 ` Bill Gatliff
2010-10-16 7:50 ` Grant Likely
2010-10-01 15:17 ` [PWM 04/10] Implements PWM-based LED control Bill Gatliff
` (11 subsequent siblings)
13 siblings, 1 reply; 33+ messages in thread
From: Bill Gatliff @ 2010-10-01 15:17 UTC (permalink / raw)
To: linux-embedded, linux-embedded; +Cc: Bill Gatliff
Signed-off-by: Bill Gatliff <bgat@billgatliff.com>
---
drivers/misc/Makefile | 1 -
drivers/misc/atmel_pwm.c | 410 --------------------------------
drivers/pwm/atmel-pwm.c | 592 ++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 592 insertions(+), 411 deletions(-)
delete mode 100644 drivers/misc/atmel_pwm.c
create mode 100644 drivers/pwm/atmel-pwm.c
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 42eab95..b5962a2 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -6,7 +6,6 @@ obj-$(CONFIG_IBM_ASM) += ibmasm/
obj-$(CONFIG_AD525X_DPOT) += ad525x_dpot.o
obj-$(CONFIG_AD525X_DPOT_I2C) += ad525x_dpot-i2c.o
obj-$(CONFIG_AD525X_DPOT_SPI) += ad525x_dpot-spi.o
-obj-$(CONFIG_ATMEL_PWM) += atmel_pwm.o
obj-$(CONFIG_ATMEL_SSC) += atmel-ssc.o
obj-$(CONFIG_ATMEL_TCLIB) += atmel_tclib.o
obj-$(CONFIG_BMP085) += bmp085.o
diff --git a/drivers/misc/atmel_pwm.c b/drivers/misc/atmel_pwm.c
deleted file mode 100644
index 0f3fb4f..0000000
--- a/drivers/misc/atmel_pwm.c
+++ /dev/null
@@ -1,410 +0,0 @@
-#include <linux/module.h>
-#include <linux/clk.h>
-#include <linux/err.h>
-#include <linux/slab.h>
-#include <linux/io.h>
-#include <linux/interrupt.h>
-#include <linux/platform_device.h>
-#include <linux/atmel_pwm.h>
-
-
-/*
- * This is a simple driver for the PWM controller found in various newer
- * Atmel SOCs, including the AVR32 series and the AT91sam9263.
- *
- * Chips with current Linux ports have only 4 PWM channels, out of max 32.
- * AT32UC3A and AT32UC3B chips have 7 channels (but currently no Linux).
- * Docs are inconsistent about the width of the channel counter registers;
- * it's at least 16 bits, but several places say 20 bits.
- */
-#define PWM_NCHAN 4 /* max 32 */
-
-struct pwm {
- spinlock_t lock;
- struct platform_device *pdev;
- u32 mask;
- int irq;
- void __iomem *base;
- struct clk *clk;
- struct pwm_channel *channel[PWM_NCHAN];
- void (*handler[PWM_NCHAN])(struct pwm_channel *);
-};
-
-
-/* global PWM controller registers */
-#define PWM_MR 0x00
-#define PWM_ENA 0x04
-#define PWM_DIS 0x08
-#define PWM_SR 0x0c
-#define PWM_IER 0x10
-#define PWM_IDR 0x14
-#define PWM_IMR 0x18
-#define PWM_ISR 0x1c
-
-static inline void pwm_writel(const struct pwm *p, unsigned offset, u32 val)
-{
- __raw_writel(val, p->base + offset);
-}
-
-static inline u32 pwm_readl(const struct pwm *p, unsigned offset)
-{
- return __raw_readl(p->base + offset);
-}
-
-static inline void __iomem *pwmc_regs(const struct pwm *p, int index)
-{
- return p->base + 0x200 + index * 0x20;
-}
-
-static struct pwm *pwm;
-
-static void pwm_dumpregs(struct pwm_channel *ch, char *tag)
-{
- struct device *dev = &pwm->pdev->dev;
-
- dev_dbg(dev, "%s: mr %08x, sr %08x, imr %08x\n",
- tag,
- pwm_readl(pwm, PWM_MR),
- pwm_readl(pwm, PWM_SR),
- pwm_readl(pwm, PWM_IMR));
- dev_dbg(dev,
- "pwm ch%d - mr %08x, dty %u, prd %u, cnt %u\n",
- ch->index,
- pwm_channel_readl(ch, PWM_CMR),
- pwm_channel_readl(ch, PWM_CDTY),
- pwm_channel_readl(ch, PWM_CPRD),
- pwm_channel_readl(ch, PWM_CCNT));
-}
-
-
-/**
- * pwm_channel_alloc - allocate an unused PWM channel
- * @index: identifies the channel
- * @ch: structure to be initialized
- *
- * Drivers allocate PWM channels according to the board's wiring, and
- * matching board-specific setup code. Returns zero or negative errno.
- */
-int pwm_channel_alloc(int index, struct pwm_channel *ch)
-{
- unsigned long flags;
- int status = 0;
-
- /* insist on PWM init, with this signal pinned out */
- if (!pwm || !(pwm->mask & 1 << index))
- return -ENODEV;
-
- if (index < 0 || index >= PWM_NCHAN || !ch)
- return -EINVAL;
- memset(ch, 0, sizeof *ch);
-
- spin_lock_irqsave(&pwm->lock, flags);
- if (pwm->channel[index])
- status = -EBUSY;
- else {
- clk_enable(pwm->clk);
-
- ch->regs = pwmc_regs(pwm, index);
- ch->index = index;
-
- /* REVISIT: ap7000 seems to go 2x as fast as we expect!! */
- ch->mck = clk_get_rate(pwm->clk);
-
- pwm->channel[index] = ch;
- pwm->handler[index] = NULL;
-
- /* channel and irq are always disabled when we return */
- pwm_writel(pwm, PWM_DIS, 1 << index);
- pwm_writel(pwm, PWM_IDR, 1 << index);
- }
- spin_unlock_irqrestore(&pwm->lock, flags);
- return status;
-}
-EXPORT_SYMBOL(pwm_channel_alloc);
-
-static int pwmcheck(struct pwm_channel *ch)
-{
- int index;
-
- if (!pwm)
- return -ENODEV;
- if (!ch)
- return -EINVAL;
- index = ch->index;
- if (index < 0 || index >= PWM_NCHAN || pwm->channel[index] != ch)
- return -EINVAL;
-
- return index;
-}
-
-/**
- * pwm_channel_free - release a previously allocated channel
- * @ch: the channel being released
- *
- * The channel is completely shut down (counter and IRQ disabled),
- * and made available for re-use. Returns zero, or negative errno.
- */
-int pwm_channel_free(struct pwm_channel *ch)
-{
- unsigned long flags;
- int t;
-
- spin_lock_irqsave(&pwm->lock, flags);
- t = pwmcheck(ch);
- if (t >= 0) {
- pwm->channel[t] = NULL;
- pwm->handler[t] = NULL;
-
- /* channel and irq are always disabled when we return */
- pwm_writel(pwm, PWM_DIS, 1 << t);
- pwm_writel(pwm, PWM_IDR, 1 << t);
-
- clk_disable(pwm->clk);
- t = 0;
- }
- spin_unlock_irqrestore(&pwm->lock, flags);
- return t;
-}
-EXPORT_SYMBOL(pwm_channel_free);
-
-int __pwm_channel_onoff(struct pwm_channel *ch, int enabled)
-{
- unsigned long flags;
- int t;
-
- /* OMITTED FUNCTIONALITY: starting several channels in synch */
-
- spin_lock_irqsave(&pwm->lock, flags);
- t = pwmcheck(ch);
- if (t >= 0) {
- pwm_writel(pwm, enabled ? PWM_ENA : PWM_DIS, 1 << t);
- t = 0;
- pwm_dumpregs(ch, enabled ? "enable" : "disable");
- }
- spin_unlock_irqrestore(&pwm->lock, flags);
-
- return t;
-}
-EXPORT_SYMBOL(__pwm_channel_onoff);
-
-/**
- * pwm_clk_alloc - allocate and configure CLKA or CLKB
- * @prescale: from 0..10, the power of two used to divide MCK
- * @div: from 1..255, the linear divisor to use
- *
- * Returns PWM_CPR_CLKA, PWM_CPR_CLKB, or negative errno. The allocated
- * clock will run with a period of (2^prescale * div) / MCK, or twice as
- * long if center aligned PWM output is used. The clock must later be
- * deconfigured using pwm_clk_free().
- */
-int pwm_clk_alloc(unsigned prescale, unsigned div)
-{
- unsigned long flags;
- u32 mr;
- u32 val = (prescale << 8) | div;
- int ret = -EBUSY;
-
- if (prescale >= 10 || div == 0 || div > 255)
- return -EINVAL;
-
- spin_lock_irqsave(&pwm->lock, flags);
- mr = pwm_readl(pwm, PWM_MR);
- if ((mr & 0xffff) == 0) {
- mr |= val;
- ret = PWM_CPR_CLKA;
- } else if ((mr & (0xffff << 16)) == 0) {
- mr |= val << 16;
- ret = PWM_CPR_CLKB;
- }
- if (ret > 0)
- pwm_writel(pwm, PWM_MR, mr);
- spin_unlock_irqrestore(&pwm->lock, flags);
- return ret;
-}
-EXPORT_SYMBOL(pwm_clk_alloc);
-
-/**
- * pwm_clk_free - deconfigure and release CLKA or CLKB
- *
- * Reverses the effect of pwm_clk_alloc().
- */
-void pwm_clk_free(unsigned clk)
-{
- unsigned long flags;
- u32 mr;
-
- spin_lock_irqsave(&pwm->lock, flags);
- mr = pwm_readl(pwm, PWM_MR);
- if (clk == PWM_CPR_CLKA)
- pwm_writel(pwm, PWM_MR, mr & ~(0xffff << 0));
- if (clk == PWM_CPR_CLKB)
- pwm_writel(pwm, PWM_MR, mr & ~(0xffff << 16));
- spin_unlock_irqrestore(&pwm->lock, flags);
-}
-EXPORT_SYMBOL(pwm_clk_free);
-
-/**
- * pwm_channel_handler - manage channel's IRQ handler
- * @ch: the channel
- * @handler: the handler to use, possibly NULL
- *
- * If the handler is non-null, the handler will be called after every
- * period of this PWM channel. If the handler is null, this channel
- * won't generate an IRQ.
- */
-int pwm_channel_handler(struct pwm_channel *ch,
- void (*handler)(struct pwm_channel *ch))
-{
- unsigned long flags;
- int t;
-
- spin_lock_irqsave(&pwm->lock, flags);
- t = pwmcheck(ch);
- if (t >= 0) {
- pwm->handler[t] = handler;
- pwm_writel(pwm, handler ? PWM_IER : PWM_IDR, 1 << t);
- t = 0;
- }
- spin_unlock_irqrestore(&pwm->lock, flags);
-
- return t;
-}
-EXPORT_SYMBOL(pwm_channel_handler);
-
-static irqreturn_t pwm_irq(int id, void *_pwm)
-{
- struct pwm *p = _pwm;
- irqreturn_t handled = IRQ_NONE;
- u32 irqstat;
- int index;
-
- spin_lock(&p->lock);
-
- /* ack irqs, then handle them */
- irqstat = pwm_readl(pwm, PWM_ISR);
-
- while (irqstat) {
- struct pwm_channel *ch;
- void (*handler)(struct pwm_channel *ch);
-
- index = ffs(irqstat) - 1;
- irqstat &= ~(1 << index);
- ch = pwm->channel[index];
- handler = pwm->handler[index];
- if (handler && ch) {
- spin_unlock(&p->lock);
- handler(ch);
- spin_lock(&p->lock);
- handled = IRQ_HANDLED;
- }
- }
-
- spin_unlock(&p->lock);
- return handled;
-}
-
-static int __init pwm_probe(struct platform_device *pdev)
-{
- struct resource *r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- int irq = platform_get_irq(pdev, 0);
- u32 *mp = pdev->dev.platform_data;
- struct pwm *p;
- int status = -EIO;
-
- if (pwm)
- return -EBUSY;
- if (!r || irq < 0 || !mp || !*mp)
- return -ENODEV;
- if (*mp & ~((1<<PWM_NCHAN)-1)) {
- dev_warn(&pdev->dev, "mask 0x%x ... more than %d channels\n",
- *mp, PWM_NCHAN);
- return -EINVAL;
- }
-
- p = kzalloc(sizeof(*p), GFP_KERNEL);
- if (!p)
- return -ENOMEM;
-
- spin_lock_init(&p->lock);
- p->pdev = pdev;
- p->mask = *mp;
- p->irq = irq;
- p->base = ioremap(r->start, r->end - r->start + 1);
- if (!p->base)
- goto fail;
- p->clk = clk_get(&pdev->dev, "pwm_clk");
- if (IS_ERR(p->clk)) {
- status = PTR_ERR(p->clk);
- p->clk = NULL;
- goto fail;
- }
-
- status = request_irq(irq, pwm_irq, 0, pdev->name, p);
- if (status < 0)
- goto fail;
-
- pwm = p;
- platform_set_drvdata(pdev, p);
-
- return 0;
-
-fail:
- if (p->clk)
- clk_put(p->clk);
- if (p->base)
- iounmap(p->base);
-
- kfree(p);
- return status;
-}
-
-static int __exit pwm_remove(struct platform_device *pdev)
-{
- struct pwm *p = platform_get_drvdata(pdev);
-
- if (p != pwm)
- return -EINVAL;
-
- clk_enable(pwm->clk);
- pwm_writel(pwm, PWM_DIS, (1 << PWM_NCHAN) - 1);
- pwm_writel(pwm, PWM_IDR, (1 << PWM_NCHAN) - 1);
- clk_disable(pwm->clk);
-
- pwm = NULL;
-
- free_irq(p->irq, p);
- clk_put(p->clk);
- iounmap(p->base);
- kfree(p);
-
- return 0;
-}
-
-static struct platform_driver atmel_pwm_driver = {
- .driver = {
- .name = "atmel_pwm",
- .owner = THIS_MODULE,
- },
- .remove = __exit_p(pwm_remove),
-
- /* NOTE: PWM can keep running in AVR32 "idle" and "frozen" states;
- * and all AT91sam9263 states, albeit at reduced clock rate if
- * MCK becomes the slow clock (i.e. what Linux labels STR).
- */
-};
-
-static int __init pwm_init(void)
-{
- return platform_driver_probe(&atmel_pwm_driver, pwm_probe);
-}
-module_init(pwm_init);
-
-static void __exit pwm_exit(void)
-{
- platform_driver_unregister(&atmel_pwm_driver);
-}
-module_exit(pwm_exit);
-
-MODULE_DESCRIPTION("Driver for AT32/AT91 PWM module");
-MODULE_LICENSE("GPL");
-MODULE_ALIAS("platform:atmel_pwm");
diff --git a/drivers/pwm/atmel-pwm.c b/drivers/pwm/atmel-pwm.c
new file mode 100644
index 0000000..8566866
--- /dev/null
+++ b/drivers/pwm/atmel-pwm.c
@@ -0,0 +1,592 @@
+/*
+ * drivers/pwm/atmel-pwm.c
+ *
+ * Copyright (C) 2010 Bill Gatliff <bgat@billgatliff.com>
+ * Copyright (C) 2007 David Brownell
+ *
+ * This program is free software; you may redistribute and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.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_release(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.release = atmel_pwm_release;
+ 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");
--
1.7.1
^ permalink raw reply related [flat|nested] 33+ messages in thread
* [PWM 04/10] Implements PWM-based LED control
2010-10-01 15:17 [PWM 01/10] API to consolidate PWM devices behind a common user and kernel interface Bill Gatliff
2010-10-01 15:17 ` [PWM 02/10] Emulates PWM hardware using a high-resolution timer and a GPIO pin Bill Gatliff
2010-10-01 15:17 ` [PWM 03/10] Expunge old Atmel PWMC driver, replacing it with one that conforms to the PWM API Bill Gatliff
@ 2010-10-01 15:17 ` Bill Gatliff
2010-10-16 7:58 ` Grant Likely
2010-10-01 15:17 ` [PWM 05/10] LED "dim" trigger based on PWM control of the LED Bill Gatliff
` (10 subsequent siblings)
13 siblings, 1 reply; 33+ messages in thread
From: Bill Gatliff @ 2010-10-01 15:17 UTC (permalink / raw)
To: linux-embedded, linux-embedded; +Cc: Bill Gatliff
Signed-off-by: Bill Gatliff <bgat@billgatliff.com>
---
drivers/leds/Kconfig | 19 +++-
drivers/leds/leds-pwm.c | 224 +++++++++++++++++++++++-------------------
include/linux/pwm/pwm-led.h | 33 +++++++
3 files changed, 169 insertions(+), 107 deletions(-)
create mode 100644 include/linux/pwm/pwm-led.h
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index e411262..3af2cde 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -26,12 +26,19 @@ config LEDS_88PM860X
This option enables support for on-chip LED drivers found on Marvell
Semiconductor 88PM8606 PMIC.
-config LEDS_ATMEL_PWM
- tristate "LED Support using Atmel PWM outputs"
- depends on ATMEL_PWM
- help
- This option enables support for LEDs driven using outputs
- of the dedicated PWM controller found on newer Atmel SOCs.
+config LEDS_PWM
+ tristate "Support for LEDs connected to Generic PWM channels"
+ depends on LEDS_CLASS && GENERIC_PWM
+ help
+ Enables support for LEDs connected to PWM devices that
+ conform to the Generic PWM API. This API allows drivers
+ to adjust the brightness of the LED by varying the duty
+ cycle of the signal at the PWM channel output, using PWM API
+ commands, rather than merely turning them on and off.
+
+ If your platform has devices with drivers that implement
+ the Generic PWM API, and those devices have outputs that
+ are connected to LEDs, then you probably want to say 'Y' here.
config LEDS_LOCOMO
tristate "LED Support for Locomo device"
diff --git a/drivers/leds/leds-pwm.c b/drivers/leds/leds-pwm.c
index da3fa8d..ab064ac 100644
--- a/drivers/leds/leds-pwm.c
+++ b/drivers/leds/leds-pwm.c
@@ -1,153 +1,175 @@
/*
- * linux/drivers/leds-pwm.c
+ * drivers/leds/leds-pwm.c
*
- * simple PWM based LED control
+ * Copyright (C) 2010 Bill Gatliff <bgat@billgatliff.com>
+ * Copyright (C) 2009 Loutao Fu, Pengutronix <l.fu@pengutronix.de>
*
- * Copyright 2009 Luotao Fu @ Pengutronix (l.fu@pengutronix.de)
+ * Based on leds-gpio.c by Raphael Assenat <raph@8d.com>
*
- * based on leds-gpio.c by Raphael Assenat <raph@8d.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
+ * 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/kernel.h>
-#include <linux/init.h>
#include <linux/platform_device.h>
-#include <linux/fb.h>
-#include <linux/leds.h>
-#include <linux/err.h>
-#include <linux/pwm.h>
-#include <linux/leds_pwm.h>
#include <linux/slab.h>
-
-struct led_pwm_data {
- struct led_classdev cdev;
- struct pwm_device *pwm;
- unsigned int active_low;
- unsigned int period;
+#include <linux/leds.h>
+#include <linux/io.h>
+#include <linux/pwm/pwm.h>
+#include <linux/pwm/pwm-led.h>
+
+struct led_pwm {
+ struct led_classdev led;
+ struct pwm_channel *pwm;
+ int percent;
};
-static void led_pwm_set(struct led_classdev *led_cdev,
- enum led_brightness brightness)
+static inline struct led_pwm *to_led_pwm(const struct led_classdev *c)
+{
+ return container_of(c, struct led_pwm, led);
+}
+
+static void
+led_pwm_brightness_set(struct led_classdev *c,
+ enum led_brightness b)
+{
+ struct led_pwm *led = to_led_pwm(c);
+ int percent;
+
+ percent = (b * 100) / (LED_FULL - LED_OFF);
+ led->percent = percent;
+ pwm_set_duty_percent(led->pwm, percent);
+}
+
+static enum led_brightness
+led_pwm_brightness_get(struct led_classdev *c)
{
- struct led_pwm_data *led_dat =
- container_of(led_cdev, struct led_pwm_data, cdev);
- unsigned int max = led_dat->cdev.max_brightness;
- unsigned int period = led_dat->period;
-
- if (brightness == 0) {
- pwm_config(led_dat->pwm, 0, period);
- pwm_disable(led_dat->pwm);
- } else {
- pwm_config(led_dat->pwm, brightness * period / max, period);
- pwm_enable(led_dat->pwm);
+ struct led_pwm *led = to_led_pwm(c);
+ return led->percent;
+}
+
+static int
+led_pwm_blink_set(struct led_classdev *c,
+ unsigned long *on_ms,
+ unsigned long *off_ms)
+{
+ struct led_pwm *led = to_led_pwm(c);
+ struct pwm_channel_config cfg;
+
+ if (*on_ms == 0 && *off_ms == 0) {
+ *on_ms = 1000UL;
+ *off_ms = 1000UL;
}
+
+ cfg.config_mask = PWM_CONFIG_DUTY_NS
+ | PWM_CONFIG_PERIOD_NS;
+
+ cfg.duty_ns = *on_ms * 1000000UL;
+ cfg.period_ns = (*on_ms + *off_ms) * 1000000UL;
+
+ return pwm_config(led->pwm, &cfg);
}
-static int led_pwm_probe(struct platform_device *pdev)
+static int __devinit
+led_pwm_probe(struct platform_device *pdev)
{
- struct led_pwm_platform_data *pdata = pdev->dev.platform_data;
- struct led_pwm *cur_led;
- struct led_pwm_data *leds_data, *led_dat;
- int i, ret = 0;
+ struct pwm_led_platform_data *pdata = pdev->dev.platform_data;
+ struct led_pwm *led;
+ int ret;
- if (!pdata)
- return -EBUSY;
+ if (!pdata || !pdata->led_info)
+ return -EINVAL;
- leds_data = kzalloc(sizeof(struct led_pwm_data) * pdata->num_leds,
- GFP_KERNEL);
- if (!leds_data)
+ led = kzalloc(sizeof(*led), GFP_KERNEL);
+ if (!led)
return -ENOMEM;
- for (i = 0; i < pdata->num_leds; i++) {
- cur_led = &pdata->leds[i];
- led_dat = &leds_data[i];
-
- led_dat->pwm = pwm_request(cur_led->pwm_id,
- cur_led->name);
- if (IS_ERR(led_dat->pwm)) {
- dev_err(&pdev->dev, "unable to request PWM %d\n",
- cur_led->pwm_id);
- goto err;
- }
-
- led_dat->cdev.name = cur_led->name;
- led_dat->cdev.default_trigger = cur_led->default_trigger;
- led_dat->active_low = cur_led->active_low;
- led_dat->period = cur_led->pwm_period_ns;
- led_dat->cdev.brightness_set = led_pwm_set;
- led_dat->cdev.brightness = LED_OFF;
- led_dat->cdev.max_brightness = cur_led->max_brightness;
- led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME;
-
- ret = led_classdev_register(&pdev->dev, &led_dat->cdev);
- if (ret < 0) {
- pwm_free(led_dat->pwm);
- goto err;
- }
+ led->pwm = pwm_request(pdata->bus_id, pdata->chan,
+ pdata->led_info->name);
+ if (!led->pwm) {
+ ret = -EINVAL;
+ goto err_pwm_request;
}
- platform_set_drvdata(pdev, leds_data);
+ platform_set_drvdata(pdev, led);
- return 0;
+ led->led.name = pdata->led_info->name;
+ led->led.default_trigger = pdata->led_info->default_trigger;
+ led->led.brightness_set = led_pwm_brightness_set;
+ led->led.brightness_get = led_pwm_brightness_get;
+ led->led.blink_set = led_pwm_blink_set;
+ led->led.brightness = LED_OFF;
-err:
- if (i > 0) {
- for (i = i - 1; i >= 0; i--) {
- led_classdev_unregister(&leds_data[i].cdev);
- pwm_free(leds_data[i].pwm);
- }
- }
+ ret = pwm_config(led->pwm, pdata->config);
+ if (ret)
+ goto err_pwm_config;
+ pwm_start(led->pwm);
+
+ ret = led_classdev_register(&pdev->dev, &led->led);
+ if (ret < 0)
+ goto err_classdev_register;
- kfree(leds_data);
+ return 0;
+
+err_classdev_register:
+ pwm_stop(led->pwm);
+err_pwm_config:
+ pwm_release(led->pwm);
+err_pwm_request:
+ kfree(led);
return ret;
}
-static int __devexit led_pwm_remove(struct platform_device *pdev)
+static int __devexit
+led_pwm_remove(struct platform_device *pdev)
{
- int i;
- struct led_pwm_platform_data *pdata = pdev->dev.platform_data;
- struct led_pwm_data *leds_data;
+ struct led_pwm *led = platform_get_drvdata(pdev);
- leds_data = platform_get_drvdata(pdev);
+ led_classdev_unregister(&led->led);
- for (i = 0; i < pdata->num_leds; i++) {
- led_classdev_unregister(&leds_data[i].cdev);
- pwm_free(leds_data[i].pwm);
+ if (led->pwm) {
+ pwm_stop(led->pwm);
+ pwm_release(led->pwm);
}
- kfree(leds_data);
+ kfree(led);
return 0;
}
static struct platform_driver led_pwm_driver = {
- .probe = led_pwm_probe,
- .remove = __devexit_p(led_pwm_remove),
- .driver = {
- .name = "leds_pwm",
- .owner = THIS_MODULE,
+ .driver = {
+ .name = "leds-pwm",
+ .owner = THIS_MODULE,
},
+ .probe = led_pwm_probe,
+ .remove = led_pwm_remove,
};
-static int __init led_pwm_init(void)
+static int __init led_pwm_modinit(void)
{
return platform_driver_register(&led_pwm_driver);
}
+module_init(led_pwm_modinit);
-static void __exit led_pwm_exit(void)
+static void __exit led_pwm_modexit(void)
{
platform_driver_unregister(&led_pwm_driver);
}
+module_exit(led_pwm_modexit);
-module_init(led_pwm_init);
-module_exit(led_pwm_exit);
-
-MODULE_AUTHOR("Luotao Fu <l.fu@pengutronix.de>");
-MODULE_DESCRIPTION("PWM LED driver for PXA");
+MODULE_AUTHOR("Bill Gatliff <bgat@billgatliff.com>");
+MODULE_DESCRIPTION("Driver for LEDs with PWM-controlled brightness");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:leds-pwm");
diff --git a/include/linux/pwm/pwm-led.h b/include/linux/pwm/pwm-led.h
new file mode 100644
index 0000000..8ffeecc
--- /dev/null
+++ b/include/linux/pwm/pwm-led.h
@@ -0,0 +1,33 @@
+/*
+ * include/linux/pwm-led.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_LED_H
+#define __LINUX_PWM_LED_H
+
+struct led_info;
+struct pwm_channel_config;
+
+struct pwm_led_platform_data {
+ const char *bus_id;
+ int chan;
+ struct pwm_channel_config *config;
+ struct led_info *led_info;
+};
+
+#endif /* __LINUX_PWM_LED_H */
--
1.7.1
^ permalink raw reply related [flat|nested] 33+ messages in thread
* [PWM 05/10] LED "dim" trigger based on PWM control of the LED
2010-10-01 15:17 [PWM 01/10] API to consolidate PWM devices behind a common user and kernel interface Bill Gatliff
` (2 preceding siblings ...)
2010-10-01 15:17 ` [PWM 04/10] Implements PWM-based LED control Bill Gatliff
@ 2010-10-01 15:17 ` Bill Gatliff
2010-10-16 8:00 ` Grant Likely
2010-10-01 15:17 ` [PWM 06/10] Incorporate PWM API code into KBuild Bill Gatliff
` (9 subsequent siblings)
13 siblings, 1 reply; 33+ messages in thread
From: Bill Gatliff @ 2010-10-01 15:17 UTC (permalink / raw)
To: linux-embedded, linux-embedded; +Cc: Bill Gatliff
Signed-off-by: Bill Gatliff <bgat@billgatliff.com>
---
drivers/leds/ledtrig-dim.c | 96 ++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 96 insertions(+), 0 deletions(-)
create mode 100644 drivers/leds/ledtrig-dim.c
diff --git a/drivers/leds/ledtrig-dim.c b/drivers/leds/ledtrig-dim.c
new file mode 100644
index 0000000..8f93f44
--- /dev/null
+++ b/drivers/leds/ledtrig-dim.c
@@ -0,0 +1,96 @@
+/*
+ * LED Dim Trigger
+ *
+ * Copyright (C) 2010 Bill Gatliff <bgat@billgatliff.com>
+ *
+ * "Dims" an LED based on system load. Derived from Atsushi Nemoto's
+ * ledtrig-heartbeat.c.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/timer.h>
+#include <linux/sched.h>
+#include <linux/leds.h>
+
+#include "leds.h"
+
+struct dim_trig_data {
+ struct timer_list timer;
+};
+
+
+static void
+led_dim_function(unsigned long data)
+{
+ struct led_classdev *led_cdev = (struct led_classdev *)data;
+ struct dim_trig_data *dim_data = led_cdev->trigger_data;
+ unsigned int brightness;
+
+ brightness = ((LED_FULL - LED_OFF) * avenrun[0]) / EXP_1;
+ if (brightness > LED_FULL)
+ brightness = LED_FULL;
+
+ led_set_brightness(led_cdev, brightness);
+ mod_timer(&dim_data->timer, jiffies + msecs_to_jiffies(500));
+}
+
+
+static void
+dim_trig_activate(struct led_classdev *led_cdev)
+{
+ struct dim_trig_data *dim_data;
+
+ dim_data = kzalloc(sizeof(*dim_data), GFP_KERNEL);
+ if (!dim_data)
+ return;
+
+ led_cdev->trigger_data = dim_data;
+ setup_timer(&dim_data->timer,
+ led_dim_function, (unsigned long)led_cdev);
+ led_dim_function(dim_data->timer.data);
+}
+
+
+static void
+dim_trig_deactivate(struct led_classdev *led_cdev)
+{
+ struct dim_trig_data *dim_data = led_cdev->trigger_data;
+
+ if (dim_data) {
+ del_timer_sync(&dim_data->timer);
+ kfree(dim_data);
+ }
+}
+
+
+static struct led_trigger dim_led_trigger = {
+ .name = "dim",
+ .activate = dim_trig_activate,
+ .deactivate = dim_trig_deactivate,
+};
+
+
+static int __init dim_trig_init(void)
+{
+ return led_trigger_register(&dim_led_trigger);
+}
+module_init(dim_trig_init);
+
+
+static void __exit dim_trig_exit(void)
+{
+ led_trigger_unregister(&dim_led_trigger);
+}
+module_exit(dim_trig_exit);
+
+
+MODULE_AUTHOR("Bill Gatliff <bgat@billgatliff.com>");
+MODULE_DESCRIPTION("Dim LED trigger");
+MODULE_LICENSE("GPL");
--
1.7.1
^ permalink raw reply related [flat|nested] 33+ messages in thread
* [PWM 06/10] Incorporate PWM API code into KBuild
2010-10-01 15:17 [PWM 01/10] API to consolidate PWM devices behind a common user and kernel interface Bill Gatliff
` (3 preceding siblings ...)
2010-10-01 15:17 ` [PWM 05/10] LED "dim" trigger based on PWM control of the LED Bill Gatliff
@ 2010-10-01 15:17 ` Bill Gatliff
2010-10-16 8:02 ` Grant Likely
2010-10-01 15:17 ` [PWM 07/10] PWM API driver for MPC52xx GPT peripheral Bill Gatliff
` (8 subsequent siblings)
13 siblings, 1 reply; 33+ messages in thread
From: Bill Gatliff @ 2010-10-01 15:17 UTC (permalink / raw)
To: linux-embedded, linux-embedded; +Cc: Bill Gatliff
Signed-off-by: Bill Gatliff <bgat@billgatliff.com>
---
drivers/Kconfig | 2 ++
drivers/Makefile | 2 ++
drivers/leds/Kconfig | 22 ++++++++++++++++------
drivers/leds/Makefile | 2 ++
drivers/pwm/Kconfig | 28 ++++++++++++++++++++++++++++
drivers/pwm/Makefile | 6 ++++++
6 files changed, 56 insertions(+), 6 deletions(-)
create mode 100644 drivers/pwm/Kconfig
create mode 100644 drivers/pwm/Makefile
diff --git a/drivers/Kconfig b/drivers/Kconfig
index a2b902f..60390cb 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 a2aea53..fa7ca1c 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/leds/Kconfig b/drivers/leds/Kconfig
index 3af2cde..b434fa3 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -249,12 +249,6 @@ config LEDS_DAC124S085
This option enables support for DAC124S085 SPI DAC from NatSemi,
which can be used to control up to four LEDs.
-config LEDS_PWM
- tristate "PWM driven LED Support"
- depends on HAVE_PWM
- help
- This option enables support for pwm driven LEDs
-
config LEDS_REGULATOR
tristate "REGULATOR driven LED support"
depends on REGULATOR
@@ -354,6 +348,14 @@ config LEDS_TRIGGER_HEARTBEAT
load average.
If unsure, say Y.
+config LEDS_TRIGGER_DIM
+ tristate "LED Dimmer Trigger"
+ depends on LEDS_TRIGGERS
+ help
+ Regulates the brightness of an LED based on the 1-minute CPU
+ load average. Ideal for PWM-driven LEDs.
+ If unsure, say Y.
+
config LEDS_TRIGGER_BACKLIGHT
tristate "LED backlight Trigger"
help
@@ -374,6 +376,14 @@ config LEDS_TRIGGER_GPIO
If unsure, say N.
+config LEDS_TRIGGER_DIM
+ tristate "LED Dimmer Trigger"
+ depends on LEDS_TRIGGERS
+ help
+ Regulates the brightness of an LED based on the 1-minute CPU
+ load average. Ideal for PWM-driven LEDs.
+ If unsure, say Y.
+
config LEDS_TRIGGER_DEFAULT_ON
tristate "LED Default ON Trigger"
help
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index 7d6b958..a4ccea4 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -22,6 +22,7 @@ obj-$(CONFIG_LEDS_COBALT_RAQ) += leds-cobalt-raq.o
obj-$(CONFIG_LEDS_SUNFIRE) += leds-sunfire.o
obj-$(CONFIG_LEDS_PCA9532) += leds-pca9532.o
obj-$(CONFIG_LEDS_GPIO) += leds-gpio.o
+obj-$(CONFIG_LEDS_PWM) += leds-pwm.o
obj-$(CONFIG_LEDS_LP3944) += leds-lp3944.o
obj-$(CONFIG_LEDS_CLEVO_MAIL) += leds-clevo-mail.o
obj-$(CONFIG_LEDS_HP6XX) += leds-hp6xx.o
@@ -46,6 +47,7 @@ obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o
obj-$(CONFIG_LEDS_TRIGGER_TIMER) += ledtrig-timer.o
obj-$(CONFIG_LEDS_TRIGGER_IDE_DISK) += ledtrig-ide-disk.o
obj-$(CONFIG_LEDS_TRIGGER_HEARTBEAT) += ledtrig-heartbeat.o
+obj-$(CONFIG_LEDS_TRIGGER_DIM) += ledtrig-dim.o
obj-$(CONFIG_LEDS_TRIGGER_BACKLIGHT) += ledtrig-backlight.o
obj-$(CONFIG_LEDS_TRIGGER_GPIO) += ledtrig-gpio.o
obj-$(CONFIG_LEDS_TRIGGER_DEFAULT_ON) += ledtrig-default-on.o
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
new file mode 100644
index 0000000..0584c25
--- /dev/null
+++ b/drivers/pwm/Kconfig
@@ -0,0 +1,28 @@
+#
+# PWM infrastructure and devices
+#
+
+menuconfig GENERIC_PWM
+ tristate "PWM Support"
+ depends on SYSFS
+ help
+ This enables PWM support through the generic PWM API.
+ 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.
+ 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/drivers/pwm/Makefile b/drivers/pwm/Makefile
new file mode 100644
index 0000000..e8cacc5
--- /dev/null
+++ b/drivers/pwm/Makefile
@@ -0,0 +1,6 @@
+#
+# Makefile for pwm devices
+#
+obj-y := pwm.o
+obj-$(CONFIG_ATMEL_PWM) += atmel-pwm.o
+obj-$(CONFIG_GPIO_PWM) += gpio.o
--
1.7.1
^ permalink raw reply related [flat|nested] 33+ messages in thread
* [PWM 07/10] PWM API driver for MPC52xx GPT peripheral
2010-10-01 15:17 [PWM 01/10] API to consolidate PWM devices behind a common user and kernel interface Bill Gatliff
` (4 preceding siblings ...)
2010-10-01 15:17 ` [PWM 06/10] Incorporate PWM API code into KBuild Bill Gatliff
@ 2010-10-01 15:17 ` Bill Gatliff
2010-10-01 15:17 ` [PWM 08/10] Initial support for PXA PWM peripheral; compile-tested only Bill Gatliff
` (7 subsequent siblings)
13 siblings, 0 replies; 33+ messages in thread
From: Bill Gatliff @ 2010-10-01 15:17 UTC (permalink / raw)
To: linux-embedded, linux-embedded; +Cc: Bill Gatliff
Signed-off-by: Bill Gatliff <bgat@billgatliff.com>
---
arch/powerpc/platforms/52xx/mpc52xx_gpt.c | 196 ++++++++++++++++++++++++++++-
1 files changed, 194 insertions(+), 2 deletions(-)
diff --git a/arch/powerpc/platforms/52xx/mpc52xx_gpt.c b/arch/powerpc/platforms/52xx/mpc52xx_gpt.c
index fea833e..76f8c40 100644
--- a/arch/powerpc/platforms/52xx/mpc52xx_gpt.c
+++ b/arch/powerpc/platforms/52xx/mpc52xx_gpt.c
@@ -1,6 +1,7 @@
/*
* MPC5200 General Purpose Timer device driver
*
+ * Copyright (c) 2010 Bill Gatliff <bgat@billgatliff.com>
* Copyright (c) 2009 Secret Lab Technologies Ltd.
* Copyright (c) 2008 Sascha Hauer <s.hauer@pengutronix.de>, Pengutronix
*
@@ -50,6 +51,12 @@
* IO, or it can be an Open Collector (OC) output. At the moment it is the
* responsibility of either the bootloader or the platform setup code to set
* the output mode. This driver does not change the output mode setting.
+ *
+ * To use the PWM function, the following property must be added to
+ * the device tree node for the gpt device:
+ * pwm-controller;
+ * TODO: we could set polarity, initial period and duty cycle, on/off,
+ * and whatnot inside the dts file...
*/
#include <linux/device.h>
@@ -66,11 +73,12 @@
#include <linux/watchdog.h>
#include <linux/miscdevice.h>
#include <linux/uaccess.h>
+#include <linux/pwm/pwm.h>
#include <asm/div64.h>
#include <asm/mpc52xx.h>
MODULE_DESCRIPTION("Freescale MPC52xx gpt driver");
-MODULE_AUTHOR("Sascha Hauer, Grant Likely, Albrecht Dreß");
+MODULE_AUTHOR("Sascha Hauer, Grant Likely, Albrecht Dreß, Bill Gatliff");
MODULE_LICENSE("GPL");
/**
@@ -96,6 +104,9 @@ struct mpc52xx_gpt_priv {
#if defined(CONFIG_GPIOLIB)
struct gpio_chip gc;
#endif
+#if defined(CONFIG_GENERIC_PWM)
+ struct pwm_device pwm;
+#endif
};
LIST_HEAD(mpc52xx_gpt_list);
@@ -126,6 +137,10 @@ DEFINE_MUTEX(mpc52xx_gpt_list_mutex);
#define MPC52xx_GPT_STATUS_IRQMASK (0x000f)
+#define MPC52xx_GPT_PWM_WIDTH_MASK (0xffff0000)
+#define MPC52xx_GPT_PWM_PWMOP (0x100)
+#define MPC52xx_GPT_PWM_LOAD (0x1)
+
#define MPC52xx_GPT_CAN_WDT (1 << 0)
#define MPC52xx_GPT_IS_WDT (1 << 1)
@@ -275,6 +290,183 @@ mpc52xx_gpt_irq_setup(struct mpc52xx_gpt_priv *gpt, struct device_node *node)
/* ---------------------------------------------------------------------
+ * PWM API hooks
+ */
+#if defined(CONFIG_GENERIC_PWM)
+static inline struct mpc52xx_gpt_priv *pwm_to_mpc52xx_gpt(const struct pwm_channel *p)
+{
+ return container_of(p->pwm, struct mpc52xx_gpt_priv, pwm);
+}
+
+static int mpc52xx_gpt_pwm_request(struct pwm_channel *p)
+{
+ struct mpc52xx_gpt_priv *gpt = pwm_to_mpc52xx_gpt(p);
+
+ /* TODO: add hooks to prevent conflicts in use */
+ p->tick_hz = gpt->ipb_freq;
+ return 0;
+}
+
+static void mpc52xx_gpt_pwm_release(struct pwm_channel *p)
+{
+ /* TODO: add hooks to prevent conflicts in use */
+}
+
+static int __mpc52xx_gpt_pwm_config_period_ticks(struct pwm_channel *p,
+ struct pwm_channel_config *c)
+{
+ struct mpc52xx_gpt_priv *gpt = pwm_to_mpc52xx_gpt(p);
+ u64 prescale, count;
+
+ prescale = (c->period_ticks >> 16) + 1;
+ count = c->period_ticks;
+ do_div(count, prescale);
+ out_be32(&gpt->regs->count, prescale << 16 | count);
+
+ p->period_ticks = count * prescale;
+ dev_dbg(p->pwm->dev, "prescale %4x count %4x period_ticks %8x\n",
+ (unsigned int)prescale, (unsigned int)count,
+ (unsigned int)p->period_ticks);
+
+ return 0;
+}
+
+static int __mpc52xx_gpt_pwm_config_duty_ticks(struct pwm_channel *p,
+ struct pwm_channel_config *c)
+{
+ struct mpc52xx_gpt_priv *gpt = pwm_to_mpc52xx_gpt(p);
+ unsigned long flags;
+ u64 width = c->duty_ticks;
+ u32 prescale, count;
+
+ spin_lock_irqsave(&gpt->lock, flags);
+
+ count = in_be32(&gpt->regs->count);
+ prescale = count >> 16;
+ count &= 0xffffUL;
+
+ /* TODO: this probably isn't the best place to do a divide... */
+ do_div(width, prescale);
+
+ clrsetbits_be32(&gpt->regs->pwm, MPC52xx_GPT_PWM_WIDTH_MASK,
+ MPC52xx_GPT_PWM_WIDTH_MASK & (width << 16));
+ spin_unlock_irqrestore(&gpt->lock, flags);
+
+ p->duty_ticks = width * prescale;
+ dev_dbg(p->pwm->dev, "prescale %4x count %4x width %4x duty_ticks %8x\n",
+ (unsigned int)prescale, (unsigned int)count,
+ (unsigned int)width, (unsigned int)p->duty_ticks);
+
+ return 0;
+}
+
+static int __mpc52xx_gpt_pwm_config_polarity(struct pwm_channel *p,
+ struct pwm_channel_config *c)
+{
+ struct mpc52xx_gpt_priv *gpt = pwm_to_mpc52xx_gpt(p);
+ unsigned long flags;
+
+ spin_lock_irqsave(&gpt->lock, flags);
+ if (c->polarity)
+ setbits32(&gpt->regs->pwm, MPC52xx_GPT_PWM_PWMOP);
+ else
+ clrbits32(&gpt->regs->pwm, MPC52xx_GPT_PWM_PWMOP);
+ p->active_high = c->polarity ? 1 : 0;
+ spin_unlock_irqrestore(&gpt->lock, flags);
+ return 0;
+}
+
+static int __mpc52xx_gpt_pwm_start(struct pwm_channel *p)
+{
+ struct mpc52xx_gpt_priv *gpt = pwm_to_mpc52xx_gpt(p);
+ unsigned long flags;
+
+ spin_lock_irqsave(&gpt->lock, flags);
+ clrsetbits_be32(&gpt->regs->mode, MPC52xx_GPT_MODE_MS_MASK,
+ MPC52xx_GPT_MODE_MS_PWM);
+ spin_unlock_irqrestore(&gpt->lock, flags);
+ return 0;
+}
+
+static int __mpc52xx_gpt_pwm_stop(struct pwm_channel *p)
+{
+ struct mpc52xx_gpt_priv *gpt = pwm_to_mpc52xx_gpt(p);
+ unsigned long flags;
+
+ spin_lock_irqsave(&gpt->lock, flags);
+ clrbits32(&gpt->regs->mode, MPC52xx_GPT_MODE_MS_MASK);
+ spin_unlock_irqrestore(&gpt->lock, flags);
+ return 0;
+}
+
+static int mpc52xx_gpt_pwm_config_nosleep(struct pwm_channel *p,
+ struct pwm_channel_config *c)
+{
+ int ret = -EINVAL;
+
+ if (c->config_mask & PWM_CONFIG_PERIOD_TICKS)
+ if ((ret = __mpc52xx_gpt_pwm_config_period_ticks(p, c)))
+ goto done;
+
+ if (c->config_mask & PWM_CONFIG_DUTY_TICKS)
+ if ((ret = __mpc52xx_gpt_pwm_config_duty_ticks(p, c)))
+ goto done;
+
+ if (c->config_mask & PWM_CONFIG_POLARITY)
+ if ((ret = __mpc52xx_gpt_pwm_config_polarity(p, c)))
+ goto done;
+
+ if (c->config_mask & PWM_CONFIG_START)
+ if ((ret = __mpc52xx_gpt_pwm_start(p)))
+ goto done;
+
+ if (c->config_mask & PWM_CONFIG_STOP)
+ if ((ret = __mpc52xx_gpt_pwm_stop(p)))
+ goto done;
+
+done:
+ return ret;
+}
+
+static int mpc52xx_gpt_pwm_config(struct pwm_channel *p,
+ struct pwm_channel_config *c)
+{
+ dev_dbg(p->pwm->dev, "config_mask %x\n", c->config_mask);
+
+ if (!mpc52xx_gpt_pwm_config_nosleep(p, c))
+ return 0;
+
+ might_sleep();
+
+ /* TODO: add other API entry points */
+
+ return -EINVAL;
+}
+
+static void
+mpc52xx_gpt_pwm_setup(struct mpc52xx_gpt_priv *gpt, struct device_node *node)
+{
+ if (!of_find_property(node, "pwm-controller", NULL))
+ return;
+
+ gpt->pwm.dev = gpt->dev;
+ gpt->pwm.bus_id = dev_name(gpt->dev);
+ gpt->pwm.nchan = 1;
+ gpt->pwm.owner = THIS_MODULE;
+
+ gpt->pwm.request = mpc52xx_gpt_pwm_request;
+ gpt->pwm.release = mpc52xx_gpt_pwm_release;
+ gpt->pwm.config_nosleep = mpc52xx_gpt_pwm_config_nosleep;
+ gpt->pwm.config = mpc52xx_gpt_pwm_config;
+
+ pwm_register(&gpt->pwm);
+}
+#else
+static void
+mpc52xx_gpt_pwm_setup(struct mpc52xx_gpt_priv *p, struct device_node *np) {}
+#endif
+
+/* ---------------------------------------------------------------------
* GPIOLIB hooks
*/
#if defined(CONFIG_GPIOLIB)
@@ -739,9 +931,9 @@ static int __devinit mpc52xx_gpt_probe(struct platform_device *ofdev,
}
dev_set_drvdata(&ofdev->dev, gpt);
-
mpc52xx_gpt_gpio_setup(gpt, ofdev->dev.of_node);
mpc52xx_gpt_irq_setup(gpt, ofdev->dev.of_node);
+ mpc52xx_gpt_pwm_setup(gpt, ofdev->dev.of_node);
mutex_lock(&mpc52xx_gpt_list_mutex);
list_add(&gpt->list, &mpc52xx_gpt_list);
--
1.7.1
^ permalink raw reply related [flat|nested] 33+ messages in thread
* [PWM 08/10] Initial support for PXA PWM peripheral; compile-tested only
2010-10-01 15:17 [PWM 01/10] API to consolidate PWM devices behind a common user and kernel interface Bill Gatliff
` (6 preceding siblings ...)
2010-10-01 15:17 ` [PWM 08/10] Initial support for PXA PWM peripheral; compile-tested only Bill Gatliff
@ 2010-10-01 15:17 ` Bill Gatliff
2010-10-01 15:17 ` [PWM 09/10] Build pwm.o only if CONFIG_GENERIC_PWM is set Bill Gatliff
` (5 subsequent siblings)
13 siblings, 0 replies; 33+ messages in thread
From: Bill Gatliff @ 2010-10-01 15:17 UTC (permalink / raw)
To: linux-embedded, linux-embedded; +Cc: Bill Gatliff
Signed-off-by: Bill Gatliff <bgat@billgatliff.com>
---
drivers/pwm/pxa-pwm.c | 322 +++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 322 insertions(+), 0 deletions(-)
create mode 100644 drivers/pwm/pxa-pwm.c
diff --git a/drivers/pwm/pxa-pwm.c b/drivers/pwm/pxa-pwm.c
new file mode 100644
index 0000000..937ab41
--- /dev/null
+++ b/drivers/pwm/pxa-pwm.c
@@ -0,0 +1,322 @@
+/*
+ * drivers/pwm/pxa-pwm.c
+ *
+ * Driver for PXA PWM controllers
+ *
+ * Copyright (c) 2010 Bill Gatliff <bgat@billgatliff.com>
+ * Copyright (c) 2008 Eric Miao (eric.miao@marvell.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.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/pwm/pwm.h>
+
+#include <asm/div64.h>
+
+#define HAS_SECONDARY_PWM 0x10
+
+static const struct platform_device_id pxa_pwm_id_table[] = {
+ /* PWM has_secondary_pwm? */
+ { "pxa25x-pwm", 0 },
+ { "pxa27x-pwm", 0 | HAS_SECONDARY_PWM },
+ { "pxa168-pwm", 1 },
+ { "pxa910-pwm", 1 },
+ { },
+};
+MODULE_DEVICE_TABLE(platform, pxa_pwm_id_table);
+
+/* PWM registers and bits definitions */
+enum {
+ PWMCR = 0,
+ PWMDCR = 0x4,
+ PWMPCR = 0x8,
+
+ PWMCR_SD = (1 << 6),
+ PWMDCR_FD = (1 << 10),
+};
+
+struct pxa_pwm {
+ struct pwm_device pwm;
+ spinlock_t lock;
+
+ struct clk *clk;
+ int clk_enabled;
+ void __iomem *mmio_base;
+};
+
+static inline struct pxa_pwm *to_pxa_pwm(const struct pwm_channel *p)
+{
+ return container_of(p->pwm, struct pxa_pwm, pwm);
+}
+
+static inline int __pxa_pwm_enable(struct pwm_channel *p)
+{
+ struct pxa_pwm *pxa = to_pxa_pwm(p);
+ int ret = 0;
+
+ /* TODO: does the hardware really permit independent control
+ * of both the primary and secondary (if present) channels?
+ * According to Eric Miao's code, it doesn't--- or he didn't
+ * use it that way. Revisit, looking for ways to
+ * independently control either channel.
+ */
+
+ if (!pxa->clk_enabled) {
+ ret = clk_enable(pxa->clk);
+ if (ret)
+ return ret;
+
+ pxa->clk_enabled = 1;
+ }
+
+ return ret;
+}
+
+static inline int __pxa_pwm_disable(struct pwm_channel *p)
+{
+ struct pxa_pwm *pxa = to_pxa_pwm(p);
+
+ /* TODO: This is how Eric Miao did it, but I'm concerned that
+ * this won't always drive the PWM output signal to its
+ * inactive state. Revisit.
+ */
+
+ if (pxa->clk_enabled) {
+ clk_disable(pxa->clk);
+ pxa->clk_enabled = 0;
+ }
+
+ return 0;
+}
+
+/*
+ * period_ns = 10^9 * (PRESCALE + 1) * (PV + 1) / PWM_CLK_RATE
+ * duty_ns = 10^9 * (PRESCALE + 1) * DC / PWM_CLK_RATE
+ */
+static int __pxa_config_duty_ticks(struct pwm_channel *p,
+ struct pwm_channel_config *c)
+{
+ struct pxa_pwm *pxa = to_pxa_pwm(p);
+ void *io;
+
+ p->duty_ticks = c->duty_ticks;
+ io = pxa->mmio_base + (p->chan * 0x10);
+
+ /* NOTE: The clock to the PWM peripheral must be running in
+ * order to write to the peripheral control registers.
+ *
+ * TODO: What does setting PWMPC == PWMDC do? What about PWMDC
+ * == 0 and/or PWMPC == 0? Investigate.
+ */
+ clk_enable(pxa->clk);
+ if (p->duty_ticks == p->period_ticks)
+ __raw_writel(PWMDCR_FD, io + PWMDCR);
+ else
+ __raw_writel(p->duty_ticks, io + PWMDCR);
+ clk_disable(pxa->clk);
+
+ return 0;
+}
+
+static int __pxa_config_period_ticks(struct pwm_channel *p,
+ struct pwm_channel_config *c)
+{
+ struct pxa_pwm *pxa = to_pxa_pwm(p);
+ void *io;
+ unsigned long prescale, pv;
+
+ prescale = (c->period_ticks - 1) / 1024;
+ if (prescale > 63)
+ return -EINVAL;
+
+ pv = c->period_ticks / (prescale + 1) - 1;
+ p->period_ticks = c->period_ticks;
+
+ io = pxa->mmio_base + (p->chan * 0x10);
+
+ /* NOTE: the clock to PWM has to be enabled first
+ * before writing to the registers.
+ *
+ * TODO: see also the TODOs in __pxa_config_duty_ticks().
+ */
+ clk_enable(pxa->clk);
+ __raw_writel(prescale, io + PWMCR);
+ if (p->period_ticks == p->duty_ticks)
+ __raw_writel(PWMDCR_FD, io + PWMDCR);
+ __raw_writel(pv, io + PWMPCR);
+ clk_disable(pxa->clk);
+
+ return 0;
+}
+
+static int pxa_pwm_request(struct pwm_channel *p)
+{
+ struct pxa_pwm *pxa = to_pxa_pwm(p);
+
+ p->tick_hz = clk_get_rate(pxa->clk);
+ return 0;
+}
+
+
+static int pxa_pwm_config_nosleep(struct pwm_channel *p,
+ struct pwm_channel_config *c)
+{
+ int ret = 0;
+ unsigned long flags;
+
+ if (!(c->config_mask & (PWM_CONFIG_STOP
+ | PWM_CONFIG_START
+ | PWM_CONFIG_DUTY_TICKS
+ | PWM_CONFIG_PERIOD_TICKS)))
+ return -EINVAL;
+
+ spin_lock_irqsave(&p->lock, flags);
+
+ if (c->config_mask & PWM_CONFIG_STOP)
+ __pxa_pwm_disable(p);
+
+ if (c->config_mask & PWM_CONFIG_PERIOD_TICKS)
+ __pxa_config_period_ticks(p, c);
+
+ if (c->config_mask & PWM_CONFIG_DUTY_TICKS)
+ __pxa_config_duty_ticks(p, c);
+
+ if (c->config_mask & PWM_CONFIG_START)
+ __pxa_pwm_enable(p);
+
+ spin_unlock_irqrestore(&p->lock, flags);
+ return ret;
+}
+
+static int pxa_pwm_config(struct pwm_channel *p,
+ struct pwm_channel_config *c)
+{
+ return pxa_pwm_config_nosleep(p, c);
+}
+
+static int __init pxa_pwm_probe(struct platform_device *pdev)
+{
+ const struct platform_device_id *id = platform_get_device_id(pdev);
+ struct pxa_pwm *pxa;
+ struct resource *r;
+ int ret = 0;
+
+ r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (IS_ERR_OR_NULL(r)) {
+ dev_err(&pdev->dev, "error, missing mmio_base resource\n");
+ return -EINVAL;
+ }
+
+ r = request_mem_region(r->start, resource_size(r), pdev->name);
+ if (IS_ERR_OR_NULL(r)) {
+ dev_err(&pdev->dev, "error, failed to request mmio_base resource\n");
+ return -EBUSY;
+ }
+
+ pxa = kzalloc(sizeof *pxa, GFP_KERNEL);
+ if (IS_ERR_OR_NULL(pxa)) {
+ dev_err(&pdev->dev, "failed to allocate memory\n");
+ ret = -ENOMEM;
+ goto err_kzalloc;
+ }
+
+ pxa->mmio_base = ioremap(r->start, resource_size(r));
+ if (IS_ERR_OR_NULL(pxa->mmio_base)) {
+ dev_err(&pdev->dev, "error, failed to ioremap() registers\n");
+ ret = -ENODEV;
+ goto err_ioremap;
+ }
+
+ pxa->clk = clk_get(&pdev->dev, NULL);
+ if (IS_ERR_OR_NULL(pxa->clk)) {
+ ret = PTR_ERR(pxa->clk);
+ if (!ret)
+ ret = -EINVAL;
+ goto err_clk_get;
+ }
+ pxa->clk_enabled = 0;
+
+ spin_lock_init(&pxa->lock);
+
+ pxa->pwm.dev = &pdev->dev;
+ pxa->pwm.bus_id = dev_name(&pdev->dev);
+ pxa->pwm.owner = THIS_MODULE;
+ pxa->pwm.request = pxa_pwm_request;
+ pxa->pwm.config_nosleep = pxa_pwm_config_nosleep;
+ pxa->pwm.config = pxa_pwm_config;
+
+ if (id->driver_data & HAS_SECONDARY_PWM)
+ pxa->pwm.nchan = 2;
+ else
+ pxa->pwm.nchan = 1;
+
+ ret = pwm_register(&pxa->pwm);
+
+ if (ret)
+ goto err_pwm_register;
+
+ platform_set_drvdata(pdev, pxa);
+ return 0;
+
+err_pwm_register:
+ clk_put(pxa->clk);
+err_clk_get:
+ iounmap(pxa->mmio_base);
+err_ioremap:
+ kfree(pxa);
+err_kzalloc:
+ release_mem_region(r->start, resource_size(r));
+ return ret;
+}
+
+static int __exit pxa_pwm_remove(struct platform_device *pdev)
+{
+ struct pxa_pwm *pxa = platform_get_drvdata(pdev);
+
+ if (IS_ERR_OR_NULL(pxa))
+ return -ENODEV;
+
+ pwm_unregister(&pxa->pwm);
+ clk_put(pxa->clk);
+ iounmap(pxa->mmio_base);
+ kfree(pxa);
+ platform_set_drvdata(pdev, NULL);
+ return 0;
+}
+
+static struct platform_driver pwm_driver = {
+ .driver = {
+ .name = "pxa25x-pwm",
+ .owner = THIS_MODULE,
+ },
+ .probe = pxa_pwm_probe,
+ .remove = pxa_pwm_remove,
+ .id_table = pxa_pwm_id_table,
+};
+
+static int __init pxa_pwm_init(void)
+{
+ return platform_driver_register(&pwm_driver);
+}
+/* TODO: do we have to do this at arch_initcall? */
+module_init(pxa_pwm_init);
+
+static void __exit pxa_pwm_exit(void)
+{
+ platform_driver_unregister(&pwm_driver);
+}
+module_exit(pxa_pwm_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Bill Gatliff <bgat@billgatliff.com>");
+MODULE_AUTHOR("Eric Miao (eric.miao@marvell.com>");
+MODULE_DESCRIPTION("Platform and PWM API drivers for PXA PWM peripheral");
--
1.7.1
^ permalink raw reply related [flat|nested] 33+ messages in thread
* [PWM 08/10] Initial support for PXA PWM peripheral; compile-tested only
2010-10-01 15:17 [PWM 01/10] API to consolidate PWM devices behind a common user and kernel interface Bill Gatliff
` (5 preceding siblings ...)
2010-10-01 15:17 ` [PWM 07/10] PWM API driver for MPC52xx GPT peripheral Bill Gatliff
@ 2010-10-01 15:17 ` Bill Gatliff
2010-10-01 15:17 ` Bill Gatliff
` (6 subsequent siblings)
13 siblings, 0 replies; 33+ messages in thread
From: Bill Gatliff @ 2010-10-01 15:17 UTC (permalink / raw)
To: linux-embedded, linux-embedded; +Cc: Bill Gatliff
Signed-off-by: Bill Gatliff <bgat@billgatliff.com>
---
drivers/pwm/pxa-pwm.c | 322 +++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 322 insertions(+), 0 deletions(-)
create mode 100644 drivers/pwm/pxa-pwm.c
diff --git a/drivers/pwm/pxa-pwm.c b/drivers/pwm/pxa-pwm.c
new file mode 100644
index 0000000..937ab41
--- /dev/null
+++ b/drivers/pwm/pxa-pwm.c
@@ -0,0 +1,322 @@
+/*
+ * drivers/pwm/pxa-pwm.c
+ *
+ * Driver for PXA PWM controllers
+ *
+ * Copyright (c) 2010 Bill Gatliff <bgat@billgatliff.com>
+ * Copyright (c) 2008 Eric Miao (eric.miao@marvell.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.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/err.h>
+#include <linux/clk.h>
+#include <linux/io.h>
+#include <linux/pwm/pwm.h>
+
+#include <asm/div64.h>
+
+#define HAS_SECONDARY_PWM 0x10
+
+static const struct platform_device_id pxa_pwm_id_table[] = {
+ /* PWM has_secondary_pwm? */
+ { "pxa25x-pwm", 0 },
+ { "pxa27x-pwm", 0 | HAS_SECONDARY_PWM },
+ { "pxa168-pwm", 1 },
+ { "pxa910-pwm", 1 },
+ { },
+};
+MODULE_DEVICE_TABLE(platform, pxa_pwm_id_table);
+
+/* PWM registers and bits definitions */
+enum {
+ PWMCR = 0,
+ PWMDCR = 0x4,
+ PWMPCR = 0x8,
+
+ PWMCR_SD = (1 << 6),
+ PWMDCR_FD = (1 << 10),
+};
+
+struct pxa_pwm {
+ struct pwm_device pwm;
+ spinlock_t lock;
+
+ struct clk *clk;
+ int clk_enabled;
+ void __iomem *mmio_base;
+};
+
+static inline struct pxa_pwm *to_pxa_pwm(const struct pwm_channel *p)
+{
+ return container_of(p->pwm, struct pxa_pwm, pwm);
+}
+
+static inline int __pxa_pwm_enable(struct pwm_channel *p)
+{
+ struct pxa_pwm *pxa = to_pxa_pwm(p);
+ int ret = 0;
+
+ /* TODO: does the hardware really permit independent control
+ * of both the primary and secondary (if present) channels?
+ * According to Eric Miao's code, it doesn't--- or he didn't
+ * use it that way. Revisit, looking for ways to
+ * independently control either channel.
+ */
+
+ if (!pxa->clk_enabled) {
+ ret = clk_enable(pxa->clk);
+ if (ret)
+ return ret;
+
+ pxa->clk_enabled = 1;
+ }
+
+ return ret;
+}
+
+static inline int __pxa_pwm_disable(struct pwm_channel *p)
+{
+ struct pxa_pwm *pxa = to_pxa_pwm(p);
+
+ /* TODO: This is how Eric Miao did it, but I'm concerned that
+ * this won't always drive the PWM output signal to its
+ * inactive state. Revisit.
+ */
+
+ if (pxa->clk_enabled) {
+ clk_disable(pxa->clk);
+ pxa->clk_enabled = 0;
+ }
+
+ return 0;
+}
+
+/*
+ * period_ns = 10^9 * (PRESCALE + 1) * (PV + 1) / PWM_CLK_RATE
+ * duty_ns = 10^9 * (PRESCALE + 1) * DC / PWM_CLK_RATE
+ */
+static int __pxa_config_duty_ticks(struct pwm_channel *p,
+ struct pwm_channel_config *c)
+{
+ struct pxa_pwm *pxa = to_pxa_pwm(p);
+ void *io;
+
+ p->duty_ticks = c->duty_ticks;
+ io = pxa->mmio_base + (p->chan * 0x10);
+
+ /* NOTE: The clock to the PWM peripheral must be running in
+ * order to write to the peripheral control registers.
+ *
+ * TODO: What does setting PWMPC == PWMDC do? What about PWMDC
+ * == 0 and/or PWMPC == 0? Investigate.
+ */
+ clk_enable(pxa->clk);
+ if (p->duty_ticks == p->period_ticks)
+ __raw_writel(PWMDCR_FD, io + PWMDCR);
+ else
+ __raw_writel(p->duty_ticks, io + PWMDCR);
+ clk_disable(pxa->clk);
+
+ return 0;
+}
+
+static int __pxa_config_period_ticks(struct pwm_channel *p,
+ struct pwm_channel_config *c)
+{
+ struct pxa_pwm *pxa = to_pxa_pwm(p);
+ void *io;
+ unsigned long prescale, pv;
+
+ prescale = (c->period_ticks - 1) / 1024;
+ if (prescale > 63)
+ return -EINVAL;
+
+ pv = c->period_ticks / (prescale + 1) - 1;
+ p->period_ticks = c->period_ticks;
+
+ io = pxa->mmio_base + (p->chan * 0x10);
+
+ /* NOTE: the clock to PWM has to be enabled first
+ * before writing to the registers.
+ *
+ * TODO: see also the TODOs in __pxa_config_duty_ticks().
+ */
+ clk_enable(pxa->clk);
+ __raw_writel(prescale, io + PWMCR);
+ if (p->period_ticks == p->duty_ticks)
+ __raw_writel(PWMDCR_FD, io + PWMDCR);
+ __raw_writel(pv, io + PWMPCR);
+ clk_disable(pxa->clk);
+
+ return 0;
+}
+
+static int pxa_pwm_request(struct pwm_channel *p)
+{
+ struct pxa_pwm *pxa = to_pxa_pwm(p);
+
+ p->tick_hz = clk_get_rate(pxa->clk);
+ return 0;
+}
+
+
+static int pxa_pwm_config_nosleep(struct pwm_channel *p,
+ struct pwm_channel_config *c)
+{
+ int ret = 0;
+ unsigned long flags;
+
+ if (!(c->config_mask & (PWM_CONFIG_STOP
+ | PWM_CONFIG_START
+ | PWM_CONFIG_DUTY_TICKS
+ | PWM_CONFIG_PERIOD_TICKS)))
+ return -EINVAL;
+
+ spin_lock_irqsave(&p->lock, flags);
+
+ if (c->config_mask & PWM_CONFIG_STOP)
+ __pxa_pwm_disable(p);
+
+ if (c->config_mask & PWM_CONFIG_PERIOD_TICKS)
+ __pxa_config_period_ticks(p, c);
+
+ if (c->config_mask & PWM_CONFIG_DUTY_TICKS)
+ __pxa_config_duty_ticks(p, c);
+
+ if (c->config_mask & PWM_CONFIG_START)
+ __pxa_pwm_enable(p);
+
+ spin_unlock_irqrestore(&p->lock, flags);
+ return ret;
+}
+
+static int pxa_pwm_config(struct pwm_channel *p,
+ struct pwm_channel_config *c)
+{
+ return pxa_pwm_config_nosleep(p, c);
+}
+
+static int __init pxa_pwm_probe(struct platform_device *pdev)
+{
+ const struct platform_device_id *id = platform_get_device_id(pdev);
+ struct pxa_pwm *pxa;
+ struct resource *r;
+ int ret = 0;
+
+ r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (IS_ERR_OR_NULL(r)) {
+ dev_err(&pdev->dev, "error, missing mmio_base resource\n");
+ return -EINVAL;
+ }
+
+ r = request_mem_region(r->start, resource_size(r), pdev->name);
+ if (IS_ERR_OR_NULL(r)) {
+ dev_err(&pdev->dev, "error, failed to request mmio_base resource\n");
+ return -EBUSY;
+ }
+
+ pxa = kzalloc(sizeof *pxa, GFP_KERNEL);
+ if (IS_ERR_OR_NULL(pxa)) {
+ dev_err(&pdev->dev, "failed to allocate memory\n");
+ ret = -ENOMEM;
+ goto err_kzalloc;
+ }
+
+ pxa->mmio_base = ioremap(r->start, resource_size(r));
+ if (IS_ERR_OR_NULL(pxa->mmio_base)) {
+ dev_err(&pdev->dev, "error, failed to ioremap() registers\n");
+ ret = -ENODEV;
+ goto err_ioremap;
+ }
+
+ pxa->clk = clk_get(&pdev->dev, NULL);
+ if (IS_ERR_OR_NULL(pxa->clk)) {
+ ret = PTR_ERR(pxa->clk);
+ if (!ret)
+ ret = -EINVAL;
+ goto err_clk_get;
+ }
+ pxa->clk_enabled = 0;
+
+ spin_lock_init(&pxa->lock);
+
+ pxa->pwm.dev = &pdev->dev;
+ pxa->pwm.bus_id = dev_name(&pdev->dev);
+ pxa->pwm.owner = THIS_MODULE;
+ pxa->pwm.request = pxa_pwm_request;
+ pxa->pwm.config_nosleep = pxa_pwm_config_nosleep;
+ pxa->pwm.config = pxa_pwm_config;
+
+ if (id->driver_data & HAS_SECONDARY_PWM)
+ pxa->pwm.nchan = 2;
+ else
+ pxa->pwm.nchan = 1;
+
+ ret = pwm_register(&pxa->pwm);
+
+ if (ret)
+ goto err_pwm_register;
+
+ platform_set_drvdata(pdev, pxa);
+ return 0;
+
+err_pwm_register:
+ clk_put(pxa->clk);
+err_clk_get:
+ iounmap(pxa->mmio_base);
+err_ioremap:
+ kfree(pxa);
+err_kzalloc:
+ release_mem_region(r->start, resource_size(r));
+ return ret;
+}
+
+static int __exit pxa_pwm_remove(struct platform_device *pdev)
+{
+ struct pxa_pwm *pxa = platform_get_drvdata(pdev);
+
+ if (IS_ERR_OR_NULL(pxa))
+ return -ENODEV;
+
+ pwm_unregister(&pxa->pwm);
+ clk_put(pxa->clk);
+ iounmap(pxa->mmio_base);
+ kfree(pxa);
+ platform_set_drvdata(pdev, NULL);
+ return 0;
+}
+
+static struct platform_driver pwm_driver = {
+ .driver = {
+ .name = "pxa25x-pwm",
+ .owner = THIS_MODULE,
+ },
+ .probe = pxa_pwm_probe,
+ .remove = pxa_pwm_remove,
+ .id_table = pxa_pwm_id_table,
+};
+
+static int __init pxa_pwm_init(void)
+{
+ return platform_driver_register(&pwm_driver);
+}
+/* TODO: do we have to do this at arch_initcall? */
+module_init(pxa_pwm_init);
+
+static void __exit pxa_pwm_exit(void)
+{
+ platform_driver_unregister(&pwm_driver);
+}
+module_exit(pxa_pwm_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Bill Gatliff <bgat@billgatliff.com>");
+MODULE_AUTHOR("Eric Miao (eric.miao@marvell.com>");
+MODULE_DESCRIPTION("Platform and PWM API drivers for PXA PWM peripheral");
--
1.7.1
^ permalink raw reply related [flat|nested] 33+ messages in thread
* [PWM 09/10] Build pwm.o only if CONFIG_GENERIC_PWM is set
2010-10-01 15:17 [PWM 01/10] API to consolidate PWM devices behind a common user and kernel interface Bill Gatliff
` (7 preceding siblings ...)
2010-10-01 15:17 ` Bill Gatliff
@ 2010-10-01 15:17 ` Bill Gatliff
2010-10-01 15:17 ` [PWM 10/10] Expunge previous driver for PXA PWM Bill Gatliff
` (4 subsequent siblings)
13 siblings, 0 replies; 33+ messages in thread
From: Bill Gatliff @ 2010-10-01 15:17 UTC (permalink / raw)
To: linux-embedded, linux-embedded; +Cc: Bill Gatliff
Signed-off-by: Bill Gatliff <bgat@billgatliff.com>
---
drivers/pwm/Makefile | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
index e8cacc5..5f29752 100644
--- a/drivers/pwm/Makefile
+++ b/drivers/pwm/Makefile
@@ -1,6 +1,6 @@
#
# Makefile for pwm devices
#
-obj-y := pwm.o
+obj-$(CONFIG_GENERIC_PWM) += pwm.o
obj-$(CONFIG_ATMEL_PWM) += atmel-pwm.o
obj-$(CONFIG_GPIO_PWM) += gpio.o
--
1.7.1
^ permalink raw reply related [flat|nested] 33+ messages in thread
* [PWM 10/10] Expunge previous driver for PXA PWM
2010-10-01 15:17 [PWM 01/10] API to consolidate PWM devices behind a common user and kernel interface Bill Gatliff
` (8 preceding siblings ...)
2010-10-01 15:17 ` [PWM 09/10] Build pwm.o only if CONFIG_GENERIC_PWM is set Bill Gatliff
@ 2010-10-01 15:17 ` Bill Gatliff
2010-10-01 22:00 ` [PWM 01/10] API to consolidate PWM devices behind a common user and kernel interface Kevin Hilman
` (3 subsequent siblings)
13 siblings, 0 replies; 33+ messages in thread
From: Bill Gatliff @ 2010-10-01 15:17 UTC (permalink / raw)
To: linux-embedded, linux-embedded; +Cc: Bill Gatliff
Signed-off-by: Bill Gatliff <bgat@billgatliff.com>
---
arch/arm/mach-pxa/Kconfig | 26 ++--
arch/arm/plat-pxa/Makefile | 1 -
arch/arm/plat-pxa/pwm.c | 304 --------------------------------------------
drivers/pwm/Kconfig | 8 +
drivers/pwm/Makefile | 1 +
5 files changed, 22 insertions(+), 318 deletions(-)
delete mode 100644 arch/arm/plat-pxa/pwm.c
diff --git a/arch/arm/mach-pxa/Kconfig b/arch/arm/mach-pxa/Kconfig
index 7aefb90..74a55fd 100644
--- a/arch/arm/mach-pxa/Kconfig
+++ b/arch/arm/mach-pxa/Kconfig
@@ -13,13 +13,13 @@ config ARCH_LUBBOCK
config MACH_MAINSTONE
bool "Intel HCDDBBVA0 Development Platform (aka Mainstone)"
select PXA27x
- select HAVE_PWM
+ select GENERIC_PWM
select PXA_HAVE_BOARD_IRQS
config MACH_ZYLONITE
bool
select PXA3xx
- select HAVE_PWM
+ select GENERIC_PWM
select PXA_HAVE_BOARD_IRQS
config MACH_ZYLONITE300
@@ -60,7 +60,7 @@ config ARCH_VIPER
select PXA25x
select ISA
select I2C_GPIO
- select HAVE_PWM
+ select GENERIC_PWM
select PXA_HAVE_BOARD_IRQS
select PXA_HAVE_ISA_IRQS
select ARCOM_PCMCIA
@@ -110,7 +110,7 @@ config MACH_CM_X300
select PXA3xx
select CPU_PXA300
select CPU_PXA310
- select HAVE_PWM
+ select GENERIC_PWM
config MACH_CAPC7117
bool "Embedian CAPC-7117 evaluation kit based on the MXM-8x10 CoM"
@@ -205,7 +205,7 @@ config TRIZEPS_PCMCIA
config MACH_LOGICPD_PXA270
bool "LogicPD PXA270 Card Engine Development Platform"
select PXA27x
- select HAVE_PWM
+ select GENERIC_PWM
select PXA_HAVE_BOARD_IRQS
config MACH_PCM027
@@ -216,7 +216,7 @@ config MACH_PCM027
config MACH_PCM990_BASEBOARD
bool "PHYTEC PCM-990 development board"
- select HAVE_PWM
+ select GENERIC_PWM
depends on MACH_PCM027
choice
@@ -273,7 +273,7 @@ config MACH_H4700
bool "HP iPAQ hx4700"
select PXA27x
select IWMMXT
- select HAVE_PWM
+ select GENERIC_PWM
select PXA_HAVE_BOARD_IRQS
config MACH_H5000
@@ -288,14 +288,14 @@ config MACH_MAGICIAN
bool "Enable HTC Magician Support"
select PXA27x
select IWMMXT
- select HAVE_PWM
+ select GENERIC_PWM
select PXA_HAVE_BOARD_IRQS
config MACH_MIOA701
bool "Mitac Mio A701 Support"
select PXA27x
select IWMMXT
- select HAVE_PWM
+ select GENERIC_PWM
select GPIO_SYSFS
help
Say Y here if you intend to run this kernel on a
@@ -306,7 +306,7 @@ config PXA_EZX
bool "Motorola EZX Platform"
select PXA27x
select IWMMXT
- select HAVE_PWM
+ select GENERIC_PWM
select PXA_HAVE_BOARD_IRQS
config MACH_EZX_A780
@@ -345,7 +345,7 @@ config MACH_MP900C
config ARCH_PXA_PALM
bool "PXA based Palm PDAs"
- select HAVE_PWM
+ select GENERIC_PWM
config MACH_PALM27X
bool
@@ -442,7 +442,7 @@ config MACH_RAUMFELD_RC
bool "Raumfeld Controller"
select PXA3xx
select CPU_PXA300
- select HAVE_PWM
+ select GENERIC_PWM
config MACH_RAUMFELD_CONNECTOR
bool "Raumfeld Connector"
@@ -605,7 +605,7 @@ config MACH_E800
config MACH_ZIPIT2
bool "Zipit Z2 Handheld"
select PXA27x
- select HAVE_PWM
+ select GENERIC_PWM
select PXA_HAVE_BOARD_IRQS
endmenu
diff --git a/arch/arm/plat-pxa/Makefile b/arch/arm/plat-pxa/Makefile
index 4aacdd1..35ced88 100644
--- a/arch/arm/plat-pxa/Makefile
+++ b/arch/arm/plat-pxa/Makefile
@@ -8,5 +8,4 @@ obj-$(CONFIG_GENERIC_GPIO) += gpio.o
obj-$(CONFIG_PXA3xx) += mfp.o
obj-$(CONFIG_ARCH_MMP) += mfp.o
-obj-$(CONFIG_HAVE_PWM) += pwm.o
obj-$(CONFIG_PXA_SSP) += ssp.o
diff --git a/arch/arm/plat-pxa/pwm.c b/arch/arm/plat-pxa/pwm.c
deleted file mode 100644
index ef32686..0000000
--- a/arch/arm/plat-pxa/pwm.c
+++ /dev/null
@@ -1,304 +0,0 @@
-/*
- * linux/arch/arm/mach-pxa/pwm.c
- *
- * simple driver for PWM (Pulse Width Modulator) controller
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- *
- * 2008-02-13 initial version
- * eric miao <eric.miao@marvell.com>
- */
-
-#include <linux/module.h>
-#include <linux/kernel.h>
-#include <linux/platform_device.h>
-#include <linux/slab.h>
-#include <linux/err.h>
-#include <linux/clk.h>
-#include <linux/io.h>
-#include <linux/pwm.h>
-
-#include <asm/div64.h>
-
-#define HAS_SECONDARY_PWM 0x10
-#define PWM_ID_BASE(d) ((d) & 0xf)
-
-static const struct platform_device_id pwm_id_table[] = {
- /* PWM has_secondary_pwm? */
- { "pxa25x-pwm", 0 },
- { "pxa27x-pwm", 0 | HAS_SECONDARY_PWM },
- { "pxa168-pwm", 1 },
- { "pxa910-pwm", 1 },
- { },
-};
-MODULE_DEVICE_TABLE(platform, pwm_id_table);
-
-/* PWM registers and bits definitions */
-#define PWMCR (0x00)
-#define PWMDCR (0x04)
-#define PWMPCR (0x08)
-
-#define PWMCR_SD (1 << 6)
-#define PWMDCR_FD (1 << 10)
-
-struct pwm_device {
- struct list_head node;
- struct pwm_device *secondary;
- struct platform_device *pdev;
-
- const char *label;
- struct clk *clk;
- int clk_enabled;
- void __iomem *mmio_base;
-
- unsigned int use_count;
- unsigned int pwm_id;
-};
-
-/*
- * period_ns = 10^9 * (PRESCALE + 1) * (PV + 1) / PWM_CLK_RATE
- * duty_ns = 10^9 * (PRESCALE + 1) * DC / PWM_CLK_RATE
- */
-int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns)
-{
- unsigned long long c;
- unsigned long period_cycles, prescale, pv, dc;
-
- if (pwm == NULL || period_ns == 0 || duty_ns > period_ns)
- return -EINVAL;
-
- c = clk_get_rate(pwm->clk);
- c = c * period_ns;
- do_div(c, 1000000000);
- period_cycles = c;
-
- if (period_cycles < 1)
- period_cycles = 1;
- prescale = (period_cycles - 1) / 1024;
- pv = period_cycles / (prescale + 1) - 1;
-
- if (prescale > 63)
- return -EINVAL;
-
- if (duty_ns == period_ns)
- dc = PWMDCR_FD;
- else
- dc = (pv + 1) * duty_ns / period_ns;
-
- /* NOTE: the clock to PWM has to be enabled first
- * before writing to the registers
- */
- clk_enable(pwm->clk);
- __raw_writel(prescale, pwm->mmio_base + PWMCR);
- __raw_writel(dc, pwm->mmio_base + PWMDCR);
- __raw_writel(pv, pwm->mmio_base + PWMPCR);
- clk_disable(pwm->clk);
-
- return 0;
-}
-EXPORT_SYMBOL(pwm_config);
-
-int pwm_enable(struct pwm_device *pwm)
-{
- int rc = 0;
-
- if (!pwm->clk_enabled) {
- rc = clk_enable(pwm->clk);
- if (!rc)
- pwm->clk_enabled = 1;
- }
- return rc;
-}
-EXPORT_SYMBOL(pwm_enable);
-
-void pwm_disable(struct pwm_device *pwm)
-{
- if (pwm->clk_enabled) {
- clk_disable(pwm->clk);
- pwm->clk_enabled = 0;
- }
-}
-EXPORT_SYMBOL(pwm_disable);
-
-static DEFINE_MUTEX(pwm_lock);
-static LIST_HEAD(pwm_list);
-
-struct pwm_device *pwm_request(int pwm_id, const char *label)
-{
- struct pwm_device *pwm;
- int found = 0;
-
- mutex_lock(&pwm_lock);
-
- list_for_each_entry(pwm, &pwm_list, node) {
- if (pwm->pwm_id == pwm_id) {
- found = 1;
- break;
- }
- }
-
- if (found) {
- if (pwm->use_count == 0) {
- pwm->use_count++;
- pwm->label = label;
- } else
- pwm = ERR_PTR(-EBUSY);
- } else
- pwm = ERR_PTR(-ENOENT);
-
- mutex_unlock(&pwm_lock);
- return pwm;
-}
-EXPORT_SYMBOL(pwm_request);
-
-void pwm_free(struct pwm_device *pwm)
-{
- mutex_lock(&pwm_lock);
-
- if (pwm->use_count) {
- pwm->use_count--;
- pwm->label = NULL;
- } else
- pr_warning("PWM device already freed\n");
-
- mutex_unlock(&pwm_lock);
-}
-EXPORT_SYMBOL(pwm_free);
-
-static inline void __add_pwm(struct pwm_device *pwm)
-{
- mutex_lock(&pwm_lock);
- list_add_tail(&pwm->node, &pwm_list);
- mutex_unlock(&pwm_lock);
-}
-
-static int __devinit pwm_probe(struct platform_device *pdev)
-{
- const struct platform_device_id *id = platform_get_device_id(pdev);
- struct pwm_device *pwm, *secondary = NULL;
- struct resource *r;
- int ret = 0;
-
- pwm = kzalloc(sizeof(struct pwm_device), GFP_KERNEL);
- if (pwm == NULL) {
- dev_err(&pdev->dev, "failed to allocate memory\n");
- return -ENOMEM;
- }
-
- pwm->clk = clk_get(&pdev->dev, NULL);
- if (IS_ERR(pwm->clk)) {
- ret = PTR_ERR(pwm->clk);
- goto err_free;
- }
- pwm->clk_enabled = 0;
-
- pwm->use_count = 0;
- pwm->pwm_id = PWM_ID_BASE(id->driver_data) + pdev->id;
- pwm->pdev = pdev;
-
- r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- if (r == NULL) {
- dev_err(&pdev->dev, "no memory resource defined\n");
- ret = -ENODEV;
- goto err_free_clk;
- }
-
- r = request_mem_region(r->start, resource_size(r), pdev->name);
- if (r == NULL) {
- dev_err(&pdev->dev, "failed to request memory resource\n");
- ret = -EBUSY;
- goto err_free_clk;
- }
-
- pwm->mmio_base = ioremap(r->start, resource_size(r));
- if (pwm->mmio_base == NULL) {
- dev_err(&pdev->dev, "failed to ioremap() registers\n");
- ret = -ENODEV;
- goto err_free_mem;
- }
-
- if (id->driver_data & HAS_SECONDARY_PWM) {
- secondary = kzalloc(sizeof(struct pwm_device), GFP_KERNEL);
- if (secondary == NULL) {
- ret = -ENOMEM;
- goto err_free_mem;
- }
-
- *secondary = *pwm;
- pwm->secondary = secondary;
-
- /* registers for the second PWM has offset of 0x10 */
- secondary->mmio_base = pwm->mmio_base + 0x10;
- secondary->pwm_id = pdev->id + 2;
- }
-
- __add_pwm(pwm);
- if (secondary)
- __add_pwm(secondary);
-
- platform_set_drvdata(pdev, pwm);
- return 0;
-
-err_free_mem:
- release_mem_region(r->start, resource_size(r));
-err_free_clk:
- clk_put(pwm->clk);
-err_free:
- kfree(pwm);
- return ret;
-}
-
-static int __devexit pwm_remove(struct platform_device *pdev)
-{
- struct pwm_device *pwm;
- struct resource *r;
-
- pwm = platform_get_drvdata(pdev);
- if (pwm == NULL)
- return -ENODEV;
-
- mutex_lock(&pwm_lock);
-
- if (pwm->secondary) {
- list_del(&pwm->secondary->node);
- kfree(pwm->secondary);
- }
-
- list_del(&pwm->node);
- mutex_unlock(&pwm_lock);
-
- iounmap(pwm->mmio_base);
-
- r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- release_mem_region(r->start, resource_size(r));
-
- clk_put(pwm->clk);
- kfree(pwm);
- return 0;
-}
-
-static struct platform_driver pwm_driver = {
- .driver = {
- .name = "pxa25x-pwm",
- .owner = THIS_MODULE,
- },
- .probe = pwm_probe,
- .remove = __devexit_p(pwm_remove),
- .id_table = pwm_id_table,
-};
-
-static int __init pwm_init(void)
-{
- return platform_driver_register(&pwm_driver);
-}
-arch_initcall(pwm_init);
-
-static void __exit pwm_exit(void)
-{
- platform_driver_unregister(&pwm_driver);
-}
-module_exit(pwm_exit);
-
-MODULE_LICENSE("GPL v2");
diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
index 0584c25..def003b 100644
--- a/drivers/pwm/Kconfig
+++ b/drivers/pwm/Kconfig
@@ -19,6 +19,14 @@ config ATMEL_PWM
peripheral channels found on certain Atmel processors.
If unsure, say N.
+config PXA_PWM
+ tristate "PXA2xx PWM support"
+ depends on PXA2xx || PXA3xx
+ help
+ This option enables device driver support for the PWM
+ peripheral device found on certain PXA2xx/PXA3xx
+ microcontrollers. If unsure, say N.
+
config GPIO_PWM
tristate "PWM emulation using GPIO"
help
diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
index 5f29752..03ae2cd 100644
--- a/drivers/pwm/Makefile
+++ b/drivers/pwm/Makefile
@@ -3,4 +3,5 @@
#
obj-$(CONFIG_GENERIC_PWM) += pwm.o
obj-$(CONFIG_ATMEL_PWM) += atmel-pwm.o
+obj-$(CONFIG_PXA_PWM) += pxa-pwm.o
obj-$(CONFIG_GPIO_PWM) += gpio.o
--
1.7.1
^ permalink raw reply related [flat|nested] 33+ messages in thread
* Re: [PWM 01/10] API to consolidate PWM devices behind a common user and kernel interface
2010-10-01 15:17 [PWM 01/10] API to consolidate PWM devices behind a common user and kernel interface Bill Gatliff
` (9 preceding siblings ...)
2010-10-01 15:17 ` [PWM 10/10] Expunge previous driver for PXA PWM Bill Gatliff
@ 2010-10-01 22:00 ` Kevin Hilman
2010-10-02 5:13 ` Jason Kridner
2010-10-06 18:45 ` Bill Gatliff
2010-10-02 12:25 ` Hector Oron
` (2 subsequent siblings)
13 siblings, 2 replies; 33+ messages in thread
From: Kevin Hilman @ 2010-10-01 22:00 UTC (permalink / raw)
To: Bill Gatliff; +Cc: linux-embedded
Bill Gatliff <bgat@billgatliff.com> writes:
> Signed-off-by: Bill Gatliff <bgat@billgatliff.com>
Hi Bill,
Any plans to post this to a broader audience? maybe linux-arm-kernel?
On davinci, I recently received a patch[1] for a davinci PWM layer which
allows the mulitple available PWM devices on davinci to make themselves
available by using the existing PWM API. It just added a pwm_add() and
pwm_remove() function to the existing API to do this. Not being
terribly familiar with all the PWM implementations out there, I was
suprised to see that something like this didn't exist.
I will ask the author of the davinci PWM code to have a look at your
stuff as well.
Thanks,
Kevin
[1] http://linux.davincidsp.com/pipermail/davinci-linux-open-source/2010-September/020338.html
> ---
> Documentation/pwm.txt | 260 +++++++++++++++++++
> drivers/pwm/pwm.c | 635 +++++++++++++++++++++++++++++++++++++++++++++++
> include/linux/pwm.h | 31 ---
> include/linux/pwm/pwm.h | 128 ++++++++++
> 4 files changed, 1023 insertions(+), 31 deletions(-)
> create mode 100644 Documentation/pwm.txt
> create mode 100644 drivers/pwm/pwm.c
> delete mode 100644 include/linux/pwm.h
> create mode 100644 include/linux/pwm/pwm.h
>
> diff --git a/Documentation/pwm.txt b/Documentation/pwm.txt
> new file mode 100644
> index 0000000..34b1e5a
> --- /dev/null
> +++ b/Documentation/pwm.txt
> @@ -0,0 +1,260 @@
> + Generic PWM Device API
> +
> + August 5, 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_release() 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_release() -- 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/drivers/pwm/pwm.c b/drivers/pwm/pwm.c
> new file mode 100644
> index 0000000..2774116
> --- /dev/null
> +++ b/drivers/pwm/pwm.c
> @@ -0,0 +1,635 @@
> +/*
> + * 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/module.h>
> +#include <linux/kernel.h>
> +#include <linux/init.h>
> +#include <linux/slab.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/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_release(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->release)
> + p->pwm->release(p);
> + module_put(p->pwm->owner);
> +done:
> + mutex_unlock(&device_list_mutex);
> +}
> +EXPORT_SYMBOL(pwm_release);
> +
> +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:
> + 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);
> +}
> +
> +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);
> +}
> +
> +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_release(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? */
> + 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/include/linux/pwm.h b/include/linux/pwm.h
> deleted file mode 100644
> index 7c77575..0000000
> --- a/include/linux/pwm.h
> +++ /dev/null
> @@ -1,31 +0,0 @@
> -#ifndef __LINUX_PWM_H
> -#define __LINUX_PWM_H
> -
> -struct pwm_device;
> -
> -/*
> - * pwm_request - request a PWM device
> - */
> -struct pwm_device *pwm_request(int pwm_id, const char *label);
> -
> -/*
> - * pwm_free - free a PWM device
> - */
> -void pwm_free(struct pwm_device *pwm);
> -
> -/*
> - * pwm_config - change a PWM device configuration
> - */
> -int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns);
> -
> -/*
> - * pwm_enable - start a PWM output toggling
> - */
> -int pwm_enable(struct pwm_device *pwm);
> -
> -/*
> - * pwm_disable - stop a PWM output toggling
> - */
> -void pwm_disable(struct pwm_device *pwm);
> -
> -#endif /* __LINUX_PWM_H */
> diff --git a/include/linux/pwm/pwm.h b/include/linux/pwm/pwm.h
> new file mode 100644
> index 0000000..a10824c
> --- /dev/null
> +++ b/include/linux/pwm/pwm.h
> @@ -0,0 +1,128 @@
> +/*
> + * include/linux/pwm.h
> + *
> + * 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
> + */
> +#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 (*release) (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_release(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 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 */
^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PWM 01/10] API to consolidate PWM devices behind a common user and kernel interface
2010-10-01 22:00 ` [PWM 01/10] API to consolidate PWM devices behind a common user and kernel interface Kevin Hilman
@ 2010-10-02 5:13 ` Jason Kridner
2010-10-06 18:45 ` Bill Gatliff
1 sibling, 0 replies; 33+ messages in thread
From: Jason Kridner @ 2010-10-02 5:13 UTC (permalink / raw)
To: Kevin Hilman; +Cc: Bill Gatliff, linux-embedded, Varun Jewalikar, scott
On Fri, Oct 1, 2010 at 5:00 PM, Kevin Hilman
<khilman@deeprootsystems.com> wrote:
> Bill Gatliff <bgat@billgatliff.com> writes:
>
>> Signed-off-by: Bill Gatliff <bgat@billgatliff.com>
>
> Hi Bill,
>
> Any plans to post this to a broader audience? maybe linux-arm-kernel?
>
> On davinci, I recently received a patch[1] for a davinci PWM layer which
> allows the mulitple available PWM devices on davinci to make themselves
> available by using the existing PWM API. It just added a pwm_add() and
> pwm_remove() function to the existing API to do this. Not being
> terribly familiar with all the PWM implementations out there, I was
> suprised to see that something like this didn't exist.
>
> I will ask the author of the davinci PWM code to have a look at your
> stuff as well.
Also, Varun and Scott also have submitted PWM patches for OMAP [2] and
I hope they will look at this patch set and comment. If I get a
chance, and I never do, I will try these myself. I hope that baseline
PWM support is accepted soon.
[2] http://www.mail-archive.com/linux-embedded@vger.kernel.org/msg02851.html
>
> Thanks,
>
> Kevin
>
> [1] http://linux.davincidsp.com/pipermail/davinci-linux-open-source/2010-September/020338.html
>
>> ---
>> Documentation/pwm.txt | 260 +++++++++++++++++++
>> drivers/pwm/pwm.c | 635 +++++++++++++++++++++++++++++++++++++++++++++++
>> include/linux/pwm.h | 31 ---
>> include/linux/pwm/pwm.h | 128 ++++++++++
>> 4 files changed, 1023 insertions(+), 31 deletions(-)
>> create mode 100644 Documentation/pwm.txt
>> create mode 100644 drivers/pwm/pwm.c
>> delete mode 100644 include/linux/pwm.h
>> create mode 100644 include/linux/pwm/pwm.h
>>
>> diff --git a/Documentation/pwm.txt b/Documentation/pwm.txt
>> new file mode 100644
>> index 0000000..34b1e5a
>> --- /dev/null
>> +++ b/Documentation/pwm.txt
>> @@ -0,0 +1,260 @@
>> + Generic PWM Device API
>> +
>> + August 5, 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_release() 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_release() -- 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/drivers/pwm/pwm.c b/drivers/pwm/pwm.c
>> new file mode 100644
>> index 0000000..2774116
>> --- /dev/null
>> +++ b/drivers/pwm/pwm.c
>> @@ -0,0 +1,635 @@
>> +/*
>> + * 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/module.h>
>> +#include <linux/kernel.h>
>> +#include <linux/init.h>
>> +#include <linux/slab.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/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_release(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->release)
>> + p->pwm->release(p);
>> + module_put(p->pwm->owner);
>> +done:
>> + mutex_unlock(&device_list_mutex);
>> +}
>> +EXPORT_SYMBOL(pwm_release);
>> +
>> +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:
>> + 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);
>> +}
>> +
>> +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);
>> +}
>> +
>> +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_release(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? */
>> + 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/include/linux/pwm.h b/include/linux/pwm.h
>> deleted file mode 100644
>> index 7c77575..0000000
>> --- a/include/linux/pwm.h
>> +++ /dev/null
>> @@ -1,31 +0,0 @@
>> -#ifndef __LINUX_PWM_H
>> -#define __LINUX_PWM_H
>> -
>> -struct pwm_device;
>> -
>> -/*
>> - * pwm_request - request a PWM device
>> - */
>> -struct pwm_device *pwm_request(int pwm_id, const char *label);
>> -
>> -/*
>> - * pwm_free - free a PWM device
>> - */
>> -void pwm_free(struct pwm_device *pwm);
>> -
>> -/*
>> - * pwm_config - change a PWM device configuration
>> - */
>> -int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns);
>> -
>> -/*
>> - * pwm_enable - start a PWM output toggling
>> - */
>> -int pwm_enable(struct pwm_device *pwm);
>> -
>> -/*
>> - * pwm_disable - stop a PWM output toggling
>> - */
>> -void pwm_disable(struct pwm_device *pwm);
>> -
>> -#endif /* __LINUX_PWM_H */
>> diff --git a/include/linux/pwm/pwm.h b/include/linux/pwm/pwm.h
>> new file mode 100644
>> index 0000000..a10824c
>> --- /dev/null
>> +++ b/include/linux/pwm/pwm.h
>> @@ -0,0 +1,128 @@
>> +/*
>> + * include/linux/pwm.h
>> + *
>> + * 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
>> + */
>> +#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 (*release) (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_release(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 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 */
> --
> To unsubscribe from this list: send the line "unsubscribe linux-embedded" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html
>
^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PWM 01/10] API to consolidate PWM devices behind a common user and kernel interface
2010-10-01 15:17 [PWM 01/10] API to consolidate PWM devices behind a common user and kernel interface Bill Gatliff
` (10 preceding siblings ...)
2010-10-01 22:00 ` [PWM 01/10] API to consolidate PWM devices behind a common user and kernel interface Kevin Hilman
@ 2010-10-02 12:25 ` Hector Oron
2010-10-06 18:48 ` Bill Gatliff
2010-10-05 10:35 ` sugumar
2010-10-16 7:42 ` Grant Likely
13 siblings, 1 reply; 33+ messages in thread
From: Hector Oron @ 2010-10-02 12:25 UTC (permalink / raw)
To: Bill Gatliff; +Cc: linux-embedded
Hello Bill,
2010/10/1 Bill Gatliff <bgat@billgatliff.com>:
> Signed-off-by: Bill Gatliff <bgat@billgatliff.com>
> ---
> Documentation/pwm.txt | 260 +++++++++++++++++++
> drivers/pwm/pwm.c | 635 +++++++++++++++++++++++++++++++++++++++++++++++
> include/linux/pwm.h | 31 ---
> include/linux/pwm/pwm.h | 128 ++++++++++
> 4 files changed, 1023 insertions(+), 31 deletions(-)
> create mode 100644 Documentation/pwm.txt
> create mode 100644 drivers/pwm/pwm.c
> delete mode 100644 include/linux/pwm.h
> create mode 100644 include/linux/pwm/pwm.h
Thanks for trying (again) to get this in mainline.
Are you planning to request a slot in the GIT at kernel.org to hold
and maintain this nice framework?
Best regards,
-- Héctor Orón
^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PWM 01/10] API to consolidate PWM devices behind a common user and kernel interface
2010-10-01 15:17 [PWM 01/10] API to consolidate PWM devices behind a common user and kernel interface Bill Gatliff
` (11 preceding siblings ...)
2010-10-02 12:25 ` Hector Oron
@ 2010-10-05 10:35 ` sugumar
2010-10-06 18:50 ` Bill Gatliff
2010-10-16 7:42 ` Grant Likely
13 siblings, 1 reply; 33+ messages in thread
From: sugumar @ 2010-10-05 10:35 UTC (permalink / raw)
To: linux-embedded
Bill Gatliff <bgat <at> billgatliff.com> writes:
>
> Signed-off-by: Bill Gatliff <bgat <at> billgatliff.com>
> ---
> Documentation/pwm.txt | 260 +++++++++++++++++++
> drivers/pwm/pwm.c | 635 +++++++++++++++++++++++++++++++++++++++++++++++
> include/linux/pwm.h | 31 ---
> include/linux/pwm/pwm.h | 128 ++++++++++
> 4 files changed, 1023 insertions(+), 31 deletions(-)
> create mode 100644 Documentation/pwm.txt
> create mode 100644 drivers/pwm/pwm.c
> delete mode 100644 include/linux/pwm.h
> create mode 100644 include/linux/pwm/pwm.h
>
> diff --git a/Documentation/pwm.txt b/Documentation/pwm.txt
> new file mode 100644
> index 0000000..34b1e5a
> --- /dev/null
> +++ b/Documentation/pwm.txt
> @@ -0,0 +1,260 @@
> +
Hi bill,
I have been working on writing generic PWM driver that provides common
interface to all the PWM devices. I hadn't come across this thread until kevin
pointed out. It looks like a very good implementation and i would like to make
my eHRPWM driver to fit into this framework. eHRPWM (enhanced high resolution
pwm) module has other features such as Dead Band, PWM chopper and Trip Zone and
i am not sure how to incorporate these in the generic framework. I really need
you help here.
I am sure the generic framework should support all the features provided by
the PWM devices. For eg, eHRPWM has Trip-Zone feature where an asynchronous
input (incase of short circuit .) can be configured to generate an interrupt.
I am interested in finding a way to handle this. Kindly give your comments.
Regards,
N.Sugumar
^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PWM 01/10] API to consolidate PWM devices behind a common user and kernel interface
2010-10-01 22:00 ` [PWM 01/10] API to consolidate PWM devices behind a common user and kernel interface Kevin Hilman
2010-10-02 5:13 ` Jason Kridner
@ 2010-10-06 18:45 ` Bill Gatliff
2010-10-06 19:08 ` Kevin Hilman
1 sibling, 1 reply; 33+ messages in thread
From: Bill Gatliff @ 2010-10-06 18:45 UTC (permalink / raw)
To: Kevin Hilman; +Cc: linux-embedded
On Fri, Oct 1, 2010 at 5:00 PM, Kevin Hilman
<khilman@deeprootsystems.com> wrote:
> Bill Gatliff <bgat@billgatliff.com> writes:
>
>> Signed-off-by: Bill Gatliff <bgat@billgatliff.com>
>
> Hi Bill,
>
> Any plans to post this to a broader audience? maybe linux-arm-kernel?
I was thinking that the appropriate audience was all right here. :)
b.g.
--
Bill Gatliff
bgat@billgatliff.com
^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PWM 01/10] API to consolidate PWM devices behind a common user and kernel interface
2010-10-02 12:25 ` Hector Oron
@ 2010-10-06 18:48 ` Bill Gatliff
2010-10-16 6:05 ` Grant Likely
0 siblings, 1 reply; 33+ messages in thread
From: Bill Gatliff @ 2010-10-06 18:48 UTC (permalink / raw)
To: Hector Oron; +Cc: linux-embedded
Hector:
On Sat, Oct 2, 2010 at 7:25 AM, Hector Oron <hector.oron@gmail.com> wrote:
> Hello Bill,
>
> Thanks for trying (again) to get this in mainline.
> Are you planning to request a slot in the GIT at kernel.org to hold
> and maintain this nice framework?
I did make such a request once, and it went nowhere. :(
b.g.
--
Bill Gatliff
bgat@billgatliff.com
^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PWM 01/10] API to consolidate PWM devices behind a common user and kernel interface
2010-10-05 10:35 ` sugumar
@ 2010-10-06 18:50 ` Bill Gatliff
2010-10-06 19:02 ` Grosen, Mark
2010-10-07 7:58 ` Sugumar Natarajan
0 siblings, 2 replies; 33+ messages in thread
From: Bill Gatliff @ 2010-10-06 18:50 UTC (permalink / raw)
To: sugumar; +Cc: linux-embedded
On Tue, Oct 5, 2010 at 5:35 AM, sugumar <sugumar.embedded@gmail.com> wrote:
> I have been working on writing generic PWM driver that provides common
> interface to all the PWM devices. I hadn't come across this thread until kevin
> pointed out. It looks like a very good implementation and i would like to make
> my eHRPWM driver to fit into this framework. eHRPWM (enhanced high resolution
> pwm) module has other features such as Dead Band, PWM chopper and Trip Zone and
> i am not sure how to incorporate these in the generic framework. I really need
> you help here.
Ok. Can you point me to a datasheet for the part?
> I am sure the generic framework should support all the features provided by
> the PWM devices. For eg, eHRPWM has Trip-Zone feature where an asynchronous
> input (incase of short circuit .) can be configured to generate an interrupt.
> I am interested in finding a way to handle this. Kindly give your comments.
Well, we could handle it in the generic framework by adding to the
synchronization callback mechanism. Or we could leave it as a feature
unique to the eHRPWM device, and a device-specific solution.
Maybe I will have a better idea once I look at the hardware.
b.g.
--
Bill Gatliff
bgat@billgatliff.com
^ permalink raw reply [flat|nested] 33+ messages in thread
* RE: [PWM 01/10] API to consolidate PWM devices behind a common user and kernel interface
2010-10-06 18:50 ` Bill Gatliff
@ 2010-10-06 19:02 ` Grosen, Mark
2010-10-07 7:58 ` Sugumar Natarajan
1 sibling, 0 replies; 33+ messages in thread
From: Grosen, Mark @ 2010-10-06 19:02 UTC (permalink / raw)
To: Bill Gatliff, sugumar; +Cc: linux-embedded@vger.kernel.org
-----Original Message-----
From: linux-embedded-owner@vger.kernel.org [mailto:linux-embedded-owner@vger.kernel.org] On Behalf Of Bill Gatliff
Sent: Wednesday, October 06, 2010 11:50 AM
To: sugumar
Cc: linux-embedded@vger.kernel.org
Subject: Re: [PWM 01/10] API to consolidate PWM devices behind a common user and kernel interface
> On Tue, Oct 5, 2010 at 5:35 AM, sugumar <sugumar.embedded@gmail.com> wrote:
>> I have been working on writing generic PWM driver that provides
>> common interface to all the PWM devices. I hadn't come across this
>> thread until kevin pointed out. It looks like a very good
>> implementation and i would like to make my eHRPWM driver to fit into
>> this framework. eHRPWM (enhanced high resolution
>> pwm) module has other features such as Dead Band, PWM chopper and Trip
>> Zone and i am not sure how to incorporate these in the generic
>> framework. I really need you help here.
> Ok. Can you point me to a datasheet for the part?
PDF datasheet here: http://www.ti.com/litv/pdf/sprufl3b
Mark--
To unsubscribe from this list: send the line "unsubscribe linux-embedded" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PWM 01/10] API to consolidate PWM devices behind a common user and kernel interface
2010-10-06 18:45 ` Bill Gatliff
@ 2010-10-06 19:08 ` Kevin Hilman
0 siblings, 0 replies; 33+ messages in thread
From: Kevin Hilman @ 2010-10-06 19:08 UTC (permalink / raw)
To: Bill Gatliff; +Cc: linux-embedded
Bill Gatliff <bgat@billgatliff.com> writes:
> On Fri, Oct 1, 2010 at 5:00 PM, Kevin Hilman
> <khilman@deeprootsystems.com> wrote:
>> Bill Gatliff <bgat@billgatliff.com> writes:
>>
>>> Signed-off-by: Bill Gatliff <bgat@billgatliff.com>
>>
>> Hi Bill,
>>
>> Any plans to post this to a broader audience? maybe linux-arm-kernel?
>
> I was thinking that the appropriate audience was all right here. :)
Maybe so, in terms of maintainers and folks watching the bigger picture
of the kernel. However, the folks that seem to be developing similar
things in parallel are apparently not here as they seem to be doing
similar work in parallel.
Kevin
^ permalink raw reply [flat|nested] 33+ messages in thread
* RE: [PWM 01/10] API to consolidate PWM devices behind a common user and kernel interface
2010-10-06 18:50 ` Bill Gatliff
2010-10-06 19:02 ` Grosen, Mark
@ 2010-10-07 7:58 ` Sugumar Natarajan
1 sibling, 0 replies; 33+ messages in thread
From: Sugumar Natarajan @ 2010-10-07 7:58 UTC (permalink / raw)
To: 'Bill Gatliff', 'sugumar'; +Cc: linux-embedded
On Thu, Oct 07, 2010 at 00:20:25, Bill Gatliff wrote:
> On Tue, Oct 5, 2010 at 5:35 AM, sugumar <sugumar.embedded@gmail.com> wrote:
> > I have been working on writing generic PWM driver that provides
> > common interface to all the PWM devices. I hadn't come across this
> > thread until kevin pointed out. It looks like a very good
> > implementation and i would like to make my eHRPWM driver to fit into
> > this framework. eHRPWM (enhanced high resolution
> > pwm) module has other features such as Dead Band, PWM chopper and Trip
> > Zone and i am not sure how to incorporate these in the generic
> > framework. I really need you help here.
>
> Ok. Can you point me to a datasheet for the part?
>
> > I am sure the generic framework should support all the features
> > provided by the PWM devices. For eg, eHRPWM has Trip-Zone feature
> > where an asynchronous input (incase of short circuit .) can be configured to generate an interrupt.
> > I am interested in finding a way to handle this. Kindly give your comments.
>
> Well, we could handle it in the generic framework by adding to the synchronization callback mechanism. Or we could leave it as a
feature unique to the eHRPWM device, and a device-specific solution.
>
> Maybe I will have a better idea once I look at the hardware.
Bill,
Thanks for your comments. Also, I am not particularly clear about the pwm_set_handler function.
As far my understanding goes, in order to defer the work,
1. Set the "set_callback" variable in the pwm_device structure to the callback function.
2. Call the "pwm_set_handler" function to set the bottom halve handler and the data.
3. "pwm_set_handler" inturn invokes the call back function defined in step 1 .The latter
would set the PWM channel call back to "pwm_handler" function which is local to the PWM
framework.
4.Call the pwm_channel callback in the interrupt handler to queue the work for later processing.
1. Is my above understanding correct?
2. Single channel may support multiple interrupts.
For eg.,
pwm_set_handler(handler_1)
pwm_set_handler(handler_2)
How to handle multiple interrupts with the above framework?
3. pwm_set_handler() -- Defines an end-of-period callback.
Is the pwm_set_handler only used for the end-of-period interrupt.
Can't it be used for other events ?
4. Various applications may require different actions to be handled in the interrupt
handler (Eg. Motor Control, Backlight control ..). so, we cannot have a generic
interrupt handler in the pwm device driver. How to handle this?
Regards,
N.Sugumar.
> b.g.
> --
> Bill Gatliff
> bgat@billgatliff.com
> --
> To unsubscribe from this list: send the line "unsubscribe linux-embedded" in the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
>
^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PWM 01/10] API to consolidate PWM devices behind a common user and kernel interface
2010-10-06 18:48 ` Bill Gatliff
@ 2010-10-16 6:05 ` Grant Likely
0 siblings, 0 replies; 33+ messages in thread
From: Grant Likely @ 2010-10-16 6:05 UTC (permalink / raw)
To: Bill Gatliff; +Cc: Hector Oron, linux-embedded
On Wed, Oct 6, 2010 at 12:48 PM, Bill Gatliff <bgat@billgatliff.com> wrote:
> Hector:
>
> On Sat, Oct 2, 2010 at 7:25 AM, Hector Oron <hector.oron@gmail.com> wrote:
>> Hello Bill,
>>
>> Thanks for trying (again) to get this in mainline.
>> Are you planning to request a slot in the GIT at kernel.org to hold
>> and maintain this nice framework?
>
> I did make such a request once, and it went nowhere. :(
Git trees don't have to be hosted on git.kernel.org. None of my trees
are on kernel.org, and Linus pulls from me all the time. You can host
your own git server, or use a git hosting provider.
But, if you're serious about getting it mainlined, then you need to
post the whole series to linux-kernel. Also, without a specific
maintainer to pick it up for you, it helps to cc: Andrew Morton. I
recommend doing so sooner rather than later. It probably won't make
2.6.37 since the merge window is opening in about a week, but it would
be a good candidate for .38. I'll go through all the patches right
now and make my comments.
g.
^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PWM 02/10] Emulates PWM hardware using a high-resolution timer and a GPIO pin
2010-10-01 15:17 ` [PWM 02/10] Emulates PWM hardware using a high-resolution timer and a GPIO pin Bill Gatliff
@ 2010-10-16 6:54 ` Grant Likely
0 siblings, 0 replies; 33+ messages in thread
From: Grant Likely @ 2010-10-16 6:54 UTC (permalink / raw)
To: Bill Gatliff; +Cc: linux-embedded
On Fri, Oct 01, 2010 at 10:17:43AM -0500, Bill Gatliff wrote:
> Signed-off-by: Bill Gatliff <bgat@billgatliff.com>
> ---
Code looks pretty clean. Minor comments below, plus a rehash of an
old argument.
> drivers/pwm/gpio.c | 298 ++++++++++++++++++++++++++++++++++++++++++++++++++++
> 1 files changed, 298 insertions(+), 0 deletions(-)
> create mode 100644 drivers/pwm/gpio.c
>
> diff --git a/drivers/pwm/gpio.c b/drivers/pwm/gpio.c
> new file mode 100644
> index 0000000..f4aab06
> --- /dev/null
> +++ b/drivers/pwm/gpio.c
> @@ -0,0 +1,298 @@
> +/*
> + * drivers/pwm/gpio.c
> + *
> + * Models a single-channel PWM device using a timer and a GPIO pin.
> + *
> + * Copyright (C) 2010 Bill Gatliff <bgat@billgatliff.com>
> + *
> + * This program is free software; you may redistribute and/or modify
> + * it under the terms of the GNU General Public License Version 2, as
> + * published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful, but
> + * WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + * General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, write to the Free Software
> + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
> + * USA
> + */
> +
> +#include <linux/module.h>
> +#include <linux/init.h>
> +#include <linux/slab.h>
> +#include <linux/hrtimer.h>
> +#include <linux/err.h>
> +#include <linux/platform_device.h>
> +#include <linux/workqueue.h>
> +#include <linux/gpio.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)
By breaking the line here; 'grep gpio_pwm_work' is a lot less useful
because it doesn't tell you the return type (a very common thing to do
when figuring out how an API works). Only split lines if you really
need to (ie, longer than 80 chars), and if you do need a line break,
then break in the parameters list.
> +{
> + 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);
> +}
Bitwise operations are probably faster than two conditionals.
How about:
gpio_direction_output(gp->gpio, !(gp->polarity ^ gp->active));
> +
> +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;
This function references gp->pwm.channels[0] a lot. You might want to
load the pointer into a local variable in the interest of readability.
> + 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 */
(digging up an old argument, I know)
I still think that the PWM api would do well to piggyback on the
namespace and support code of gpiolib. Some pins would support gpio
functions, some would support pwm, some would support both, but it
makes sense to share the overall pin management code. (I'm not
talking about pin muxing; that is a separate problem)
In that scenario, the code in this file becomes library available to
be used by regular gpios to implement pwm behaviour.
> +
> + 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");
> --
> 1.7.1
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-embedded" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PWM 01/10] API to consolidate PWM devices behind a common user and kernel interface
2010-10-01 15:17 [PWM 01/10] API to consolidate PWM devices behind a common user and kernel interface Bill Gatliff
` (12 preceding siblings ...)
2010-10-05 10:35 ` sugumar
@ 2010-10-16 7:42 ` Grant Likely
2010-10-20 18:13 ` Bill Gatliff
13 siblings, 1 reply; 33+ messages in thread
From: Grant Likely @ 2010-10-16 7:42 UTC (permalink / raw)
To: Bill Gatliff; +Cc: linux-embedded
On Fri, Oct 01, 2010 at 10:17:42AM -0500, Bill Gatliff wrote:
> Signed-off-by: Bill Gatliff <bgat@billgatliff.com>
Hi Bill, comments below.
> ---
> Documentation/pwm.txt | 260 +++++++++++++++++++
> drivers/pwm/pwm.c | 635 +++++++++++++++++++++++++++++++++++++++++++++++
> include/linux/pwm.h | 31 ---
> include/linux/pwm/pwm.h | 128 ++++++++++
> 4 files changed, 1023 insertions(+), 31 deletions(-)
> create mode 100644 Documentation/pwm.txt
> create mode 100644 drivers/pwm/pwm.c
> delete mode 100644 include/linux/pwm.h
> create mode 100644 include/linux/pwm/pwm.h
>
> diff --git a/Documentation/pwm.txt b/Documentation/pwm.txt
> new file mode 100644
> index 0000000..34b1e5a
> --- /dev/null
> +++ b/Documentation/pwm.txt
> @@ -0,0 +1,260 @@
> + Generic PWM Device API
> +
> + August 5, 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_release() 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.
I'm not hugely keen on the naming approach, and I'd rather see the
pwm core be responsible for the namespace. Something like "pwm-%i:%i" where the first number is a controller enumeration (dynamically assigned) and the second is the channel number. Matching a controller can be accomplished by looking
at the parent device. The name doesn't need to be encoded directly
into the pwm device for that to work.
However, it isn't a major issue that I have and I won't make a big
stink about it.
> +pwm_release() -- 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/drivers/pwm/pwm.c b/drivers/pwm/pwm.c
> new file mode 100644
> index 0000000..2774116
> --- /dev/null
> +++ b/drivers/pwm/pwm.c
> @@ -0,0 +1,635 @@
> +/*
> + * drivers/pwm/pwm.c
Nitpick: putting the filename into the file itself I find tends to be
useless
> + *
> + * 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/kernel.h>
> +#include <linux/init.h>
> +#include <linux/slab.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/pwm/pwm.h>
> +
> +static int __pwm_create_sysfs(struct pwm_device *pwm);
If you order the functions in this file correct, the forward
declaration should not be necessary.
> +
> +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 the pwm_channel structure carried a pointer to its device
structure, then this lookup wouldn't be needed.
> + 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_release(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->release)
> + p->pwm->release(p);
> + module_put(p->pwm->owner);
> +done:
> + mutex_unlock(&device_list_mutex);
> +}
> +EXPORT_SYMBOL(pwm_release);
> +
> +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:
> + 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);
The above 8 functions are so tiny that they should probably be static
inlines in the header file.
> +
> +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);
> +}
> +
> +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);
> +}
> +
> +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_release(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;
> + }
Rather than open coding the registration of sysfs files, I believe the
right thing to do is to use class attributes so that the files get
automatically registered for you and are immediately advertised to
userspace (very important for correct interaction with udev).
Take a look at struct class->dev_attrs. It is just a null terminated
list of attributes that need to be registered for each device that is
a member of the class. It also takes care of unwinding correctly if
the registration fails.
Also, sysfs_create_group() should not be called directly when working
with devices.
> +
> + 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? */
Same thing we always do for bootstrapping. If it *must* be early,
then we do a simple direct access to get things running in platform
code, and then take over in the driver when it finally gets probed.
The driver model helps with many things, but really early stuff isn't
one of them.
> + 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/include/linux/pwm.h b/include/linux/pwm.h
> deleted file mode 100644
> index 7c77575..0000000
> --- a/include/linux/pwm.h
> +++ /dev/null
> @@ -1,31 +0,0 @@
> -#ifndef __LINUX_PWM_H
> -#define __LINUX_PWM_H
> -
> -struct pwm_device;
> -
> -/*
> - * pwm_request - request a PWM device
> - */
> -struct pwm_device *pwm_request(int pwm_id, const char *label);
> -
> -/*
> - * pwm_free - free a PWM device
> - */
> -void pwm_free(struct pwm_device *pwm);
> -
> -/*
> - * pwm_config - change a PWM device configuration
> - */
> -int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns);
> -
> -/*
> - * pwm_enable - start a PWM output toggling
> - */
> -int pwm_enable(struct pwm_device *pwm);
> -
> -/*
> - * pwm_disable - stop a PWM output toggling
> - */
> -void pwm_disable(struct pwm_device *pwm);
> -
> -#endif /* __LINUX_PWM_H */
> diff --git a/include/linux/pwm/pwm.h b/include/linux/pwm/pwm.h
> new file mode 100644
> index 0000000..a10824c
> --- /dev/null
> +++ b/include/linux/pwm/pwm.h
> @@ -0,0 +1,128 @@
> +/*
> + * include/linux/pwm.h
> + *
> + * 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
> + */
> +#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 (*release) (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_release(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 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 */
> --
> 1.7.1
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-embedded" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PWM 03/10] Expunge old Atmel PWMC driver, replacing it with one that conforms to the PWM API
2010-10-01 15:17 ` [PWM 03/10] Expunge old Atmel PWMC driver, replacing it with one that conforms to the PWM API Bill Gatliff
@ 2010-10-16 7:50 ` Grant Likely
0 siblings, 0 replies; 33+ messages in thread
From: Grant Likely @ 2010-10-16 7:50 UTC (permalink / raw)
To: Bill Gatliff; +Cc: linux-embedded
On Fri, Oct 01, 2010 at 10:17:44AM -0500, Bill Gatliff wrote:
> Signed-off-by: Bill Gatliff <bgat@billgatliff.com>
This patch needs a better description about what is going on here. If
you're replacing an old driver, you must talk about what is changing
and why. You cannot assume that a future reader will have the context
of the full patch series.
Also, since this patch deletes existing code; does it break any users
of the current driver? Is it fully bisectable? What testing has been
done?
Finally, please shorten the patch title. It should be somewhere in
the neighbourhood of 70 characters or less so that it doesn't spill
out the end of reviewers inbox list.
> ---
> drivers/misc/Makefile | 1 -
> drivers/misc/atmel_pwm.c | 410 --------------------------------
> drivers/pwm/atmel-pwm.c | 592 ++++++++++++++++++++++++++++++++++++++++++++++
Since the old driver is being removed; shouldn't the atmel_pwm.h
header file be removed also?
> 3 files changed, 592 insertions(+), 411 deletions(-)
> delete mode 100644 drivers/misc/atmel_pwm.c
> create mode 100644 drivers/pwm/atmel-pwm.c
>
> diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
> index 42eab95..b5962a2 100644
> --- a/drivers/misc/Makefile
> +++ b/drivers/misc/Makefile
> @@ -6,7 +6,6 @@ obj-$(CONFIG_IBM_ASM) += ibmasm/
> obj-$(CONFIG_AD525X_DPOT) += ad525x_dpot.o
> obj-$(CONFIG_AD525X_DPOT_I2C) += ad525x_dpot-i2c.o
> obj-$(CONFIG_AD525X_DPOT_SPI) += ad525x_dpot-spi.o
> -obj-$(CONFIG_ATMEL_PWM) += atmel_pwm.o
> obj-$(CONFIG_ATMEL_SSC) += atmel-ssc.o
> obj-$(CONFIG_ATMEL_TCLIB) += atmel_tclib.o
> obj-$(CONFIG_BMP085) += bmp085.o
> diff --git a/drivers/misc/atmel_pwm.c b/drivers/misc/atmel_pwm.c
> deleted file mode 100644
> index 0f3fb4f..0000000
> --- a/drivers/misc/atmel_pwm.c
> +++ /dev/null
> @@ -1,410 +0,0 @@
> -#include <linux/module.h>
> -#include <linux/clk.h>
> -#include <linux/err.h>
> -#include <linux/slab.h>
> -#include <linux/io.h>
> -#include <linux/interrupt.h>
> -#include <linux/platform_device.h>
> -#include <linux/atmel_pwm.h>
> -
> -
> -/*
> - * This is a simple driver for the PWM controller found in various newer
> - * Atmel SOCs, including the AVR32 series and the AT91sam9263.
> - *
> - * Chips with current Linux ports have only 4 PWM channels, out of max 32.
> - * AT32UC3A and AT32UC3B chips have 7 channels (but currently no Linux).
> - * Docs are inconsistent about the width of the channel counter registers;
> - * it's at least 16 bits, but several places say 20 bits.
> - */
> -#define PWM_NCHAN 4 /* max 32 */
> -
> -struct pwm {
> - spinlock_t lock;
> - struct platform_device *pdev;
> - u32 mask;
> - int irq;
> - void __iomem *base;
> - struct clk *clk;
> - struct pwm_channel *channel[PWM_NCHAN];
> - void (*handler[PWM_NCHAN])(struct pwm_channel *);
> -};
> -
> -
> -/* global PWM controller registers */
> -#define PWM_MR 0x00
> -#define PWM_ENA 0x04
> -#define PWM_DIS 0x08
> -#define PWM_SR 0x0c
> -#define PWM_IER 0x10
> -#define PWM_IDR 0x14
> -#define PWM_IMR 0x18
> -#define PWM_ISR 0x1c
> -
> -static inline void pwm_writel(const struct pwm *p, unsigned offset, u32 val)
> -{
> - __raw_writel(val, p->base + offset);
> -}
> -
> -static inline u32 pwm_readl(const struct pwm *p, unsigned offset)
> -{
> - return __raw_readl(p->base + offset);
> -}
> -
> -static inline void __iomem *pwmc_regs(const struct pwm *p, int index)
> -{
> - return p->base + 0x200 + index * 0x20;
> -}
> -
> -static struct pwm *pwm;
> -
> -static void pwm_dumpregs(struct pwm_channel *ch, char *tag)
> -{
> - struct device *dev = &pwm->pdev->dev;
> -
> - dev_dbg(dev, "%s: mr %08x, sr %08x, imr %08x\n",
> - tag,
> - pwm_readl(pwm, PWM_MR),
> - pwm_readl(pwm, PWM_SR),
> - pwm_readl(pwm, PWM_IMR));
> - dev_dbg(dev,
> - "pwm ch%d - mr %08x, dty %u, prd %u, cnt %u\n",
> - ch->index,
> - pwm_channel_readl(ch, PWM_CMR),
> - pwm_channel_readl(ch, PWM_CDTY),
> - pwm_channel_readl(ch, PWM_CPRD),
> - pwm_channel_readl(ch, PWM_CCNT));
> -}
> -
> -
> -/**
> - * pwm_channel_alloc - allocate an unused PWM channel
> - * @index: identifies the channel
> - * @ch: structure to be initialized
> - *
> - * Drivers allocate PWM channels according to the board's wiring, and
> - * matching board-specific setup code. Returns zero or negative errno.
> - */
> -int pwm_channel_alloc(int index, struct pwm_channel *ch)
> -{
> - unsigned long flags;
> - int status = 0;
> -
> - /* insist on PWM init, with this signal pinned out */
> - if (!pwm || !(pwm->mask & 1 << index))
> - return -ENODEV;
> -
> - if (index < 0 || index >= PWM_NCHAN || !ch)
> - return -EINVAL;
> - memset(ch, 0, sizeof *ch);
> -
> - spin_lock_irqsave(&pwm->lock, flags);
> - if (pwm->channel[index])
> - status = -EBUSY;
> - else {
> - clk_enable(pwm->clk);
> -
> - ch->regs = pwmc_regs(pwm, index);
> - ch->index = index;
> -
> - /* REVISIT: ap7000 seems to go 2x as fast as we expect!! */
> - ch->mck = clk_get_rate(pwm->clk);
> -
> - pwm->channel[index] = ch;
> - pwm->handler[index] = NULL;
> -
> - /* channel and irq are always disabled when we return */
> - pwm_writel(pwm, PWM_DIS, 1 << index);
> - pwm_writel(pwm, PWM_IDR, 1 << index);
> - }
> - spin_unlock_irqrestore(&pwm->lock, flags);
> - return status;
> -}
> -EXPORT_SYMBOL(pwm_channel_alloc);
> -
> -static int pwmcheck(struct pwm_channel *ch)
> -{
> - int index;
> -
> - if (!pwm)
> - return -ENODEV;
> - if (!ch)
> - return -EINVAL;
> - index = ch->index;
> - if (index < 0 || index >= PWM_NCHAN || pwm->channel[index] != ch)
> - return -EINVAL;
> -
> - return index;
> -}
> -
> -/**
> - * pwm_channel_free - release a previously allocated channel
> - * @ch: the channel being released
> - *
> - * The channel is completely shut down (counter and IRQ disabled),
> - * and made available for re-use. Returns zero, or negative errno.
> - */
> -int pwm_channel_free(struct pwm_channel *ch)
> -{
> - unsigned long flags;
> - int t;
> -
> - spin_lock_irqsave(&pwm->lock, flags);
> - t = pwmcheck(ch);
> - if (t >= 0) {
> - pwm->channel[t] = NULL;
> - pwm->handler[t] = NULL;
> -
> - /* channel and irq are always disabled when we return */
> - pwm_writel(pwm, PWM_DIS, 1 << t);
> - pwm_writel(pwm, PWM_IDR, 1 << t);
> -
> - clk_disable(pwm->clk);
> - t = 0;
> - }
> - spin_unlock_irqrestore(&pwm->lock, flags);
> - return t;
> -}
> -EXPORT_SYMBOL(pwm_channel_free);
> -
> -int __pwm_channel_onoff(struct pwm_channel *ch, int enabled)
> -{
> - unsigned long flags;
> - int t;
> -
> - /* OMITTED FUNCTIONALITY: starting several channels in synch */
> -
> - spin_lock_irqsave(&pwm->lock, flags);
> - t = pwmcheck(ch);
> - if (t >= 0) {
> - pwm_writel(pwm, enabled ? PWM_ENA : PWM_DIS, 1 << t);
> - t = 0;
> - pwm_dumpregs(ch, enabled ? "enable" : "disable");
> - }
> - spin_unlock_irqrestore(&pwm->lock, flags);
> -
> - return t;
> -}
> -EXPORT_SYMBOL(__pwm_channel_onoff);
> -
> -/**
> - * pwm_clk_alloc - allocate and configure CLKA or CLKB
> - * @prescale: from 0..10, the power of two used to divide MCK
> - * @div: from 1..255, the linear divisor to use
> - *
> - * Returns PWM_CPR_CLKA, PWM_CPR_CLKB, or negative errno. The allocated
> - * clock will run with a period of (2^prescale * div) / MCK, or twice as
> - * long if center aligned PWM output is used. The clock must later be
> - * deconfigured using pwm_clk_free().
> - */
> -int pwm_clk_alloc(unsigned prescale, unsigned div)
> -{
> - unsigned long flags;
> - u32 mr;
> - u32 val = (prescale << 8) | div;
> - int ret = -EBUSY;
> -
> - if (prescale >= 10 || div == 0 || div > 255)
> - return -EINVAL;
> -
> - spin_lock_irqsave(&pwm->lock, flags);
> - mr = pwm_readl(pwm, PWM_MR);
> - if ((mr & 0xffff) == 0) {
> - mr |= val;
> - ret = PWM_CPR_CLKA;
> - } else if ((mr & (0xffff << 16)) == 0) {
> - mr |= val << 16;
> - ret = PWM_CPR_CLKB;
> - }
> - if (ret > 0)
> - pwm_writel(pwm, PWM_MR, mr);
> - spin_unlock_irqrestore(&pwm->lock, flags);
> - return ret;
> -}
> -EXPORT_SYMBOL(pwm_clk_alloc);
> -
> -/**
> - * pwm_clk_free - deconfigure and release CLKA or CLKB
> - *
> - * Reverses the effect of pwm_clk_alloc().
> - */
> -void pwm_clk_free(unsigned clk)
> -{
> - unsigned long flags;
> - u32 mr;
> -
> - spin_lock_irqsave(&pwm->lock, flags);
> - mr = pwm_readl(pwm, PWM_MR);
> - if (clk == PWM_CPR_CLKA)
> - pwm_writel(pwm, PWM_MR, mr & ~(0xffff << 0));
> - if (clk == PWM_CPR_CLKB)
> - pwm_writel(pwm, PWM_MR, mr & ~(0xffff << 16));
> - spin_unlock_irqrestore(&pwm->lock, flags);
> -}
> -EXPORT_SYMBOL(pwm_clk_free);
> -
> -/**
> - * pwm_channel_handler - manage channel's IRQ handler
> - * @ch: the channel
> - * @handler: the handler to use, possibly NULL
> - *
> - * If the handler is non-null, the handler will be called after every
> - * period of this PWM channel. If the handler is null, this channel
> - * won't generate an IRQ.
> - */
> -int pwm_channel_handler(struct pwm_channel *ch,
> - void (*handler)(struct pwm_channel *ch))
> -{
> - unsigned long flags;
> - int t;
> -
> - spin_lock_irqsave(&pwm->lock, flags);
> - t = pwmcheck(ch);
> - if (t >= 0) {
> - pwm->handler[t] = handler;
> - pwm_writel(pwm, handler ? PWM_IER : PWM_IDR, 1 << t);
> - t = 0;
> - }
> - spin_unlock_irqrestore(&pwm->lock, flags);
> -
> - return t;
> -}
> -EXPORT_SYMBOL(pwm_channel_handler);
> -
> -static irqreturn_t pwm_irq(int id, void *_pwm)
> -{
> - struct pwm *p = _pwm;
> - irqreturn_t handled = IRQ_NONE;
> - u32 irqstat;
> - int index;
> -
> - spin_lock(&p->lock);
> -
> - /* ack irqs, then handle them */
> - irqstat = pwm_readl(pwm, PWM_ISR);
> -
> - while (irqstat) {
> - struct pwm_channel *ch;
> - void (*handler)(struct pwm_channel *ch);
> -
> - index = ffs(irqstat) - 1;
> - irqstat &= ~(1 << index);
> - ch = pwm->channel[index];
> - handler = pwm->handler[index];
> - if (handler && ch) {
> - spin_unlock(&p->lock);
> - handler(ch);
> - spin_lock(&p->lock);
> - handled = IRQ_HANDLED;
> - }
> - }
> -
> - spin_unlock(&p->lock);
> - return handled;
> -}
> -
> -static int __init pwm_probe(struct platform_device *pdev)
> -{
> - struct resource *r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> - int irq = platform_get_irq(pdev, 0);
> - u32 *mp = pdev->dev.platform_data;
> - struct pwm *p;
> - int status = -EIO;
> -
> - if (pwm)
> - return -EBUSY;
> - if (!r || irq < 0 || !mp || !*mp)
> - return -ENODEV;
> - if (*mp & ~((1<<PWM_NCHAN)-1)) {
> - dev_warn(&pdev->dev, "mask 0x%x ... more than %d channels\n",
> - *mp, PWM_NCHAN);
> - return -EINVAL;
> - }
> -
> - p = kzalloc(sizeof(*p), GFP_KERNEL);
> - if (!p)
> - return -ENOMEM;
> -
> - spin_lock_init(&p->lock);
> - p->pdev = pdev;
> - p->mask = *mp;
> - p->irq = irq;
> - p->base = ioremap(r->start, r->end - r->start + 1);
> - if (!p->base)
> - goto fail;
> - p->clk = clk_get(&pdev->dev, "pwm_clk");
> - if (IS_ERR(p->clk)) {
> - status = PTR_ERR(p->clk);
> - p->clk = NULL;
> - goto fail;
> - }
> -
> - status = request_irq(irq, pwm_irq, 0, pdev->name, p);
> - if (status < 0)
> - goto fail;
> -
> - pwm = p;
> - platform_set_drvdata(pdev, p);
> -
> - return 0;
> -
> -fail:
> - if (p->clk)
> - clk_put(p->clk);
> - if (p->base)
> - iounmap(p->base);
> -
> - kfree(p);
> - return status;
> -}
> -
> -static int __exit pwm_remove(struct platform_device *pdev)
> -{
> - struct pwm *p = platform_get_drvdata(pdev);
> -
> - if (p != pwm)
> - return -EINVAL;
> -
> - clk_enable(pwm->clk);
> - pwm_writel(pwm, PWM_DIS, (1 << PWM_NCHAN) - 1);
> - pwm_writel(pwm, PWM_IDR, (1 << PWM_NCHAN) - 1);
> - clk_disable(pwm->clk);
> -
> - pwm = NULL;
> -
> - free_irq(p->irq, p);
> - clk_put(p->clk);
> - iounmap(p->base);
> - kfree(p);
> -
> - return 0;
> -}
> -
> -static struct platform_driver atmel_pwm_driver = {
> - .driver = {
> - .name = "atmel_pwm",
> - .owner = THIS_MODULE,
> - },
> - .remove = __exit_p(pwm_remove),
> -
> - /* NOTE: PWM can keep running in AVR32 "idle" and "frozen" states;
> - * and all AT91sam9263 states, albeit at reduced clock rate if
> - * MCK becomes the slow clock (i.e. what Linux labels STR).
> - */
> -};
> -
> -static int __init pwm_init(void)
> -{
> - return platform_driver_probe(&atmel_pwm_driver, pwm_probe);
> -}
> -module_init(pwm_init);
> -
> -static void __exit pwm_exit(void)
> -{
> - platform_driver_unregister(&atmel_pwm_driver);
> -}
> -module_exit(pwm_exit);
> -
> -MODULE_DESCRIPTION("Driver for AT32/AT91 PWM module");
> -MODULE_LICENSE("GPL");
> -MODULE_ALIAS("platform:atmel_pwm");
> diff --git a/drivers/pwm/atmel-pwm.c b/drivers/pwm/atmel-pwm.c
> new file mode 100644
> index 0000000..8566866
> --- /dev/null
> +++ b/drivers/pwm/atmel-pwm.c
> @@ -0,0 +1,592 @@
> +/*
> + * drivers/pwm/atmel-pwm.c
> + *
> + * Copyright (C) 2010 Bill Gatliff <bgat@billgatliff.com>
> + * Copyright (C) 2007 David Brownell
> + *
> + * This program is free software; you may redistribute and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful, but
> + * WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + * General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, write to the Free Software
> + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
> + * USA
> + */
> +
> +#include <linux/module.h>
> +#include <linux/init.h>
> +#include <linux/slab.h>
> +#include <linux/clk.h>
> +#include <linux/err.h>
> +#include <linux/io.h>
> +#include <linux/interrupt.h>
> +#include <linux/platform_device.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_release(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.release = atmel_pwm_release;
> + 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");
> --
> 1.7.1
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-embedded" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PWM 04/10] Implements PWM-based LED control
2010-10-01 15:17 ` [PWM 04/10] Implements PWM-based LED control Bill Gatliff
@ 2010-10-16 7:58 ` Grant Likely
0 siblings, 0 replies; 33+ messages in thread
From: Grant Likely @ 2010-10-16 7:58 UTC (permalink / raw)
To: Bill Gatliff; +Cc: linux-embedded
On Fri, Oct 01, 2010 at 10:17:45AM -0500, Bill Gatliff wrote:
> Signed-off-by: Bill Gatliff <bgat@billgatliff.com>
Ditto on comment and patch title
> ---
> drivers/leds/Kconfig | 19 +++-
> drivers/leds/leds-pwm.c | 224 +++++++++++++++++++++++-------------------
> include/linux/pwm/pwm-led.h | 33 +++++++
> 3 files changed, 169 insertions(+), 107 deletions(-)
> create mode 100644 include/linux/pwm/pwm-led.h
>
> diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
> index e411262..3af2cde 100644
> --- a/drivers/leds/Kconfig
> +++ b/drivers/leds/Kconfig
> @@ -26,12 +26,19 @@ config LEDS_88PM860X
> This option enables support for on-chip LED drivers found on Marvell
> Semiconductor 88PM8606 PMIC.
>
> -config LEDS_ATMEL_PWM
> - tristate "LED Support using Atmel PWM outputs"
> - depends on ATMEL_PWM
> - help
> - This option enables support for LEDs driven using outputs
> - of the dedicated PWM controller found on newer Atmel SOCs.
> +config LEDS_PWM
> + tristate "Support for LEDs connected to Generic PWM channels"
> + depends on LEDS_CLASS && GENERIC_PWM
> + help
> + Enables support for LEDs connected to PWM devices that
Inconsistent whitespace indentation
> + conform to the Generic PWM API. This API allows drivers
> + to adjust the brightness of the LED by varying the duty
> + cycle of the signal at the PWM channel output, using PWM API
> + commands, rather than merely turning them on and off.
> +
> + If your platform has devices with drivers that implement
> + the Generic PWM API, and those devices have outputs that
> + are connected to LEDs, then you probably want to say 'Y' here.
>
> config LEDS_LOCOMO
> tristate "LED Support for Locomo device"
> diff --git a/drivers/leds/leds-pwm.c b/drivers/leds/leds-pwm.c
> index da3fa8d..ab064ac 100644
> --- a/drivers/leds/leds-pwm.c
> +++ b/drivers/leds/leds-pwm.c
> @@ -1,153 +1,175 @@
> /*
> - * linux/drivers/leds-pwm.c
> + * drivers/leds/leds-pwm.c
> *
> - * simple PWM based LED control
> + * Copyright (C) 2010 Bill Gatliff <bgat@billgatliff.com>
> + * Copyright (C) 2009 Loutao Fu, Pengutronix <l.fu@pengutronix.de>
> *
> - * Copyright 2009 Luotao Fu @ Pengutronix (l.fu@pengutronix.de)
> + * Based on leds-gpio.c by Raphael Assenat <raph@8d.com>
> *
> - * based on leds-gpio.c by Raphael Assenat <raph@8d.com>
> - *
> - * This program is free software; you can redistribute it and/or modify
> - * it under the terms of the GNU General Public License version 2 as
> + * 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/kernel.h>
> -#include <linux/init.h>
> #include <linux/platform_device.h>
> -#include <linux/fb.h>
> -#include <linux/leds.h>
> -#include <linux/err.h>
> -#include <linux/pwm.h>
> -#include <linux/leds_pwm.h>
> #include <linux/slab.h>
> -
> -struct led_pwm_data {
> - struct led_classdev cdev;
> - struct pwm_device *pwm;
> - unsigned int active_low;
> - unsigned int period;
> +#include <linux/leds.h>
> +#include <linux/io.h>
> +#include <linux/pwm/pwm.h>
> +#include <linux/pwm/pwm-led.h>
> +
> +struct led_pwm {
> + struct led_classdev led;
> + struct pwm_channel *pwm;
> + int percent;
> };
Looks to be gratuitous modification. Changing the name of the
structure (led_pwm_data --> led_pwm) and changing cdev --> led makes
this diff very large for no good reason. If you really want to change
the names, then do it in a separate cleanup patch without functional
changes.
>
> -static void led_pwm_set(struct led_classdev *led_cdev,
> - enum led_brightness brightness)
Ditto here. Reformatting and renaming the function name just makes
the patch hard to review. Please repost without the unnecessary
rework (or at least split into a separate patch).
> +static inline struct led_pwm *to_led_pwm(const struct led_classdev *c)
> +{
> + return container_of(c, struct led_pwm, led);
> +}
> +
> +static void
> +led_pwm_brightness_set(struct led_classdev *c,
> + enum led_brightness b)
> +{
> + struct led_pwm *led = to_led_pwm(c);
> + int percent;
> +
> + percent = (b * 100) / (LED_FULL - LED_OFF);
> + led->percent = percent;
> + pwm_set_duty_percent(led->pwm, percent);
> +}
> +
> +static enum led_brightness
> +led_pwm_brightness_get(struct led_classdev *c)
> {
> - struct led_pwm_data *led_dat =
> - container_of(led_cdev, struct led_pwm_data, cdev);
> - unsigned int max = led_dat->cdev.max_brightness;
> - unsigned int period = led_dat->period;
> -
> - if (brightness == 0) {
> - pwm_config(led_dat->pwm, 0, period);
> - pwm_disable(led_dat->pwm);
> - } else {
> - pwm_config(led_dat->pwm, brightness * period / max, period);
> - pwm_enable(led_dat->pwm);
> + struct led_pwm *led = to_led_pwm(c);
> + return led->percent;
> +}
> +
> +static int
> +led_pwm_blink_set(struct led_classdev *c,
> + unsigned long *on_ms,
> + unsigned long *off_ms)
> +{
> + struct led_pwm *led = to_led_pwm(c);
> + struct pwm_channel_config cfg;
> +
> + if (*on_ms == 0 && *off_ms == 0) {
> + *on_ms = 1000UL;
> + *off_ms = 1000UL;
> }
> +
> + cfg.config_mask = PWM_CONFIG_DUTY_NS
> + | PWM_CONFIG_PERIOD_NS;
> +
> + cfg.duty_ns = *on_ms * 1000000UL;
> + cfg.period_ns = (*on_ms + *off_ms) * 1000000UL;
> +
> + return pwm_config(led->pwm, &cfg);
> }
>
> -static int led_pwm_probe(struct platform_device *pdev)
> +static int __devinit
> +led_pwm_probe(struct platform_device *pdev)
> {
> - struct led_pwm_platform_data *pdata = pdev->dev.platform_data;
> - struct led_pwm *cur_led;
> - struct led_pwm_data *leds_data, *led_dat;
> - int i, ret = 0;
> + struct pwm_led_platform_data *pdata = pdev->dev.platform_data;
> + struct led_pwm *led;
> + int ret;
>
> - if (!pdata)
> - return -EBUSY;
> + if (!pdata || !pdata->led_info)
> + return -EINVAL;
>
> - leds_data = kzalloc(sizeof(struct led_pwm_data) * pdata->num_leds,
> - GFP_KERNEL);
> - if (!leds_data)
> + led = kzalloc(sizeof(*led), GFP_KERNEL);
> + if (!led)
> return -ENOMEM;
>
> - for (i = 0; i < pdata->num_leds; i++) {
> - cur_led = &pdata->leds[i];
> - led_dat = &leds_data[i];
> -
> - led_dat->pwm = pwm_request(cur_led->pwm_id,
> - cur_led->name);
> - if (IS_ERR(led_dat->pwm)) {
> - dev_err(&pdev->dev, "unable to request PWM %d\n",
> - cur_led->pwm_id);
> - goto err;
> - }
> -
> - led_dat->cdev.name = cur_led->name;
> - led_dat->cdev.default_trigger = cur_led->default_trigger;
> - led_dat->active_low = cur_led->active_low;
> - led_dat->period = cur_led->pwm_period_ns;
> - led_dat->cdev.brightness_set = led_pwm_set;
> - led_dat->cdev.brightness = LED_OFF;
> - led_dat->cdev.max_brightness = cur_led->max_brightness;
> - led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME;
> -
> - ret = led_classdev_register(&pdev->dev, &led_dat->cdev);
> - if (ret < 0) {
> - pwm_free(led_dat->pwm);
> - goto err;
> - }
> + led->pwm = pwm_request(pdata->bus_id, pdata->chan,
> + pdata->led_info->name);
> + if (!led->pwm) {
> + ret = -EINVAL;
> + goto err_pwm_request;
> }
>
> - platform_set_drvdata(pdev, leds_data);
> + platform_set_drvdata(pdev, led);
>
> - return 0;
> + led->led.name = pdata->led_info->name;
> + led->led.default_trigger = pdata->led_info->default_trigger;
> + led->led.brightness_set = led_pwm_brightness_set;
> + led->led.brightness_get = led_pwm_brightness_get;
> + led->led.blink_set = led_pwm_blink_set;
> + led->led.brightness = LED_OFF;
>
> -err:
> - if (i > 0) {
> - for (i = i - 1; i >= 0; i--) {
> - led_classdev_unregister(&leds_data[i].cdev);
> - pwm_free(leds_data[i].pwm);
> - }
> - }
> + ret = pwm_config(led->pwm, pdata->config);
> + if (ret)
> + goto err_pwm_config;
> + pwm_start(led->pwm);
> +
> + ret = led_classdev_register(&pdev->dev, &led->led);
> + if (ret < 0)
> + goto err_classdev_register;
>
> - kfree(leds_data);
> + return 0;
> +
> +err_classdev_register:
> + pwm_stop(led->pwm);
> +err_pwm_config:
> + pwm_release(led->pwm);
> +err_pwm_request:
> + kfree(led);
>
> return ret;
> }
>
> -static int __devexit led_pwm_remove(struct platform_device *pdev)
> +static int __devexit
> +led_pwm_remove(struct platform_device *pdev)
> {
> - int i;
> - struct led_pwm_platform_data *pdata = pdev->dev.platform_data;
> - struct led_pwm_data *leds_data;
> + struct led_pwm *led = platform_get_drvdata(pdev);
>
> - leds_data = platform_get_drvdata(pdev);
> + led_classdev_unregister(&led->led);
>
> - for (i = 0; i < pdata->num_leds; i++) {
> - led_classdev_unregister(&leds_data[i].cdev);
> - pwm_free(leds_data[i].pwm);
> + if (led->pwm) {
> + pwm_stop(led->pwm);
> + pwm_release(led->pwm);
> }
>
> - kfree(leds_data);
> + kfree(led);
>
> return 0;
> }
>
> static struct platform_driver led_pwm_driver = {
> - .probe = led_pwm_probe,
> - .remove = __devexit_p(led_pwm_remove),
> - .driver = {
> - .name = "leds_pwm",
> - .owner = THIS_MODULE,
> + .driver = {
> + .name = "leds-pwm",
> + .owner = THIS_MODULE,
> },
> + .probe = led_pwm_probe,
> + .remove = led_pwm_remove,
> };
>
> -static int __init led_pwm_init(void)
> +static int __init led_pwm_modinit(void)
> {
> return platform_driver_register(&led_pwm_driver);
> }
> +module_init(led_pwm_modinit);
>
> -static void __exit led_pwm_exit(void)
> +static void __exit led_pwm_modexit(void)
> {
> platform_driver_unregister(&led_pwm_driver);
> }
> +module_exit(led_pwm_modexit);
>
> -module_init(led_pwm_init);
> -module_exit(led_pwm_exit);
> -
> -MODULE_AUTHOR("Luotao Fu <l.fu@pengutronix.de>");
> -MODULE_DESCRIPTION("PWM LED driver for PXA");
> +MODULE_AUTHOR("Bill Gatliff <bgat@billgatliff.com>");
> +MODULE_DESCRIPTION("Driver for LEDs with PWM-controlled brightness");
> MODULE_LICENSE("GPL");
> MODULE_ALIAS("platform:leds-pwm");
> diff --git a/include/linux/pwm/pwm-led.h b/include/linux/pwm/pwm-led.h
> new file mode 100644
> index 0000000..8ffeecc
> --- /dev/null
> +++ b/include/linux/pwm/pwm-led.h
> @@ -0,0 +1,33 @@
> +/*
> + * include/linux/pwm-led.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_LED_H
> +#define __LINUX_PWM_LED_H
> +
> +struct led_info;
> +struct pwm_channel_config;
> +
> +struct pwm_led_platform_data {
> + const char *bus_id;
> + int chan;
> + struct pwm_channel_config *config;
> + struct led_info *led_info;
> +};
> +
> +#endif /* __LINUX_PWM_LED_H */
> --
> 1.7.1
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-embedded" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PWM 05/10] LED "dim" trigger based on PWM control of the LED
2010-10-01 15:17 ` [PWM 05/10] LED "dim" trigger based on PWM control of the LED Bill Gatliff
@ 2010-10-16 8:00 ` Grant Likely
0 siblings, 0 replies; 33+ messages in thread
From: Grant Likely @ 2010-10-16 8:00 UTC (permalink / raw)
To: Bill Gatliff; +Cc: linux-embedded
On Fri, Oct 01, 2010 at 10:17:46AM -0500, Bill Gatliff wrote:
> Signed-off-by: Bill Gatliff <bgat@billgatliff.com>
Ditto on description
Otherwise pretty straight forward. Looks okay to me.
g.
> ---
> drivers/leds/ledtrig-dim.c | 96 ++++++++++++++++++++++++++++++++++++++++++++
> 1 files changed, 96 insertions(+), 0 deletions(-)
> create mode 100644 drivers/leds/ledtrig-dim.c
>
> diff --git a/drivers/leds/ledtrig-dim.c b/drivers/leds/ledtrig-dim.c
> new file mode 100644
> index 0000000..8f93f44
> --- /dev/null
> +++ b/drivers/leds/ledtrig-dim.c
> @@ -0,0 +1,96 @@
> +/*
> + * LED Dim Trigger
> + *
> + * Copyright (C) 2010 Bill Gatliff <bgat@billgatliff.com>
> + *
> + * "Dims" an LED based on system load. Derived from Atsushi Nemoto's
> + * ledtrig-heartbeat.c.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + *
> + */
> +#include <linux/module.h>
> +#include <linux/kernel.h>
> +#include <linux/slab.h>
> +#include <linux/init.h>
> +#include <linux/timer.h>
> +#include <linux/sched.h>
> +#include <linux/leds.h>
> +
> +#include "leds.h"
> +
> +struct dim_trig_data {
> + struct timer_list timer;
> +};
> +
> +
> +static void
> +led_dim_function(unsigned long data)
> +{
> + struct led_classdev *led_cdev = (struct led_classdev *)data;
> + struct dim_trig_data *dim_data = led_cdev->trigger_data;
> + unsigned int brightness;
> +
> + brightness = ((LED_FULL - LED_OFF) * avenrun[0]) / EXP_1;
> + if (brightness > LED_FULL)
> + brightness = LED_FULL;
> +
> + led_set_brightness(led_cdev, brightness);
> + mod_timer(&dim_data->timer, jiffies + msecs_to_jiffies(500));
> +}
> +
> +
> +static void
> +dim_trig_activate(struct led_classdev *led_cdev)
> +{
> + struct dim_trig_data *dim_data;
> +
> + dim_data = kzalloc(sizeof(*dim_data), GFP_KERNEL);
> + if (!dim_data)
> + return;
> +
> + led_cdev->trigger_data = dim_data;
> + setup_timer(&dim_data->timer,
> + led_dim_function, (unsigned long)led_cdev);
> + led_dim_function(dim_data->timer.data);
> +}
> +
> +
> +static void
> +dim_trig_deactivate(struct led_classdev *led_cdev)
> +{
> + struct dim_trig_data *dim_data = led_cdev->trigger_data;
> +
> + if (dim_data) {
> + del_timer_sync(&dim_data->timer);
> + kfree(dim_data);
> + }
> +}
> +
> +
> +static struct led_trigger dim_led_trigger = {
> + .name = "dim",
> + .activate = dim_trig_activate,
> + .deactivate = dim_trig_deactivate,
> +};
> +
> +
> +static int __init dim_trig_init(void)
> +{
> + return led_trigger_register(&dim_led_trigger);
> +}
> +module_init(dim_trig_init);
> +
> +
> +static void __exit dim_trig_exit(void)
> +{
> + led_trigger_unregister(&dim_led_trigger);
> +}
> +module_exit(dim_trig_exit);
> +
> +
> +MODULE_AUTHOR("Bill Gatliff <bgat@billgatliff.com>");
> +MODULE_DESCRIPTION("Dim LED trigger");
> +MODULE_LICENSE("GPL");
> --
> 1.7.1
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-embedded" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PWM 06/10] Incorporate PWM API code into KBuild
2010-10-01 15:17 ` [PWM 06/10] Incorporate PWM API code into KBuild Bill Gatliff
@ 2010-10-16 8:02 ` Grant Likely
2010-10-19 2:17 ` Bill Gatliff
0 siblings, 1 reply; 33+ messages in thread
From: Grant Likely @ 2010-10-16 8:02 UTC (permalink / raw)
To: Bill Gatliff; +Cc: linux-embedded
On Fri, Oct 01, 2010 at 10:17:47AM -0500, Bill Gatliff wrote:
> Signed-off-by: Bill Gatliff <bgat@billgatliff.com>
Ditto on description.
> ---
> drivers/Kconfig | 2 ++
> drivers/Makefile | 2 ++
> drivers/leds/Kconfig | 22 ++++++++++++++++------
> drivers/leds/Makefile | 2 ++
> drivers/pwm/Kconfig | 28 ++++++++++++++++++++++++++++
> drivers/pwm/Makefile | 6 ++++++
> 6 files changed, 56 insertions(+), 6 deletions(-)
> create mode 100644 drivers/pwm/Kconfig
> create mode 100644 drivers/pwm/Makefile
Hmmm... is this patch series bisectable?
>
> diff --git a/drivers/Kconfig b/drivers/Kconfig
> index a2b902f..60390cb 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 a2aea53..fa7ca1c 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/leds/Kconfig b/drivers/leds/Kconfig
> index 3af2cde..b434fa3 100644
> --- a/drivers/leds/Kconfig
> +++ b/drivers/leds/Kconfig
> @@ -249,12 +249,6 @@ config LEDS_DAC124S085
> This option enables support for DAC124S085 SPI DAC from NatSemi,
> which can be used to control up to four LEDs.
>
> -config LEDS_PWM
> - tristate "PWM driven LED Support"
> - depends on HAVE_PWM
> - help
> - This option enables support for pwm driven LEDs
> -
> config LEDS_REGULATOR
> tristate "REGULATOR driven LED support"
> depends on REGULATOR
> @@ -354,6 +348,14 @@ config LEDS_TRIGGER_HEARTBEAT
> load average.
> If unsure, say Y.
>
> +config LEDS_TRIGGER_DIM
> + tristate "LED Dimmer Trigger"
> + depends on LEDS_TRIGGERS
> + help
> + Regulates the brightness of an LED based on the 1-minute CPU
> + load average. Ideal for PWM-driven LEDs.
> + If unsure, say Y.
> +
> config LEDS_TRIGGER_BACKLIGHT
> tristate "LED backlight Trigger"
> help
> @@ -374,6 +376,14 @@ config LEDS_TRIGGER_GPIO
>
> If unsure, say N.
>
> +config LEDS_TRIGGER_DIM
> + tristate "LED Dimmer Trigger"
> + depends on LEDS_TRIGGERS
> + help
> + Regulates the brightness of an LED based on the 1-minute CPU
> + load average. Ideal for PWM-driven LEDs.
> + If unsure, say Y.
> +
Inconsistent whitespace
> config LEDS_TRIGGER_DEFAULT_ON
> tristate "LED Default ON Trigger"
> help
> diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
> index 7d6b958..a4ccea4 100644
> --- a/drivers/leds/Makefile
> +++ b/drivers/leds/Makefile
> @@ -22,6 +22,7 @@ obj-$(CONFIG_LEDS_COBALT_RAQ) += leds-cobalt-raq.o
> obj-$(CONFIG_LEDS_SUNFIRE) += leds-sunfire.o
> obj-$(CONFIG_LEDS_PCA9532) += leds-pca9532.o
> obj-$(CONFIG_LEDS_GPIO) += leds-gpio.o
> +obj-$(CONFIG_LEDS_PWM) += leds-pwm.o
> obj-$(CONFIG_LEDS_LP3944) += leds-lp3944.o
> obj-$(CONFIG_LEDS_CLEVO_MAIL) += leds-clevo-mail.o
> obj-$(CONFIG_LEDS_HP6XX) += leds-hp6xx.o
> @@ -46,6 +47,7 @@ obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o
> obj-$(CONFIG_LEDS_TRIGGER_TIMER) += ledtrig-timer.o
> obj-$(CONFIG_LEDS_TRIGGER_IDE_DISK) += ledtrig-ide-disk.o
> obj-$(CONFIG_LEDS_TRIGGER_HEARTBEAT) += ledtrig-heartbeat.o
> +obj-$(CONFIG_LEDS_TRIGGER_DIM) += ledtrig-dim.o
> obj-$(CONFIG_LEDS_TRIGGER_BACKLIGHT) += ledtrig-backlight.o
> obj-$(CONFIG_LEDS_TRIGGER_GPIO) += ledtrig-gpio.o
> obj-$(CONFIG_LEDS_TRIGGER_DEFAULT_ON) += ledtrig-default-on.o
> diff --git a/drivers/pwm/Kconfig b/drivers/pwm/Kconfig
> new file mode 100644
> index 0000000..0584c25
> --- /dev/null
> +++ b/drivers/pwm/Kconfig
> @@ -0,0 +1,28 @@
> +#
> +# PWM infrastructure and devices
> +#
> +
> +menuconfig GENERIC_PWM
> + tristate "PWM Support"
> + depends on SYSFS
> + help
> + This enables PWM support through the generic PWM API.
> + 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.
> + If unsure, say N.
> +
> +config GPIO_PWM
> + tristate "PWM emulation using GPIO"
> + help
> + This option enables a single-channel PWM device using
Ditto
> + a kernel interval timer and a GPIO pin. If unsure, say N.
> +
> +endif
> diff --git a/drivers/pwm/Makefile b/drivers/pwm/Makefile
> new file mode 100644
> index 0000000..e8cacc5
> --- /dev/null
> +++ b/drivers/pwm/Makefile
> @@ -0,0 +1,6 @@
> +#
> +# Makefile for pwm devices
> +#
> +obj-y := pwm.o
> +obj-$(CONFIG_ATMEL_PWM) += atmel-pwm.o
> +obj-$(CONFIG_GPIO_PWM) += gpio.o
> --
> 1.7.1
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-embedded" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at http://vger.kernel.org/majordomo-info.html
^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PWM 06/10] Incorporate PWM API code into KBuild
2010-10-16 8:02 ` Grant Likely
@ 2010-10-19 2:17 ` Bill Gatliff
0 siblings, 0 replies; 33+ messages in thread
From: Bill Gatliff @ 2010-10-19 2:17 UTC (permalink / raw)
To: Grant Likely; +Cc: linux-embedded
>
> Hmmm... is this patch series bisectable?
Ooh, that's something I hadn't thought about. Probably not.
In addition to incorporating your feedback (thanks!), I'll rejigger
things in the next revision so that they are bisectable.
b.g.
--
Bill Gatliff
bgat@billgatliff.com
^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PWM 01/10] API to consolidate PWM devices behind a common user and kernel interface
2010-10-16 7:42 ` Grant Likely
@ 2010-10-20 18:13 ` Bill Gatliff
2010-10-20 18:34 ` Grant Likely
0 siblings, 1 reply; 33+ messages in thread
From: Bill Gatliff @ 2010-10-20 18:13 UTC (permalink / raw)
To: Grant Likely; +Cc: linux-embedded
Grant:
> I'm not hugely keen on the naming approach, and I'd rather see the
> pwm core be responsible for the namespace.
The problem with that, as I see it, is that then the channel
identifiers become dependent on the order of device initialization.
By allowing driver authors to assign names, however, users are capable
of pinning their channel requests to specific devices and channels
with confidence that those names will remain valid regardless of the
sequence they compile their kernel in.
> Nitpick: putting the filename into the file itself I find tends to be
> useless
Agreed. Those tend to sneak in during my early development, and then
I get so used to seeing them that I forget to take them out.
>> +static int __pwm_create_sysfs(struct pwm_device *pwm);
>
> If you order the functions in this file correct, the forward
> declaration should not be necessary.
In theory, I agree completely with you. In this specific instance,
however, I haven't been able to structure things in a way that
eliminates at least one forward declaration.
One possibility would be to move all the sysfs-related stuff out to a
separate file altogether. Depending on what the class attribute
rework ends up doing to the code, I just might do that. Of course,
that wouldn't eliminate the forward declaration--- it would just move
it. :)
>> + for (wchan = 0; wchan < pwm->nchan; wchan++) {
>> + dev = class_find_device(&pwm_class, NULL,
>> + &pwm->channels[wchan],
>> + __match_device);
>
> If the pwm_channel structure carried a pointer to its device
> structure, then this lookup wouldn't be needed.
This comment, combined with your other one on the class attributes
(below) convince me that the device model isn't quite right yet. I
will rework this until seems correct.
> The above 8 functions are so tiny that they should probably be static
> inlines in the header file.
Agreed.
> Rather than open coding the registration of sysfs files, I believe the
> right thing to do is to use class attributes so that the files get
> automatically registered for you and are immediately advertised to
> userspace (very important for correct interaction with udev).
I think I'm either still a little weak on my device model
understanding, or things have been changing there lately and I haven't
had the time to notice. Regardless, this code always seemed a little
awkward--- you seem to agree, so I'll do some research and come back
with something better.
> Also, sysfs_create_group() should not be called directly when working
> with devices.
Probably necessary because I'm not doing something right elsewhere. :)
>> + /* TODO: how to deal with devices that register very early? */
>
> Same thing we always do for bootstrapping. If it *must* be early,
> then we do a simple direct access to get things running in platform
> code, and then take over in the driver when it finally gets probed.
> The driver model helps with many things, but really early stuff isn't
> one of them.
I _think_ you and I are saying the same thing: that the device model
only works well with late-registering devices. I'm not sure I
understand your suggestion for how to deal with must-be-early devices.
b.g.
--
Bill Gatliff
bgat@billgatliff.com
^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PWM 01/10] API to consolidate PWM devices behind a common user and kernel interface
2010-10-20 18:13 ` Bill Gatliff
@ 2010-10-20 18:34 ` Grant Likely
2010-10-20 19:32 ` Bill Gatliff
0 siblings, 1 reply; 33+ messages in thread
From: Grant Likely @ 2010-10-20 18:34 UTC (permalink / raw)
To: Bill Gatliff; +Cc: linux-embedded
On Wed, Oct 20, 2010 at 01:13:34PM -0500, Bill Gatliff wrote:
> Grant:
>
> > I'm not hugely keen on the naming approach, and I'd rather see the
> > pwm core be responsible for the namespace.
>
> The problem with that, as I see it, is that then the channel
> identifiers become dependent on the order of device initialization.
Correct, just like on all other subsystems.
> By allowing driver authors to assign names, however, users are capable
> of pinning their channel requests to specific devices and channels
> with confidence that those names will remain valid regardless of the
> sequence they compile their kernel in.
Identify by name tends to be a horrible interface in my opinion. The
lesson has been learned in the network and block layers that names
change over time and trying to lock them down to something that a user
finds 'sane' ends up in a mess of workarounds and bad assumptions in
the code.
For in-kernel users, either platform_data or notifier hooks can be used
by one device to have explicit knowledge about the device. No need to
muck about with name encoding.
In userspace, the correct solution is to expose real information about
the device (how the instance is connected to the system plus any other
identifying data) in the sysfs tree and let udev (or a trivial
replacement) decode to a stable user-friendly name.
Essentially, identifying by name attempts to expose real information
about which device it is, but the process of encoding a name is lossy
and so it fails to be robust in the long term.
>
> > Nitpick: putting the filename into the file itself I find tends to be
> > useless
>
> Agreed. Those tend to sneak in during my early development, and then
> I get so used to seeing them that I forget to take them out.
:-)
>
> >> +static int __pwm_create_sysfs(struct pwm_device *pwm);
> >
> > If you order the functions in this file correct, the forward
> > declaration should not be necessary.
>
> In theory, I agree completely with you. In this specific instance,
> however, I haven't been able to structure things in a way that
> eliminates at least one forward declaration.
Fair enough, sometimes that is necessary. I didn't look that closely.
> One possibility would be to move all the sysfs-related stuff out to a
> separate file altogether. Depending on what the class attribute
> rework ends up doing to the code, I just might do that. Of course,
> that wouldn't eliminate the forward declaration--- it would just move
> it. :)
Yeah, by changing to using class attributes, a lot of this could end
up going away.
>
> >> + for (wchan = 0; wchan < pwm->nchan; wchan++) {
> >> + dev = class_find_device(&pwm_class, NULL,
> >> + &pwm->channels[wchan],
> >> + __match_device);
> >
> > If the pwm_channel structure carried a pointer to its device
> > structure, then this lookup wouldn't be needed.
>
> This comment, combined with your other one on the class attributes
> (below) convince me that the device model isn't quite right yet. I
> will rework this until seems correct.
:-)
>
> > The above 8 functions are so tiny that they should probably be static
> > inlines in the header file.
>
> Agreed.
>
> > Rather than open coding the registration of sysfs files, I believe the
> > right thing to do is to use class attributes so that the files get
> > automatically registered for you and are immediately advertised to
> > userspace (very important for correct interaction with udev).
>
> I think I'm either still a little weak on my device model
> understanding, or things have been changing there lately and I haven't
> had the time to notice. Regardless, this code always seemed a little
> awkward--- you seem to agree, so I'll do some research and come back
> with something better.
>
> > Also, sysfs_create_group() should not be called directly when working
> > with devices.
>
> Probably necessary because I'm not doing something right elsewhere. :)
>
> >> + /* TODO: how to deal with devices that register very early? */
> >
> > Same thing we always do for bootstrapping. If it *must* be early,
> > then we do a simple direct access to get things running in platform
> > code, and then take over in the driver when it finally gets probed.
> > The driver model helps with many things, but really early stuff isn't
> > one of them.
>
> I _think_ you and I are saying the same thing: that the device model
> only works well with late-registering devices. I'm not sure I
> understand your suggestion for how to deal with must-be-early devices.
My experience has been there are very few things that cannot be
deferred to module_initcall() time if the device model is handled
well. Typically the things that fall outside of that model are core
infrastructure things like irq controllers which need to be ready to
go before many of the subsystems are initialized.
Basically I'm saying that for things that *really do* need to be setup
before the initcalls, you either don't use the device model at all, or
you figure out how to hand off control from early bootstrap code once
the driver model is set up. Kind of like the early console code.
Scary stuff.
g.
^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PWM 01/10] API to consolidate PWM devices behind a common user and kernel interface
2010-10-20 18:34 ` Grant Likely
@ 2010-10-20 19:32 ` Bill Gatliff
2010-10-21 13:18 ` Bill Gatliff
0 siblings, 1 reply; 33+ messages in thread
From: Bill Gatliff @ 2010-10-20 19:32 UTC (permalink / raw)
To: Grant Likely; +Cc: linux-embedded
Grant:
> Identify by name tends to be a horrible interface in my opinion. The
> lesson has been learned in the network and block layers that names
> change over time and trying to lock them down to something that a user
> finds 'sane' ends up in a mess of workarounds and bad assumptions in
> the code.
The reason it doesn't work for network and block devices is because
the names of those devices aren't relevant; it's only the IP address
assigned and mount point that are of interest. Furthermore, you
rarely talk directly to an ethernet controller: you just route packets
through it. And the kernel decides which packets are going to go
where based solely on IP and related information, and never the name
of the device. So pretending that the name matters is a recipe for
trouble.
PWM devices are different, I think, in that the name of the device
uniquely identifies a physical peripheral in the part, and the channel
number is often mapped to a specific pin on the chip package by the
platform integrator. If you have a hope of driving your PWM signal to
the right place each time the system builds and boots, you have to be
able to specify things to that level of detail.
And then there's the laziness factor. :) I don't like having to
figure out over and over again that "pwm-1:2" is really mapped to the
PWMC peripheral's channel two; and I definitely don't like worrying
that channel enumerations will change just because I plugged in a new
i2c GPIO chip or LED driver. I want to be able to tell the system
that I want to talk to a specific pin, regardless of when it shows up
relative to all the other pins in the system.
> In userspace, the correct solution is to expose real information about
> the device (how the instance is connected to the system plus any other
> identifying data) in the sysfs tree and let udev (or a trivial
> replacement) decode to a stable user-friendly name.
That's precisely what I'm doing.
I don't anticipate that device authors will use names like
"panel_backlight". Rather, they should use names like "pwmc:2:3",
which means "PWMC peripheral 2 channel number 3". Then they can look
at a schematic to verify that PWMC2 indeed goes to the panel
backlight.
> Essentially, identifying by name attempts to expose real information
> about which device it is, but the process of encoding a name is lossy
> and so it fails to be robust in the long term.
In the case of semi-transparent devices like ethernet controllers and
block devices, I agree. But the information for a PWM signal doesn't
merely pass through a PWM controller: it tells the controller what to
do. Subtle but important difference compared to an ethernet
controller that makes it compelling to use actual names in my case.
> Yeah, by changing to using class attributes, a lot of this could end
> up going away.
Looking at that now, in fact.
> My experience has been there are very few things that cannot be
> deferred to module_initcall() time if the device model is handled
> well. Typically the things that fall outside of that model are core
> infrastructure things like irq controllers which need to be ready to
> go before many of the subsystems are initialized.
The one situation that comes to mind is i2c GPIO expanders on powerpc.
Since everything in that world is bound only after the DTS file gets
parsed, finding and registering things can be tricky. Or maybe it was
the onboard GPIO controllers in the MPC5200. IIRC, I found a case
where something could call pwm_register() before the PWM framework
itself had even been established. That caused sysfs to complain, to
say the least.
Of course, the problem could actually be that I'm not using sysfs
quite right yet. Hmm...
b.g.
--
Bill Gatliff
bgat@billgatliff.com
^ permalink raw reply [flat|nested] 33+ messages in thread
* Re: [PWM 01/10] API to consolidate PWM devices behind a common user and kernel interface
2010-10-20 19:32 ` Bill Gatliff
@ 2010-10-21 13:18 ` Bill Gatliff
0 siblings, 0 replies; 33+ messages in thread
From: Bill Gatliff @ 2010-10-21 13:18 UTC (permalink / raw)
To: Grant Likely; +Cc: linux-embedded
Grant:
On Wed, Oct 20, 2010 at 2:32 PM, Bill Gatliff <bgat@billgatliff.com> wrote:
>> Yeah, by changing to using class attributes, a lot of this could end
>> up going away.
>
> Looking at that now, in fact.
Going to class device attributes did the trick. Thanks for pointing
me in the right direction!
b.g.
--
Bill Gatliff
bgat@billgatliff.com
^ permalink raw reply [flat|nested] 33+ messages in thread
end of thread, other threads:[~2010-10-21 13:18 UTC | newest]
Thread overview: 33+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2010-10-01 15:17 [PWM 01/10] API to consolidate PWM devices behind a common user and kernel interface Bill Gatliff
2010-10-01 15:17 ` [PWM 02/10] Emulates PWM hardware using a high-resolution timer and a GPIO pin Bill Gatliff
2010-10-16 6:54 ` Grant Likely
2010-10-01 15:17 ` [PWM 03/10] Expunge old Atmel PWMC driver, replacing it with one that conforms to the PWM API Bill Gatliff
2010-10-16 7:50 ` Grant Likely
2010-10-01 15:17 ` [PWM 04/10] Implements PWM-based LED control Bill Gatliff
2010-10-16 7:58 ` Grant Likely
2010-10-01 15:17 ` [PWM 05/10] LED "dim" trigger based on PWM control of the LED Bill Gatliff
2010-10-16 8:00 ` Grant Likely
2010-10-01 15:17 ` [PWM 06/10] Incorporate PWM API code into KBuild Bill Gatliff
2010-10-16 8:02 ` Grant Likely
2010-10-19 2:17 ` Bill Gatliff
2010-10-01 15:17 ` [PWM 07/10] PWM API driver for MPC52xx GPT peripheral Bill Gatliff
2010-10-01 15:17 ` [PWM 08/10] Initial support for PXA PWM peripheral; compile-tested only Bill Gatliff
2010-10-01 15:17 ` Bill Gatliff
2010-10-01 15:17 ` [PWM 09/10] Build pwm.o only if CONFIG_GENERIC_PWM is set Bill Gatliff
2010-10-01 15:17 ` [PWM 10/10] Expunge previous driver for PXA PWM Bill Gatliff
2010-10-01 22:00 ` [PWM 01/10] API to consolidate PWM devices behind a common user and kernel interface Kevin Hilman
2010-10-02 5:13 ` Jason Kridner
2010-10-06 18:45 ` Bill Gatliff
2010-10-06 19:08 ` Kevin Hilman
2010-10-02 12:25 ` Hector Oron
2010-10-06 18:48 ` Bill Gatliff
2010-10-16 6:05 ` Grant Likely
2010-10-05 10:35 ` sugumar
2010-10-06 18:50 ` Bill Gatliff
2010-10-06 19:02 ` Grosen, Mark
2010-10-07 7:58 ` Sugumar Natarajan
2010-10-16 7:42 ` Grant Likely
2010-10-20 18:13 ` Bill Gatliff
2010-10-20 18:34 ` Grant Likely
2010-10-20 19:32 ` Bill Gatliff
2010-10-21 13:18 ` Bill Gatliff
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).