Darn it!! I'll clean this one up in the final post.
b.g.
--
Bill Gatliff
bg...@billgatliff.com
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majo...@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
diff --git a/Documentation/pwm.txt b/Documentation/pwm.txt
new file mode 100644
index 0000000..2c41ca5
--- /dev/null
+++ b/Documentation/pwm.txt
@@ -0,0 +1,260 @@
+ Generic PWM Device API
+
+ February 1, 2010
+ Bill Gatliff
+ <bg...@billgatliff.com>
+
+
+
+The code in drivers/pwm and include/linux/pwm/ implements an API for
+applications involving pulse-width-modulation signals. This document
+describes how the API implementation facilitates both PWM-generating
+devices, and users of those devices.
+
+
+
+Motivation
+
+The primary goals for implementing the "generic PWM API" are to
+consolidate the various PWM implementations within a consistent and
+redundancy-reducing framework, and to facilitate the use of
+hotpluggable PWM devices.
+
+Previous PWM-related implementations within the Linux kernel achieved
+their consistency via cut-and-paste, but did not need to (and didn't)
+facilitate more than one PWM-generating device within the system---
+hotplug or otherwise. The Generic PWM Device API might be most
+appropriately viewed as an update to those implementations, rather
+than a complete rewrite.
+
+
+
+Challenges
+
+One of the difficulties in implementing a generic PWM framework is the
+fact that pulse-width-modulation applications involve real-world
+signals, which often must be carefully managed to prevent destruction
+of hardware that is linked to those signals. A DC motor that
+experiences a brief interruption in the PWM signal controlling it
+might destructively overheat; it could suddenly change speed, losing
+synchronization with a sensor; it could even suddenly change direction
+or torque, breaking the mechanical device connected to it.
+
+(A generic PWM device framework is not directly responsible for
+preventing the above scenarios: that responsibility lies with the
+hardware designer, and the application and driver authors. But it
+must to the greatest extent possible make it easy to avoid such
+problems).
+
+A generic PWM device framework must accommodate the substantial
+differences between available PWM-generating hardware devices, without
+becoming sub-optimal for any of them.
+
+Finally, a generic PWM device framework must be relatively
+lightweight, computationally speaking. Some PWM users demand
+high-speed outputs, plus the ability to regulate those outputs
+quickly. A device framework must be able to "keep up" with such
+hardware, while still leaving time to do real work.
+
+The Generic PWM Device API is an attempt to meet all of the above
+requirements. At its initial publication, the API was already in use
+managing small DC motors, sensors and solenoids through a
+custom-designed, optically-isolated H-bridge driver.
+
+
+
+Functional Overview
+
+The Generic PWM Device API framework is implemented in
+include/linux/pwm/pwm.h and drivers/pwm/pwm.c. The functions therein
+use information from pwm_device, pwm_channel and pwm_channel_config
+structures to invoke services in PWM peripheral device drivers.
+Consult drivers/pwm/atmel-pwm.c for an example driver.
+
+There are two classes of adopters of the PWM framework:
+
+ "Users" -- those wishing to employ the API merely to produce PWM
+ signals; once they have identified the appropriate physical output
+ on the platform in question, they don't care about the details of
+ the underlying hardware
+
+ "Driver authors" -- those wishing to bind devices that can generate
+ PWM signals to the Generic PWM Device API, so that the services of
+ those devices become available to users. Assuming the hardware can
+ support the needs of a user, driver authors don't care about the
+ details of the user's application
+
+Generally speaking, users will first invoke pwm_request() to obtain a
+handle to a PWM device. They will then pass that handle to functions
+like pwm_duty_ns() and pwm_period_ns() to set the duty cycle and
+period of the PWM signal, respectively. They will also invoke
+pwm_start() and pwm_stop() to turn the signal on and off.
+
+The Generic PWM API framework also provides a sysfs interface to PWM
+devices, which is adequate for basic application needs and testing.
+
+Driver authors fill out a pwm_device structure, which describes the
+capabilities of the PWM hardware being constructed--- including the
+number of distinct output "channels" the peripheral offers. They then
+invoke pwm_register() (usually from within their device's probe()
+handler) to make the PWM API aware of their device. The framework
+will call back to the methods described in the pwm_device structure as
+users begin to configure and utilize the hardware.
+
+Note that PWM signals can be produced by a variety of peripherals,
+beyond the true "PWM hardware" offered by many system-on-chip devices.
+Other possibilities include timer/counters with compare-match
+capabilities, carefully-programmed synchronous serial ports
+(e.g. SPI), and GPIO pins driven by kernel interval timers. With a
+proper pwm_device structure, these devices and pseudo-devices can all
+be accommodated by the Generic PWM Device API framework.
+
+
+
+Using the API to Generate PWM Signals -- Basic Functions for Users
+
+
+pwm_request() -- Returns a pwm_channel pointer, which is subsequently
+passed to the other user-related PWM functions. Once requested, a PWM
+channel is marked as in-use and subsequent requests prior to
+pwm_free() will fail.
+
+The names used to refer to PWM devices are defined by driver authors.
+Typically they are platform device bus identifiers, and this
+convention is encouraged for consistency.
+
+
+pwm_free() -- Marks a PWM channel as no longer in use. The PWM device
+is stopped before it is released by the API.
+
+
+pwm_period_ns() -- Specifies the PWM signal's period, in nanoseconds.
+
+
+pwm_duty_ns() -- Specifies the PWM signal's active duration, in nanoseconds.
+
+
+pwm_duty_percent() -- Specifies the PWM signal's active duration, as a
+percentage of the current period of the signal. NOTE: this value is
+not recalculated if the period of the signal is subsequently changed.
+
+
+pwm_start(), pwm_stop() -- Turns the PWM signal on and off. Except
+where stated otherwise by a driver author, signals are stopped at the
+end of the current period, at which time the output is set to its
+inactive state.
+
+
+pwm_polarity() -- Defines whether the PWM signal output's active
+region is "1" or "0". A 10% duty-cycle, polarity=1 signal will
+conventionally be at 5V (or 3.3V, or 1000V, or whatever the platform
+hardware does) for 10% of the period. The same configuration of a
+polarity=0 signal will be at 5V (or 3.3V, or ...) for 90% of the
+period.
+
+
+
+Using the API to Generate PWM Signals -- Advanced Functions
+
+
+pwm_config() -- Passes a pwm_channel_config structure to the
+associated device driver. This function is invoked by pwm_start(),
+pwm_duty_ns(), etc. and is one of two main entry points to the PWM
+driver for the hardware being used. The configuration change is
+guaranteed atomic if multiple configuration changes are specified.
+This function might sleep, depending on what the device driver has to
+do to satisfy the request. All PWM device drivers must support this
+entry point.
+
+
+pwm_config_nosleep() -- Passes a pwm_channel_config structure to the
+associated device driver. If the driver must sleep in order to
+implement the requested configuration change, -EWOULDBLOCK is
+returned. Users may call this function from interrupt handlers, for
+example. This is the other main entry point into the PWM hardware
+driver, but not all device drivers support this entry point.
+
+
+pwm_synchronize(), pwm_unsynchronize() -- "Synchronizes" two or more
+PWM channels, if the underlying hardware permits. (If it doesn't, the
+framework facilitates emulating this capability but it is not yet
+implemented). Synchronized channels will start and stop
+simultaneously when any single channel in the group is started or
+stopped. Use pwm_unsynchronize(..., NULL) to completely detach a
+channel from any other synchronized channels. By default, all PWM
+channels are unsynchronized.
+
+
+pwm_set_handler() -- Defines an end-of-period callback. The indicated
+function will be invoked in a worker thread at the end of each PWM
+period, and can subsequently invoke pwm_config(), etc. Must be used
+with extreme care for high-speed PWM outputs. Set the handler
+function to NULL to un-set the handler.
+
+
+
+Implementing a PWM Device API Driver -- Functions for Driver Authors
+
+
+Fill out the appropriate fields in a pwm_device structure, and submit
+to pwm_register():
+
+
+bus_id -- the plain-text name of the device. Users will bind to a
+channel on the device using this name plus the channel number. For
+example, the Atmel PWMC's bus_id is "atmel_pwmc", the same as used by
+the platform device driver (recommended). The first device registered
+thereby receives bus_id "atmel_pwmc.0", which is what you put in
+pwm_device.bus_id. Channels are then named "atmel_pwmc.0:[0-3]".
+(Hint: just use pdev->dev.bus_id in your probe() method).
+
+
+nchan -- the number of distinct output channels provided by the device.
+
+
+request -- (optional) Invoked each time a user requests a channel.
+Use to turn on clocks, clean up register states, etc. The framework
+takes care of device locking/unlocking; you will see only successful
+requests.
+
+
+free -- (optional) Callback for each time a user relinquishes a
+channel. The framework will have already stopped, unsynchronized and
+un-handled the channel. Use to turn off clocks, etc. as necessary.
+
+
+synchronize, unsynchronize -- (optional) Callbacks to
+synchronize/unsynchronize channels. Some devices provide this
+capability in hardware; for others, it can be emulated (see
+atmel_pwmc.c's sync_mask for an example).
+
+
+set_callback -- (optional) Invoked when a user requests a handler. If
+the hardware supports an end-of-period interrupt, invoke the function
+indicated during your interrupt handler. The callback function itself
+is always internal to the API, and does not map directly to the user's
+callback function.
+
+
+config -- Invoked to change the device configuration, always from a
+sleep-capable context. All the changes indicated must be performed
+atomically, ideally synchronized to an end-of-period event (so that
+you avoid short or long output pulses). You may sleep, etc. as
+necessary within this function.
+
+
+config_nosleep -- (optional) Invoked to change device configuration
+from within a context that is not allowed to sleep. If you cannot
+perform the requested configuration changes without sleeping, return
+-EWOULDBLOCK.
+
+
+
+Acknowledgements
+
+
+The author expresses his gratitude to the countless developers who
+have reviewed and submitted feedback on the various versions of the
+Generic PWM Device API code, and those who have submitted drivers and
+applications that use the framework. You know who you are. ;)
+
diff --git a/drivers/pwm/pwm.c b/drivers/pwm/pwm.c
new file mode 100644
index 0000000..f369384
--- /dev/null
+++ b/drivers/pwm/pwm.c
@@ -0,0 +1,633 @@
+/*
+ * drivers/pwm/pwm.c
+ *
+ * Copyright (C) 2010 Bill Gatliff <bg...@billgatliff.com>
+ *
+ * This program is free software; you may redistribute and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/spinlock.h>
+#include <linux/fs.h>
+#include <linux/completion.h>
+#include <linux/workqueue.h>
+#include <linux/pwm/pwm.h>
+
+static int __pwm_create_sysfs(struct pwm_device *pwm);
+
+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 & 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;
+ return 0;
+}
+
+struct pwm_channel *
+pwm_request(const char *bus_id,
+ int chan,
+ const char *requester)
+{
+ struct pwm_device *p;
+ int ret;
+
+ mutex_lock(&device_list_mutex);
+
+ p = __pwm_find_device(bus_id);
+ if (!p || chan >= p->nchan)
+ goto err_no_device;
+
+ if (!try_module_get(p->owner))
+ goto err_module_get_failed;
+
+ ret = __pwm_request_channel(&p->channels[chan], requester);
+ if (ret)
+ goto err_request_failed;
+
+ mutex_unlock(&device_list_mutex);
+ return &p->channels[chan];
+
+err_request_failed:
+ module_put(p->owner);
+err_module_get_failed:
+err_no_device:
+ mutex_unlock(&device_list_mutex);
+ return NULL;
+}
+EXPORT_SYMBOL(pwm_request);
+
+void pwm_free(struct pwm_channel *p)
+{
+ mutex_lock(&device_list_mutex);
+
+ if (!test_and_clear_bit(FLAG_REQUESTED, &p->flags))
+ goto done;
+
+ pwm_stop(p);
+ pwm_unsynchronize(p, NULL);
+ pwm_set_handler(p, NULL, NULL);
+
+ if (p->pwm->free)
+ p->pwm->free(p);
+ module_put(p->pwm->owner);
+done:
+ mutex_unlock(&device_list_mutex);
+}
+EXPORT_SYMBOL(pwm_free);
+
+unsigned long pwm_ns_to_ticks(struct pwm_channel *p,
+ unsigned long nsecs)
+{
+ unsigned long long ticks;
+
+ ticks = nsecs;
+ ticks *= p->tick_hz;
+ do_div(ticks, 1000000000);
+ return ticks;
+}
+EXPORT_SYMBOL(pwm_ns_to_ticks);
+
+unsigned long pwm_ticks_to_ns(struct pwm_channel *p,
+ unsigned long ticks)
+{
+ unsigned long long ns;
+
+ if (!p->tick_hz)
+ return 0;
+
+ ns = ticks;
+ ns *= 1000000000UL;
+ do_div(ns, p->tick_hz);
+ return ns;
+}
+EXPORT_SYMBOL(pwm_ticks_to_ns);
+
+static void
+pwm_config_ns_to_ticks(struct pwm_channel *p,
+ struct pwm_channel_config *c)
+{
+ if (c->config_mask & PWM_CONFIG_PERIOD_NS) {
+ c->period_ticks = pwm_ns_to_ticks(p, c->period_ns);
+ c->config_mask &= ~PWM_CONFIG_PERIOD_NS;
+ c->config_mask |= PWM_CONFIG_PERIOD_TICKS;
+ }
+
+ if (c->config_mask & PWM_CONFIG_DUTY_NS) {
+ c->duty_ticks = pwm_ns_to_ticks(p, c->duty_ns);
+ c->config_mask &= ~PWM_CONFIG_DUTY_NS;
+ c->config_mask |= PWM_CONFIG_DUTY_TICKS;
+ }
+}
+
+static void
+pwm_config_percent_to_ticks(struct pwm_channel *p,
+ struct pwm_channel_config *c)
+{
+ if (c->config_mask & PWM_CONFIG_DUTY_PERCENT) {
+ if (c->config_mask & PWM_CONFIG_PERIOD_TICKS)
+ c->duty_ticks = c->period_ticks;
+ else
+ c->duty_ticks = p->period_ticks;
+
+ c->duty_ticks *= c->duty_percent;
+ c->duty_ticks /= 100;
+ c->config_mask &= ~PWM_CONFIG_DUTY_PERCENT;
+ c->config_mask |= PWM_CONFIG_DUTY_TICKS;
+ }
+}
+
+int pwm_config_nosleep(struct pwm_channel *p,
+ struct pwm_channel_config *c)
+{
+ if (!p->pwm->config_nosleep)
+ return -EINVAL;
+
+ pwm_config_ns_to_ticks(p, c);
+ pwm_config_percent_to_ticks(p, c);
+
+ return p->pwm->config_nosleep(p, c);
+}
+EXPORT_SYMBOL(pwm_config_nosleep);
+
+int pwm_config(struct pwm_channel *p,
+ struct pwm_channel_config *c)
+{
+ int ret = 0;
+
+ if (unlikely(!p->pwm->config))
+ return -EINVAL;
+
+ pwm_config_ns_to_ticks(p, c);
+ pwm_config_percent_to_ticks(p, c);
+
+ switch (c->config_mask & (PWM_CONFIG_PERIOD_TICKS
+ | PWM_CONFIG_DUTY_TICKS)) {
+ case PWM_CONFIG_PERIOD_TICKS:
+ if (p->duty_ticks > c->period_ticks) {
+ ret = -EINVAL;
+ goto err;
+ }
+ break;
+ case PWM_CONFIG_DUTY_TICKS:
+ if (p->period_ticks < c->duty_ticks) {
+ ret = -EINVAL;
+ goto err;
+ }
+ break;
+ case PWM_CONFIG_DUTY_TICKS | PWM_CONFIG_PERIOD_TICKS:
+ if (c->duty_ticks > c->period_ticks) {
+ ret = -EINVAL;
+ goto err;
+ }
+ break;
+ default:
+ break;
+ }
+
+err:
+ dev_dbg(p->pwm->dev, "%s: config_mask %d period_ticks %lu duty_ticks %lu"
+ " polarity %d duty_ns %lu period_ns %lu duty_percent %d\n",
+ __func__, c->config_mask, c->period_ticks, c->duty_ticks,
+ c->polarity, c->duty_ns, c->period_ns, c->duty_percent);
+
+ if (ret)
+ return ret;
+ return p->pwm->config(p, c);
+}
+EXPORT_SYMBOL(pwm_config);
+
+int pwm_set_period_ns(struct pwm_channel *p,
+ unsigned long period_ns)
+{
+ struct pwm_channel_config c = {
+ .config_mask = PWM_CONFIG_PERIOD_TICKS,
+ .period_ticks = pwm_ns_to_ticks(p, period_ns),
+ };
+
+ return pwm_config(p, &c);
+}
+EXPORT_SYMBOL(pwm_set_period_ns);
+
+unsigned long pwm_get_period_ns(struct pwm_channel *p)
+{
+ return pwm_ticks_to_ns(p, p->period_ticks);
+}
+EXPORT_SYMBOL(pwm_get_period_ns);
+
+int pwm_set_duty_ns(struct pwm_channel *p,
+ unsigned long duty_ns)
+{
+ struct pwm_channel_config c = {
+ .config_mask = PWM_CONFIG_DUTY_TICKS,
+ .duty_ticks = pwm_ns_to_ticks(p, duty_ns),
+ };
+ return pwm_config(p, &c);
+}
+EXPORT_SYMBOL(pwm_set_duty_ns);
+
+unsigned long pwm_get_duty_ns(struct pwm_channel *p)
+{
+ return pwm_ticks_to_ns(p, p->duty_ticks);
+}
+EXPORT_SYMBOL(pwm_get_duty_ns);
+
+int pwm_set_duty_percent(struct pwm_channel *p,
+ int percent)
+{
+ struct pwm_channel_config c = {
+ .config_mask = PWM_CONFIG_DUTY_PERCENT,
+ .duty_percent = percent,
+ };
+ return pwm_config(p, &c);
+}
+EXPORT_SYMBOL(pwm_set_duty_percent);
+
+int pwm_set_polarity(struct pwm_channel *p,
+ int active_high)
+{
+ struct pwm_channel_config c = {
+ .config_mask = PWM_CONFIG_POLARITY,
+ .polarity = active_high,
+ };
+ return pwm_config(p, &c);
+}
+EXPORT_SYMBOL(pwm_set_polarity);
+
+int pwm_start(struct pwm_channel *p)
+{
+ struct pwm_channel_config c = {
+ .config_mask = PWM_CONFIG_START,
+ };
+ return pwm_config(p, &c);
+}
+EXPORT_SYMBOL(pwm_start);
+
+int pwm_stop(struct pwm_channel *p)
+{
+ struct pwm_channel_config c = {
+ .config_mask = PWM_CONFIG_STOP,
+ };
+ return pwm_config(p, &c);
+}
+EXPORT_SYMBOL(pwm_stop);
+
+int pwm_synchronize(struct pwm_channel *p,
+ struct pwm_channel *to_p)
+{
+ if (p->pwm != to_p->pwm) {
+ /* TODO: support cross-device synchronization */
+ return -EINVAL;
+ }
+
+ if (!p->pwm->synchronize)
+ return -EINVAL;
+
+ return p->pwm->synchronize(p, to_p);
+}
+EXPORT_SYMBOL(pwm_synchronize);
+
+int pwm_unsynchronize(struct pwm_channel *p,
+ struct pwm_channel *from_p)
+{
+ if (from_p && (p->pwm != from_p->pwm)) {
+ /* TODO: support cross-device synchronization */
+ return -EINVAL;
+ }
+
+ if (!p->pwm->unsynchronize)
+ return -EINVAL;
+
+ return p->pwm->unsynchronize(p, from_p);
+}
+EXPORT_SYMBOL(pwm_unsynchronize);
+
+static void pwm_handler(struct work_struct *w)
+{
+ struct pwm_channel *p = container_of(w, struct pwm_channel,
+ handler_work);
+ if (p->handler && p->handler(p, p->handler_data))
+ pwm_stop(p);
+}
+
+static void __pwm_callback(struct pwm_channel *p)
+{
+ queue_work(pwm_handler_workqueue, &p->handler_work);
+ dev_dbg(p->pwm->dev, "handler %p scheduled with data %p\n",
+ p->handler, p->handler_data);
+}
+
+int pwm_set_handler(struct pwm_channel *p,
+ pwm_handler_t handler,
+ void *data)
+{
+ if (p->pwm->set_callback) {
+ p->handler_data = data;
+ p->handler = handler;
+ INIT_WORK(&p->handler_work, pwm_handler);
+ return p->pwm->set_callback(p, handler ? __pwm_callback : NULL);
+ }
+ return -EINVAL;
+}
+EXPORT_SYMBOL(pwm_set_handler);
+
+static ssize_t pwm_run_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t len)
+{
+ struct pwm_channel *p = dev_get_drvdata(dev);
+ if (sysfs_streq(buf, "1"))
+ pwm_start(p);
+ else if (sysfs_streq(buf, "0"))
+ pwm_stop(p);
+ return len;
+}
+static DEVICE_ATTR(run, 0200, NULL, pwm_run_store);
+
+static ssize_t pwm_duty_ns_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct pwm_channel *p = dev_get_drvdata(dev);
+ return sprintf(buf, "%lu\n", pwm_get_duty_ns(p));
+}
+
+static ssize_t pwm_duty_ns_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t len)
+{
+ unsigned long duty_ns;
+ struct pwm_channel *p = dev_get_drvdata(dev);
+
+ if (1 == sscanf(buf, "%lu", &duty_ns))
+ pwm_set_duty_ns(p, duty_ns);
+ return len;
+}
+static DEVICE_ATTR(duty_ns, 0644, pwm_duty_ns_show, pwm_duty_ns_store);
+
+static ssize_t pwm_period_ns_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct pwm_channel *p = dev_get_drvdata(dev);
+ return sprintf(buf, "%lu\n", pwm_get_period_ns(p));
+}
+
+static ssize_t pwm_period_ns_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t len)
+{
+ unsigned long period_ns;
+ struct pwm_channel *p = dev_get_drvdata(dev);
+
+ if (1 == sscanf(buf, "%lu", &period_ns))
+ pwm_set_period_ns(p, period_ns);
+ return len;
+}
+static DEVICE_ATTR(period_ns, 0644, pwm_period_ns_show, pwm_period_ns_store);
+
+static ssize_t pwm_polarity_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct pwm_channel *p = dev_get_drvdata(dev);
+ return sprintf(buf, "%d\n", p->active_low ? 0 : 1);
+}
+
+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, "sysfs");
+ mutex_unlock(&device_list_mutex);
+
+ return sprintf(buf, "%s\n", p->requester);
+}
+
+static ssize_t pwm_request_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t len)
+{
+ struct pwm_channel *p = dev_get_drvdata(dev);
+ pwm_free(p);
+ return len;
+}
+static DEVICE_ATTR(request, 0644, pwm_request_show, pwm_request_store);
+
+static const struct attribute *pwm_attrs[] =
+{
+ &dev_attr_run.attr,
+ &dev_attr_polarity.attr,
+ &dev_attr_duty_ns.attr,
+ &dev_attr_period_ns.attr,
+ &dev_attr_request.attr,
+ NULL,
+};
+
+static const struct attribute_group pwm_device_attr_group = {
+ .attrs = (struct attribute **)pwm_attrs,
+};
+
+static int __pwm_create_sysfs(struct pwm_device *pwm)
+{
+ int ret = 0;
+ struct device *dev;
+ int wchan;
+
+ for (wchan = 0; wchan < pwm->nchan; wchan++) {
+ dev = device_create(&pwm_class, pwm->dev, MKDEV(0, 0),
+ pwm->channels + wchan,
+ "%s:%d", pwm->bus_id, wchan);
+ if (!dev)
+ goto err_dev_create;
+ ret = sysfs_create_group(&dev->kobj, &pwm_device_attr_group);
+ if (ret)
+ goto err_dev_create;
+ }
+
+ return ret;
+
+err_dev_create:
+ for (wchan = 0; wchan < pwm->nchan; wchan++) {
+ dev = class_find_device(&pwm_class, NULL,
+ &pwm->channels[wchan],
+ __match_device);
+ if (dev) {
+ put_device(dev);
+ device_unregister(dev);
+ }
+ }
+
+ return ret;
+}
+
+static struct class_attribute pwm_class_attrs[] = {
+ __ATTR_NULL,
+};
+
+static struct class pwm_class = {
+ .name = "pwm",
+ .owner = THIS_MODULE,
+
+ .class_attrs = pwm_class_attrs,
+};
+
+static int __init pwm_init(void)
+{
+ int ret;
+
+ /* TODO: how to deal with devices that register very early? */
+
+ 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..848cd76
--- /dev/null
+++ b/include/linux/pwm/pwm.h
@@ -0,0 +1,170 @@
+#ifndef __LINUX_PWM_H
+#define __LINUX_PWM_H
+
+/*
+ * include/linux/pwm.h
+ *
+ * Copyright (C) 2008 Bill Gatliff
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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/completion.h>
+#include <linux/workqueue.h>
+#include <linux/spinlock.h>
+#include <linux/list.h>
+
+enum {
+ PWM_CONFIG_DUTY_TICKS = BIT(0),
+ PWM_CONFIG_PERIOD_TICKS = BIT(1),
+ PWM_CONFIG_POLARITY = BIT(2),
+ PWM_CONFIG_START = BIT(3),
+ PWM_CONFIG_STOP = BIT(4),
+
+ PWM_CONFIG_HANDLER = BIT(5),
+
+ PWM_CONFIG_DUTY_NS = BIT(6),
+ PWM_CONFIG_DUTY_PERCENT = BIT(7),
+ PWM_CONFIG_PERIOD_NS = BIT(8),
+};
+
+struct pwm_channel;
+struct work_struct;
+
+typedef int (*pwm_handler_t)(struct pwm_channel *p, void *data);
+typedef void (*pwm_callback_t)(struct pwm_channel *p);
+
+struct pwm_channel_config {
+ int config_mask;
+ unsigned long duty_ticks;
+ unsigned long period_ticks;
+ int polarity;
+
+ pwm_handler_t handler;
+
+ unsigned long duty_ns;
+ unsigned long period_ns;
+ int duty_percent;
+};
+
+struct pwm_device {
+ struct list_head list;
+ spinlock_t list_lock;
+ struct device *dev;
+ struct module *owner;
+ struct pwm_channel *channels;
+
+ const char *bus_id;
+ int nchan;
+
+ int (*request) (struct pwm_channel *p);
+ void (*free) (struct pwm_channel *p);
+ int (*config) (struct pwm_channel *p,
+ struct pwm_channel_config *c);
+ int (*config_nosleep)(struct pwm_channel *p,
+ struct pwm_channel_config *c);
+ int (*synchronize) (struct pwm_channel *p,
+ struct pwm_channel *to_p);
+ int (*unsynchronize)(struct pwm_channel *p,
+ struct pwm_channel *from_p);
+ int (*set_callback) (struct pwm_channel *p,
+ pwm_callback_t callback);
+};
+
+int pwm_register(struct pwm_device *pwm);
+int pwm_unregister(struct pwm_device *pwm);
+
+enum {
+ FLAG_REQUESTED = 0,
+ FLAG_STOP = 1,
+};
+
+struct pwm_channel {
+ struct list_head list;
+ struct pwm_device *pwm;
+ const char *requester;
+ 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_low;
+ unsigned long period_ticks;
+ unsigned long duty_ticks;
+};
+
+struct gpio_pwm_platform_data {
+ int gpio;
+};
+
+struct pwm_channel *
+pwm_request(const char *bus_id, int chan,
+ const char *requester);
+
+void pwm_free(struct pwm_channel *pwm);
+
+int pwm_config_nosleep(struct pwm_channel *pwm,
+ struct pwm_channel_config *c);
+
+int pwm_config(struct pwm_channel *pwm,
+ struct pwm_channel_config *c);
+
+unsigned long pwm_ns_to_ticks(struct pwm_channel *pwm,
+ unsigned long nsecs);
+
+unsigned long pwm_ticks_to_ns(struct pwm_channel *pwm,
+ unsigned long ticks);
+
+int pwm_set_period_ns(struct pwm_channel *pwm,
+ unsigned long period_ns);
+
+unsigned long int pwm_get_period_ns(struct pwm_channel *pwm);
+
+int pwm_set_duty_ns(struct pwm_channel *pwm,
+ unsigned long duty_ns);
+
+int pwm_set_duty_percent(struct pwm_channel *pwm,
+ int percent);
+
+unsigned long pwm_get_duty_ns(struct pwm_channel *pwm);
+
+int pwm_set_polarity(struct pwm_channel *pwm,
+ int active_high);
+
+int pwm_start(struct pwm_channel *pwm);
+
+int pwm_stop(struct pwm_channel *pwm);
+
+int pwm_set_handler(struct pwm_channel *pwm,
+ pwm_handler_t handler,
+ void *data);
+
+int pwm_synchronize(struct pwm_channel *p,
+ struct pwm_channel *to_p);
+
+
+int pwm_unsynchronize(struct pwm_channel *p,
+ struct pwm_channel *from_p);
+
+
+#endif /* __LINUX_PWM_H */
--
1.6.5
diff --git a/drivers/leds/leds-pwm.c b/drivers/leds/leds-pwm.c
index 88b1dd0..7877af8 100644
--- a/drivers/leds/leds-pwm.c
+++ b/drivers/leds/leds-pwm.c
@@ -1,152 +1,176 @@
/*
- * linux/drivers/leds-pwm.c
+ * drivers/leds/leds-pwm.c
*
- * simple PWM based LED control
+ * Copyright (C) 2010 Bill Gatliff <bg...@billgatliff.com>
+ * Copyright (C) 2009 Loutao Fu, Pengutronix <l....@pengutronix.de>
*
- * Copyright 2009 Luotao Fu @ Pengutronix (l....@pengutronix.de)
+ * Based on leds-gpio.c by Raphael Assenat <ra...@8d.com>
*
- * based on leds-gpio.c by Raphael Assenat <ra...@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>
-
-struct led_pwm_data {
- struct led_classdev cdev;
- struct pwm_device *pwm;
- unsigned int active_low;
- unsigned int period;
+#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 *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_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);
+ 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;
+ struct device *d = &pdev->dev;
+ 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_free(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);
+ struct device *d = &pdev->dev;
- 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_free(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....@pengutronix.de>");
-MODULE_DESCRIPTION("PWM LED driver for PXA");
+MODULE_AUTHOR("Bill Gatliff <bg...@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..92363c8
--- /dev/null
+++ b/include/linux/pwm/pwm-led.h
@@ -0,0 +1,34 @@
+#ifndef __LINUX_PWM_LED_H
+#define __LINUX_PWM_LED_H
+
+/*
+ * include/linux/pwm-led.h
+ *
+ * Copyright (C) 2008 Bill Gatliff
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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
+ */
+
+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 */
diff --git a/drivers/leds/ledtrig-dim.c b/drivers/leds/ledtrig-dim.c
new file mode 100644
index 0000000..299865b
--- /dev/null
+++ b/drivers/leds/ledtrig-dim.c
@@ -0,0 +1,95 @@
+/*
+ * LED Dim Trigger
+ *
+ * Copyright (C) 2008 Bill Gatliff <bg...@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/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 <bg...@billgatliff.com>");
+MODULE_DESCRIPTION("Dim LED trigger");
+MODULE_LICENSE("GPL");
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 049ff24..8cf48cf 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -5,7 +5,11 @@
obj-$(CONFIG_IBM_ASM) += ibmasm/
obj-$(CONFIG_HDPU_FEATURES) += hdpuftrs/
obj-$(CONFIG_AD525X_DPOT) += ad525x_dpot.o
-obj-$(CONFIG_ATMEL_PWM) += atmel_pwm.o
+obj-$(CONFIG_ASUS_LAPTOP) += asus-laptop.o
+obj-$(CONFIG_EEEPC_LAPTOP) += eeepc-laptop.o
+obj-$(CONFIG_MSI_LAPTOP) += msi-laptop.o
+obj-$(CONFIG_COMPAL_LAPTOP) += compal-laptop.o
+obj-$(CONFIG_ACER_WMI) += acer-wmi.o
obj-$(CONFIG_ATMEL_SSC) += atmel-ssc.o
obj-$(CONFIG_ATMEL_TCLIB) += atmel_tclib.o
obj-$(CONFIG_ICS932S401) += ics932s401.o
diff --git a/drivers/misc/atmel_pwm.c b/drivers/misc/atmel_pwm.c
deleted file mode 100644
index 6aa5294..0000000
--- a/drivers/misc/atmel_pwm.c
+++ /dev/null
@@ -1,409 +0,0 @@
-#include <linux/module.h>
-#include <linux/clk.h>
-#include <linux/err.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..a2a08c5
--- /dev/null
+++ b/drivers/pwm/atmel-pwm.c
@@ -0,0 +1,589 @@
+/*
+ * drivers/pwm/atmel-pwm.c
+ *
+ * Copyright (C) 2010 Bill Gatliff <bg...@billgatliff.com>
+ * Copyright (C) 2007 David Brownell
+ *
+ * This program is free software; you may redistribute and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/clk.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/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);
+
+ dev_dbg(p->pwm->dev, "polarity %d\n", c->polarity);
+ return 0;
+}
+
+static inline int
+__atmel_pwm_config_duty_ticks(struct pwm_channel *p,
+ struct pwm_channel_config *c)
+{
+ u32 cmr, cprd, cpre, cdty;
+
+ cmr = pwmc_chan_readl(p, PWMC_CMR);
+ cprd = pwmc_chan_readl(p, PWMC_CPRD);
+
+ cpre = cmr & PWMC_CMR_CPRE_MASK;
+ cmr &= ~BIT(PWMC_CMR_CPD);
+
+ cdty = cprd - (c->duty_ticks >> cpre);
+
+ p->duty_ticks = c->duty_ticks;
+
+ if (__atmel_pwm_is_on(p)) {
+ pwmc_chan_writel(p, PWMC_CMR, cmr);
+ pwmc_chan_writel(p, PWMC_CUPD, cdty);
+ } else
+ pwmc_chan_writel(p, PWMC_CDTY, cdty);
+
+ dev_dbg(p->pwm->dev, "duty_ticks = %lu cprd = %x"
+ " cdty = %x cpre = %x\n", p->duty_ticks,
+ cprd, cdty, cpre);
+
+ return 0;
+}
+
+static inline int
+__atmel_pwm_config_period_ticks(struct pwm_channel *p,
+ struct pwm_channel_config *c)
+{
+ u32 cmr, cprd, cpre;
+
+ cpre = fls(c->period_ticks);
+ if (cpre < 16)
+ cpre = 0;
+ else {
+ cpre -= 15;
+ if (cpre > 10)
+ return -EINVAL;
+ }
+
+ cmr = pwmc_chan_readl(p, PWMC_CMR);
+ cmr &= ~PWMC_CMR_CPRE_MASK;
+ cmr |= cpre;
+
+ cprd = c->period_ticks >> cpre;
+
+ pwmc_chan_writel(p, PWMC_CMR, cmr);
+ pwmc_chan_writel(p, PWMC_CPRD, cprd);
+ p->period_ticks = c->period_ticks;
+
+ dev_dbg(p->pwm->dev, "period_ticks = %lu cprd = %x cpre = %x\n",
+ p->period_ticks, cprd, cpre);
+
+ return 0;
+}
+
+static int
+atmel_pwm_config_nosleep(struct pwm_channel *p,
+ struct pwm_channel_config *c)
+{
+ int ret = 0;
+ unsigned long flags;
+
+ spin_lock_irqsave(&p->lock, flags);
+
+ switch (c->config_mask) {
+
+ case PWM_CONFIG_DUTY_TICKS:
+ __atmel_pwm_config_duty_ticks(p, c);
+ break;
+
+ case PWM_CONFIG_STOP:
+ __atmel_pwm_stop(p);
+ break;
+
+ case PWM_CONFIG_START:
+ __atmel_pwm_start(p);
+ break;
+
+ case PWM_CONFIG_POLARITY:
+ __atmel_pwm_config_polarity(p, c);
+ break;
+
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ spin_unlock_irqrestore(&p->lock, flags);
+ return ret;
+}
+
+static int
+atmel_pwm_stop_sync(struct pwm_channel *p)
+{
+ struct atmel_pwm *ap = container_of(p->pwm, struct atmel_pwm, pwm);
+ int ret;
+ int was_on = __atmel_pwm_is_on(p);
+
+ if (was_on) {
+ do {
+ init_completion(&p->complete);
+ set_bit(FLAG_STOP, &p->flags);
+ pwmc_writel(ap, PWMC_IER, 1 << p->chan);
+
+ dev_dbg(p->pwm->dev, "waiting on stop_sync completion...\n");
+
+ ret = wait_for_completion_interruptible(&p->complete);
+
+ dev_dbg(p->pwm->dev, "stop_sync complete (%d)\n", ret);
+
+ if (ret)
+ return ret;
+ } while (p->flags & BIT(FLAG_STOP));
+ }
+
+ return was_on;
+}
+
+static int
+atmel_pwm_config(struct pwm_channel *p,
+ struct pwm_channel_config *c)
+{
+ int was_on = 0;
+
+ if (p->pwm->config_nosleep) {
+ if (!p->pwm->config_nosleep(p, c))
+ return 0;
+ }
+
+ might_sleep();
+
+ dev_dbg(p->pwm->dev, "config_mask %x\n", c->config_mask);
+
+ was_on = atmel_pwm_stop_sync(p);
+ if (was_on < 0)
+ return was_on;
+
+ if (c->config_mask & PWM_CONFIG_PERIOD_TICKS) {
+ __atmel_pwm_config_period_ticks(p, c);
+ if (!(c->config_mask & PWM_CONFIG_DUTY_TICKS)) {
+ struct pwm_channel_config d = {
+ .config_mask = PWM_CONFIG_DUTY_TICKS,
+ .duty_ticks = p->duty_ticks,
+ };
+ __atmel_pwm_config_duty_ticks(p, &d);
+ }
+ }
+
+ if (c->config_mask & PWM_CONFIG_DUTY_TICKS)
+ __atmel_pwm_config_duty_ticks(p, c);
+
+ if (c->config_mask & PWM_CONFIG_POLARITY)
+ __atmel_pwm_config_polarity(p, c);
+
+ if ((c->config_mask & PWM_CONFIG_START)
+ || (was_on && !(c->config_mask & PWM_CONFIG_STOP)))
+ __atmel_pwm_start(p);
+
+ return 0;
+}
+
+static void
+__atmel_pwm_set_callback(struct pwm_channel *p,
+ pwm_callback_t callback)
+{
+ struct atmel_pwm *ap = container_of(p->pwm, struct atmel_pwm, pwm);
+
+ p->callback = callback;
+ pwmc_writel(ap, p->callback ? PWMC_IER : PWMC_IDR, 1 << p->chan);
+}
+
+static int
+atmel_pwm_set_callback(struct pwm_channel *p,
+ pwm_callback_t callback)
+{
+ struct atmel_pwm *ap = to_atmel_pwm(p);
+ unsigned long flags;
+
+ spin_lock_irqsave(&ap->lock, flags);
+ __atmel_pwm_set_callback(p, callback);
+ spin_unlock_irqrestore(&ap->lock, flags);
+
+ return 0;
+}
+
+static int
+atmel_pwm_request(struct pwm_channel *p)
+{
+ struct atmel_pwm *ap = to_atmel_pwm(p);
+ unsigned long flags;
+
+ spin_lock_irqsave(&p->lock, flags);
+ clk_enable(ap->clk);
+ p->tick_hz = clk_get_rate(ap->clk);
+ __atmel_pwm_unsynchronize(p, NULL);
+ __atmel_pwm_stop(p);
+ spin_unlock_irqrestore(&p->lock, flags);
+
+ return 0;
+}
+
+static void
+atmel_pwm_free(struct pwm_channel *p)
+{
+ struct atmel_pwm *ap = to_atmel_pwm(p);
+ clk_disable(ap->clk);
+}
+
+static irqreturn_t
+atmel_pwmc_irq(int irq, void *data)
+{
+ struct atmel_pwm *ap = data;
+ struct pwm_channel *p;
+ u32 isr;
+ int chid;
+ unsigned long flags;
+
+ spin_lock_irqsave(&ap->lock, flags);
+
+ isr = pwmc_readl(ap, PWMC_ISR);
+ for (chid = 0; isr; chid++, isr >>= 1) {
+ p = &ap->pwm.channels[chid];
+ if (isr & 1) {
+ if (p->callback)
+ p->callback(p);
+ if (p->flags & BIT(FLAG_STOP)) {
+ __atmel_pwm_stop(p);
+ clear_bit(FLAG_STOP, &p->flags);
+ }
+ complete_all(&p->complete);
+ }
+ }
+
+ spin_unlock_irqrestore(&ap->lock, flags);
+
+ return IRQ_HANDLED;
+}
+
+static int __devinit
+atmel_pwmc_probe(struct platform_device *pdev)
+{
+ struct atmel_pwm *ap;
+ struct resource *r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ int ret = 0;
+
+ ap = kzalloc(sizeof(*ap), GFP_KERNEL);
+ if (!ap) {
+ ret = -ENOMEM;
+ goto err_atmel_pwm_alloc;
+ }
+
+ spin_lock_init(&ap->lock);
+ platform_set_drvdata(pdev, ap);
+
+ ap->pwm.bus_id = dev_name(&pdev->dev);
+
+ ap->pwm.nchan = 4; /* TODO: true only for SAM9263 and AP7000 */
+ ap->ccnt_mask = 0xffffUL; /* TODO: true only for SAM9263 */
+
+ ap->sync_mask = kzalloc(ap->pwm.nchan * sizeof(u32), GFP_KERNEL);
+ if (!ap->sync_mask) {
+ ret = -ENOMEM;
+ goto err_alloc_sync_masks;
+ }
+
+ ap->pwm.owner = THIS_MODULE;
+ ap->pwm.request = atmel_pwm_request;
+ ap->pwm.free = atmel_pwm_free;
+ ap->pwm.config_nosleep = atmel_pwm_config_nosleep;
+ ap->pwm.config = atmel_pwm_config;
+ ap->pwm.synchronize = atmel_pwm_synchronize;
+ ap->pwm.unsynchronize = atmel_pwm_unsynchronize;
+ ap->pwm.set_callback = atmel_pwm_set_callback;
+
+ ap->clk = clk_get(&pdev->dev, "pwm_clk");
+ if (IS_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 <bg...@billgatliff.com>");
+MODULE_DESCRIPTION("Driver for Atmel PWMC peripheral");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:atmel_pwmc");
diff --git a/drivers/pwm/gpio.c b/drivers/pwm/gpio.c
new file mode 100644
index 0000000..552ad1a
--- /dev/null
+++ b/drivers/pwm/gpio.c
@@ -0,0 +1,307 @@
+/*
+ * drivers/pwm/gpio.c
+ *
+ * Models a single-channel PWM device using a kernel interval timer
+ * and a GPIO pin.
+ *
+ * Copyright (C) 2010 Bill Gatliff <bg...@billgatliff.com>
+ *
+ * This program is free software; you may redistribute and/or modify
+ * it under the terms of the GNU General Public License Version 2, as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/hrtimer.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/workqueue.h>
+#include <mach/gpio.h>
+#include <linux/pwm/pwm.h>
+
+struct gpio_pwm {
+ struct pwm_device pwm;
+ struct hrtimer t;
+ 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, t);
+
+ 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)
+ hrtimer_start(&gp->t,
+ ktime_set(0, gp->pwm.channels[0].duty_ticks),
+ HRTIMER_MODE_REL);
+ else
+ hrtimer_start(&gp->t,
+ ktime_set(0,gp->pwm.channels[0].period_ticks
+ - gp->pwm.channels[0].duty_ticks),
+ 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->t);
+ pr_debug("%s:%d start, %lu ticks\n",
+ dev_name(p->pwm->dev), p->chan, p->duty_ticks);
+}
+
+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;
+ dev_dbg(p->pwm->dev, ":%d duty_ticks %lu\n",
+ p->chan, p->duty_ticks);
+ break;
+
+ case PWM_CONFIG_START:
+ if (!hrtimer_active(&gp->t)) {
+ 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->t);
+
+ if (was_on) {
+ do {
+ init_completion(&p->complete);
+ set_bit(FLAG_STOP, &p->flags);
+
+ dev_dbg(p->pwm->dev, ":%d waiting on stop_sync completion...\n",
+ p->chan);
+
+ ret = wait_for_completion_interruptible(&p->complete);
+
+ dev_dbg(p->pwm->dev, ":%d stop_sync complete (%d)\n",
+ p->chan, ret);
+
+ if (ret)
+ return ret;
+ } while (p->flags & BIT(FLAG_STOP));
+ }
+
+ dev_dbg(p->pwm->dev, ":%d stop_sync returning %d\n",
+ p->chan, was_on);
+
+ 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();
+ dev_dbg(p->pwm->dev, ":%d config_mask %x\n",
+ p->chan, c->config_mask);
+
+ 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;
+
+ 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;
+
+ 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.bus_id = dev_name(&pdev->dev);
+ gp->pwm.nchan = 1;
+ gp->gpio = gpd->gpio;
+
+ INIT_WORK(&gp->work, gpio_pwm_work);
+
+ hrtimer_init(&gp->t, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ gp->t.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:
+ 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->t);
+ cancel_work_sync(&gp->work);
+ 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 <bg...@billgatliff.com>");
+MODULE_DESCRIPTION("PWM output using GPIO and a high-resolution timer");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:gpio_pwm");
A couple quick comments, more after a better review.
[snip]
> diff --git a/drivers/misc/atmel_pwm.c b/drivers/misc/atmel_pwm.c
> deleted file mode 100644
> index 6aa5294..0000000
> --- a/drivers/misc/atmel_pwm.c
> +++ /dev/null
[snip]
> -static struct platform_driver atmel_pwm_driver = {
> - .driver = {
> - .name = "atmel_pwm",
> - .owner = THIS_MODULE,
> - },
[snip]
> -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..a2a08c5
> --- /dev/null
> +++ b/drivers/pwm/atmel-pwm.c
[snip]
> +static struct platform_driver atmel_pwm_driver = {
> + .driver = {
> + .name = "atmel_pwmc",
> + .owner = THIS_MODULE,
> + },
[snip]
> +MODULE_AUTHOR("Bill Gatliff <bg...@billgatliff.com>");
> +MODULE_DESCRIPTION("Driver for Atmel PWMC peripheral");
> +MODULE_LICENSE("GPL");
> +MODULE_ALIAS("platform:atmel_pwmc");
You have a couple name changes here, were they intentional?
Filename: atmel_pwm.c -> atmel-pwm.c
Driver name: atmel_pwm -> atmel_pwmc
Module alias: platform:atmel_pwm -> platform:atmel_pwmc
Also, will this new driver still work for all the existing users of
the old one? Are there any platform changes that need to be done?
Regards,
Hartley
A couple quick comments, more after a better review.
[snip]
> diff --git a/drivers/pwm/pwm.c b/drivers/pwm/pwm.c
> new file mode 100644
> index 0000000..f369384
> --- /dev/null
> +++ b/drivers/pwm/pwm.c
[snip]
> +int pwm_unregister(struct pwm_device *pwm)
> +{
[snip]
> + for (wchan = 0; wchan < pwm->nchan; wchan++) {
> + if (pwm->channels[wchan].flags & FLAG_REQUESTED) {
Shouldn't this be:
& BIT(FLAG_REQUESTED)
[snip]
> diff --git a/include/linux/pwm/pwm.h b/include/linux/pwm/pwm.h
> new file mode 100644
> index 0000000..848cd76
> --- /dev/null
> +++ b/include/linux/pwm/pwm.h
> @@ -0,0 +1,170 @@
> +#ifndef __LINUX_PWM_H
> +#define __LINUX_PWM_H
Nitpick... Can you move the #ifndef/#define to after the comment?
Regards,
Hartley
Yes. In the datasheet, the peripheral is called the "PWMC" and so
that's why the driver name changed. For consistency, I guess I should
have named the file atmel-pwmc.c as well. Finally, I find it easier to
type file names with dashes rather than underscores. :)
> Also, will this new driver still work for all the existing users of
> the old one? Are there any platform changes that need to be done?
>
There are platform changes necessary, to register and use the devices.
The driver API is different from the previous code, so there are changes
there as well. Functionally, however, the new stuff is a superset of
what the previous driver provided.
b.g.
--
Bill Gatliff
bg...@billgatliff.com
Yes. I've fixed that one several times now! I think I need to review
my git-fu, especially rebasing.
> Nitpick... Can you move the #ifndef/#define to after the comment?
>
I did that in all the others, I guess I missed this one...
b.g.
--
Bill Gatliff
bg...@billgatliff.com
[snip]
> diff --git a/drivers/pwm/pwm.c b/drivers/pwm/pwm.c
> new file mode 100644
> index 0000000..f369384
> --- /dev/null
> +++ b/drivers/pwm/pwm.c
[snip]
Bill,
I'm trying to test your latest pwm patches using the ep93xx pwm driver
I wrote based on your previous patches. I'm getting an Oops when booting
this that is hanging my system. I put in the printascii hack to see
what's going on and got this:
<1>Unable to handle kernel NULL pointer dereference at virtual address 00000044
<1>pgd = c0004000
<1>[00000044] *pgd=00000000
<0>Internal error: Oops: 5 [#1] PREEMPT
<0>last sysfs file:
<d>Modules linked in:
CPU: 0 Not tainted (2.6.32.7 #5)
PC is at dev_driver_string+0x0/0x38
LR is at pwm_register+0x17c/0x1e8
pc : [<c01b1b10>] lr : [<c0187dfc>] psr: 60000013
sp : c581bee0 ip : c036efa5 fp : 00000000
r10: c4511e60 r9 : 00000000 r8 : 00000000
r7 : c036ef6c r6 : 00000050 r5 : c44c9b20 r4 : 00000001
r3 : 00000001 r2 : 00000001 r1 : 00000000 r0 : 00000000
Flags: nZCv IRQs on FIQs on Mode SVC_32 ISA ARM Segment kernel
Control: c000717f Table: c0004000 DAC: 00000017
<0>Process swapper (pid: 1, stack limit = 0xc581a270)
<0>Stack: (0xc581bee0 to 0xc581c000)
<0>bee0: c036ef6c c58449a0 00000000 c44c9b20 00000000 c44c9b20 00000000 c03bece8
<0>bf00: c58839c0 c03bece0 00000000 c0016784 00000000 00000000 c03bece8 c03bece8
<0>bf20: c03d105c c03d105c c03d4c40 c01b5e70 c03d105c c01b4f08 c0054498 c03bece8
<0>bf40: c03bed1c c03d105c c581bf60 c01b5038 00000000 c01b4fdc c03d105c c01b473c
<0>bf60: c58034d8 c5852dd0 c03d4c40 c00211c4 c03d105c c03d105c c4511ec0 c01b4028
<0>bf80: c0351a5d c581bf90 c01756c4 c00211c4 c002133c c03d105c c001666c 00000001
<0>bfa0: 00000000 c01b533c c00211c4 c002133c 00000000 c001666c 00000001 c00273a8
<0>bfc0: 00000000 00000078 c03c2aa0 00000000 c00211c4 c002133c 00000000 00000000
<0>bfe0: 00000000 c0008588 00000000 00000000 00000000 c00289d4 33cc33cc 33cc33cc
[<c01b1b10>] (dev_driver_string+0x0/0x38) from [<c0187dfc>] (pwm_register+0x17c/0x1e8)
[<c0187dfc>] (pwm_register+0x17c/0x1e8) from [<c0016784>] (ep93xx_pwm_probe+0x10c/0x17c)
[<c0016784>] (ep93xx_pwm_probe+0x10c/0x17c) from [<c01b5e70>] (platform_drv_probe+0x1c/0x24)
[<c01b5e70>] (platform_drv_probe+0x1c/0x24) from [<c01b4f08>] (driver_probe_device+0xac/0x180)
[<c01b4f08>] (driver_probe_device+0xac/0x180) from [<c01b5038>] (__driver_attach+0x5c/0x7c)
[<c01b5038>] (__driver_attach+0x5c/0x7c) from [<c01b473c>] (bus_for_each_dev+0x50/0x90)
[<c01b473c>] (bus_for_each_dev+0x50/0x90) from [<c01b4028>] (bus_add_driver+0xa0/0x224)
[<c01b4028>] (bus_add_driver+0xa0/0x224) from [<c01b533c>] (driver_register+0xbc/0x14c)
[<c01b533c>] (driver_register+0xbc/0x14c) from [<c00273a8>] (do_one_initcall+0x60/0x1c0)
[<c00273a8>] (do_one_initcall+0x60/0x1c0) from [<c0008588>] (kernel_init+0x9c/0x114)
[<c0008588>] (kernel_init+0x9c/0x114) from [<c00289d4>] (kernel_thread_exit+0x0/0x8)
<0>Code: c03c5c28 000080d0 c03d49c8 c03d4958 (e5903044)
<4>---[ end trace da227214a82491b7 ]---
<0>Kernel panic - not syncing: Attempted to kill init!
[<c002c7dc>] (unwind_backtrace+0x0/0xdc) from [<c02d7050>] (panic+0x3c/0x128)
[<c02d7050>] (panic+0x3c/0x128) from [<c003ee2c>] (do_exit+0x60/0x608)
[<c003ee2c>] (do_exit+0x60/0x608) from [<c002b5f8>] (die+0x178/0x19c)
[<c002b5f8>] (die+0x178/0x19c) from [<c002db1c>] (__do_kernel_fault+0x68/0x80)
[<c002db1c>] (__do_kernel_fault+0x68/0x80) from [<c002dcf8>] (do_page_fault+0x1c4/0x1dc)
[<c002dcf8>] (do_page_fault+0x1c4/0x1dc) from [<c00272e8>] (do_DataAbort+0x34/0x94)
[<c00272e8>] (do_DataAbort+0x34/0x94) from [<c0027ae0>] (__dabt_svc+0x40/0x60)
Exception stack(0xc581be98 to 0xc581bee0)
be80: 00000000 00000000
bea0: 00000001 00000001 00000001 c44c9b20 00000050 c036ef6c 00000000 00000000
bec0: c4511e60 00000000 c036efa5 c581bee0 c0187dfc c01b1b10 60000013 ffffffff
[<c0027ae0>] (__dabt_svc+0x40/0x60) from [<c01b1b10>] (dev_driver_string+0x0/0x38)
[<c01b1b10>] (dev_driver_string+0x0/0x38) from [<c0187dfc>] (pwm_register+0x17c/0x1e8)
[<c0187dfc>] (pwm_register+0x17c/0x1e8) from [<c0016784>] (ep93xx_pwm_probe+0x10c/0x17c)
[<c0016784>] (ep93xx_pwm_probe+0x10c/0x17c) from [<c01b5e70>] (platform_drv_probe+0x1c/0x24)
[<c01b5e70>] (platform_drv_probe+0x1c/0x24) from [<c01b4f08>] (driver_probe_device+0xac/0x180)
[<c01b4f08>] (driver_probe_device+0xac/0x180) from [<c01b5038>] (__driver_attach+0x5c/0x7c)
[<c01b5038>] (__driver_attach+0x5c/0x7c) from [<c01b473c>] (bus_for_each_dev+0x50/0x90)
[<c01b473c>] (bus_for_each_dev+0x50/0x90) from [<c01b4028>] (bus_add_driver+0xa0/0x224)
[<c01b4028>] (bus_add_driver+0xa0/0x224) from [<c01b533c>] (driver_register+0xbc/0x14c)
[<c01b533c>] (driver_register+0xbc/0x14c) from [<c00273a8>] (do_one_initcall+0x60/0x1c0)
[<c00273a8>] (do_one_initcall+0x60/0x1c0) from [<c0008588>] (kernel_init+0x9c/0x114)
[<c0008588>] (kernel_init+0x9c/0x114) from [<c00289d4>] (kernel_thread_exit+0x0/0x8)
Your older patch just used printk's to output the messages. I think the
Oops is being caused by the dev_<level>(pwm->dev, ...). Where is pwm->dev
being setup?
Regards,
Hartley
Good question! Makes me think that it isn't...
I'm traveling back to my office at the moment, should be back in late
tomorrow or Friday a.m. I'll look at this then.
The intention was to save the result from device_create() called from
__pwm_create_sysfs(), but I'm suspicious that the patch I posted doesn't
have that code for some reason. My local git repo here doesn't seem to
have it. Hmmm...
b.g.
--
Bill Gatliff
bg...@billgatliff.com
No, that's not right. Disregard the above.
If the PWM device in question is already known to the kernel, then
before you call pwm_register() you must set pwm->dev to the address of
that device structure. I need to come up with a better way to handle this.
If the PWM device isn't already known to the kernel as a true struct
device, then I don't yet have a Plan B. Drat. :(
diff --git a/Documentation/pwm.txt b/Documentation/pwm.txt
new file mode 100644
diff --git a/drivers/pwm/pwm.c b/drivers/pwm/pwm.c
new file mode 100644
index 0000000..40378cb
--- /dev/null
+++ b/drivers/pwm/pwm.c
@@ -0,0 +1,644 @@
+/*
+ * drivers/pwm/pwm.c
+ *
+ * Copyright (C) 2010 Bill Gatliff <bg...@billgatliff.com>
+ *
+ * This program is free software; you may redistribute and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/spinlock.h>
+#include <linux/fs.h>
+#include <linux/completion.h>
+#include <linux/workqueue.h>
+#include <linux/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)
+ {
+ pr_err("%s: ENOMEM\n", __func__);
+ 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);
+ pr_err("%s: err_create_sysfs\n", __func__);
+ goto err_create_sysfs;
+ }
+
+ mutex_unlock(&device_list_mutex);
+
+ dev_info(pwm->dev, "%d channel%s\n", pwm->nchan,
+ pwm->nchan > 1 ? "s" : "");
+ return 0;
+
+err_create_sysfs:
+ kfree(p);
+
+ return ret;
+}
+EXPORT_SYMBOL(pwm_register);
+
+static int __match_device(struct device *dev, void *data)
+{
+ return dev_get_drvdata(dev) == data;
+}
+
+int pwm_unregister(struct pwm_device *pwm)
+{
+ int wchan;
+ struct device *dev;
+
+ mutex_lock(&device_list_mutex);
+
+ for (wchan = 0; wchan < pwm->nchan; wchan++) {
+ if (pwm->channels[wchan].flags & BIT(FLAG_REQUESTED)) {
+ mutex_unlock(&device_list_mutex);
+ return -EBUSY;
+ }
+ }
+
+ for (wchan = 0; wchan < pwm->nchan; wchan++) {
+ dev = class_find_device(&pwm_class, NULL,
+ &pwm->channels[wchan],
+ __match_device);
+ if (dev) {
+ put_device(dev);
+ device_unregister(dev);
+ }
+ }
+
+ kfree(pwm->channels);
+ list_del(&pwm->list);
+ mutex_unlock(&device_list_mutex);
+
+ return 0;
+}
+EXPORT_SYMBOL(pwm_unregister);
+
+static struct pwm_device *
+__pwm_find_device(const char *bus_id)
+{
+ struct pwm_device *p;
+
+ list_for_each_entry(p, &pwm_device_list, list) {
+ if (!strcmp(bus_id, p->bus_id))
+ return p;
+ }
+ return NULL;
+}
+
+static int
+__pwm_request_channel(struct pwm_channel *p,
+ const char *requester)
+{
+ int ret;
+
+ if (test_and_set_bit(FLAG_REQUESTED, &p->flags))
+ return -EBUSY;
+
+ if (p->pwm->request) {
+ ret = p->pwm->request(p);
+ if (ret) {
+ clear_bit(FLAG_REQUESTED, &p->flags);
+ return ret;
+ }
+ }
+
+ p->requester = requester;
+ if (!strcmp(requester, REQUEST_SYSFS))
+ p->pid = current->pid;
+
+ return 0;
+}
+
+struct pwm_channel *
+pwm_request(const char *bus_id,
+ int chan,
+ const char *requester)
+{
+ struct pwm_device *p;
+ int ret;
+
+ mutex_lock(&device_list_mutex);
+
+ p = __pwm_find_device(bus_id);
+ if (!p || chan >= p->nchan)
+ goto err_no_device;
+
+ if (!try_module_get(p->owner))
+ goto err_module_get_failed;
+
+ ret = __pwm_request_channel(&p->channels[chan], requester);
+ if (ret)
+ goto err_request_failed;
+
+ mutex_unlock(&device_list_mutex);
+ return &p->channels[chan];
+
+err_request_failed:
+ module_put(p->owner);
+err_module_get_failed:
+err_no_device:
+ mutex_unlock(&device_list_mutex);
+ return NULL;
+}
+EXPORT_SYMBOL(pwm_request);
+
+void pwm_free(struct pwm_channel *p)
+{
+ mutex_lock(&device_list_mutex);
+
+ if (!test_and_clear_bit(FLAG_REQUESTED, &p->flags))
+ goto done;
+
+ pwm_stop(p);
+ pwm_unsynchronize(p, NULL);
+ pwm_set_handler(p, NULL, NULL);
+
+ return 0;
+
+ ns = ticks;
+ ns *= 1000000000UL;
+ do_div(ns, p->tick_hz);
+ return ns;
+}
+EXPORT_SYMBOL(pwm_ticks_to_ns);
+
+static void
+pwm_config_ns_to_ticks(struct pwm_channel *p,
+ struct pwm_channel_config *c)
+{
+ if (c->config_mask & PWM_CONFIG_PERIOD_NS) {
+ c->period_ticks = pwm_ns_to_ticks(p, c->period_ns);
+ c->config_mask &= ~PWM_CONFIG_PERIOD_NS;
+ c->config_mask |= PWM_CONFIG_PERIOD_TICKS;
+ }
+
+ if (c->config_mask & PWM_CONFIG_DUTY_NS) {
+ c->duty_ticks = pwm_ns_to_ticks(p, c->duty_ns);
+ c->config_mask &= ~PWM_CONFIG_DUTY_NS;
+ c->config_mask |= PWM_CONFIG_DUTY_TICKS;
+ }
+}
+
+static void
+pwm_config_percent_to_ticks(struct pwm_channel *p,
+ struct pwm_channel_config *c)
+{
+ if (c->config_mask & PWM_CONFIG_DUTY_PERCENT) {
+ if (c->config_mask & PWM_CONFIG_PERIOD_TICKS)
+ c->duty_ticks = c->period_ticks;
+ else
+ c->duty_ticks = p->period_ticks;
+
+ c->duty_ticks *= c->duty_percent;
+ c->duty_ticks /= 100;
+ c->config_mask &= ~PWM_CONFIG_DUTY_PERCENT;
+ c->config_mask |= PWM_CONFIG_DUTY_TICKS;
+ }
+}
+
+int pwm_config_nosleep(struct pwm_channel *p,
+ struct pwm_channel_config *c)
+{
+ if (!p->pwm->config_nosleep)
+ return -EINVAL;
+
+ pwm_config_ns_to_ticks(p, c);
+ pwm_config_percent_to_ticks(p, c);
+
+ return p->pwm->config_nosleep(p, c);
+}
+EXPORT_SYMBOL(pwm_config_nosleep);
+
+int pwm_config(struct pwm_channel *p,
+ struct pwm_channel_config *c)
+{
+ int ret = 0;
+
+ if (unlikely(!p->pwm->config))
+ return -EINVAL;
+
+ pwm_config_ns_to_ticks(p, c);
+ pwm_config_percent_to_ticks(p, c);
+
+ switch (c->config_mask & (PWM_CONFIG_PERIOD_TICKS
+ | PWM_CONFIG_DUTY_TICKS)) {
+ case PWM_CONFIG_PERIOD_TICKS:
+ if (p->duty_ticks > c->period_ticks) {
+ ret = -EINVAL;
+ goto err;
+ }
+ break;
+ case PWM_CONFIG_DUTY_TICKS:
+ if (p->period_ticks < c->duty_ticks) {
+ ret = -EINVAL;
+ goto err;
+ }
+ break;
+ case PWM_CONFIG_DUTY_TICKS | PWM_CONFIG_PERIOD_TICKS:
+ if (c->duty_ticks > c->period_ticks) {
+ ret = -EINVAL;
+ goto err;
+ }
+ break;
+ default:
+ break;
+ }
+
+err:
+ dev_dbg(p->pwm->dev, "%s: config_mask %d period_ticks %lu duty_ticks %lu"
+ " polarity %d duty_ns %lu period_ns %lu duty_percent %d\n",
+ __func__, c->config_mask, c->period_ticks, c->duty_ticks,
+ c->polarity, c->duty_ns, c->period_ns, c->duty_percent);
+
+ if (ret)
+ return ret;
+ return p->pwm->config(p, c);
+}
+EXPORT_SYMBOL(pwm_config);
+
+int pwm_set_period_ns(struct pwm_channel *p,
+ unsigned long period_ns)
+{
+ struct pwm_channel_config c = {
+ .config_mask = PWM_CONFIG_PERIOD_TICKS,
+ .period_ticks = pwm_ns_to_ticks(p, period_ns),
+ };
+
+ return pwm_config(p, &c);
+}
+EXPORT_SYMBOL(pwm_set_period_ns);
+
+unsigned long pwm_get_period_ns(struct pwm_channel *p)
+{
+ return pwm_ticks_to_ns(p, p->period_ticks);
+}
+EXPORT_SYMBOL(pwm_get_period_ns);
+
+int pwm_set_duty_ns(struct pwm_channel *p,
+ unsigned long duty_ns)
+{
+ struct pwm_channel_config c = {
+ .config_mask = PWM_CONFIG_DUTY_TICKS,
+ .duty_ticks = pwm_ns_to_ticks(p, duty_ns),
+ };
+ return pwm_config(p, &c);
+}
+EXPORT_SYMBOL(pwm_set_duty_ns);
+
+unsigned long pwm_get_duty_ns(struct pwm_channel *p)
+{
+int pwm_start(struct pwm_channel *p)
+{
+ struct pwm_channel_config c = {
+ .config_mask = PWM_CONFIG_START,
+ };
+ return pwm_config(p, &c);
+}
+EXPORT_SYMBOL(pwm_start);
+
+int pwm_stop(struct pwm_channel *p)
+{
+ struct pwm_channel_config c = {
+ .config_mask = PWM_CONFIG_STOP,
+ };
+ return pwm_config(p, &c);
+}
+EXPORT_SYMBOL(pwm_stop);
+
+int pwm_synchronize(struct pwm_channel *p,
+ struct pwm_channel *to_p)
+{
+ if (p->pwm != to_p->pwm) {
+ /* TODO: support cross-device synchronization */
+ return -EINVAL;
+ }
+
+ if (!p->pwm->synchronize)
+ return -EINVAL;
+
+ return p->pwm->synchronize(p, to_p);
+}
+EXPORT_SYMBOL(pwm_synchronize);
+
+int pwm_unsynchronize(struct pwm_channel *p,
+ struct pwm_channel *from_p)
+{
+ if (from_p && (p->pwm != from_p->pwm)) {
+ /* TODO: support cross-device synchronization */
+ return -EINVAL;
+ }
+
+ if (!p->pwm->unsynchronize)
+ return -EINVAL;
+
+ return p->pwm->unsynchronize(p, from_p);
+}
+EXPORT_SYMBOL(pwm_unsynchronize);
+
+static void pwm_handler(struct work_struct *w)
+{
+ struct pwm_channel *p = container_of(w, struct pwm_channel,
+ handler_work);
+ if (p->handler && p->handler(p, p->handler_data))
+ pwm_stop(p);
+}
+
+static void __pwm_callback(struct pwm_channel *p)
+{
+ queue_work(pwm_handler_workqueue, &p->handler_work);
+ dev_dbg(p->pwm->dev, "handler %p scheduled with data %p\n",
+ p->handler, p->handler_data);
+}
+
+int pwm_set_handler(struct pwm_channel *p,
+ pwm_handler_t handler,
+ void *data)
+{
+ if (p->pwm->set_callback) {
+ p->handler_data = data;
+ p->handler = handler;
+ INIT_WORK(&p->handler_work, pwm_handler);
+ return p->pwm->set_callback(p, handler ? __pwm_callback : NULL);
+ }
+ return -EINVAL;
+}
+EXPORT_SYMBOL(pwm_set_handler);
+
+static ssize_t pwm_run_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t len)
+{
+ struct pwm_channel *p = dev_get_drvdata(dev);
+ if (sysfs_streq(buf, "1"))
+ pwm_start(p);
+ else if (sysfs_streq(buf, "0"))
+ pwm_stop(p);
+ return len;
+}
+static DEVICE_ATTR(run, 0200, NULL, pwm_run_store);
+
+static ssize_t pwm_duty_ns_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct pwm_channel *p = dev_get_drvdata(dev);
+ return sprintf(buf, "%lu\n", pwm_get_duty_ns(p));
+}
+
+static ssize_t pwm_duty_ns_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t len)
+{
+ unsigned long duty_ns;
+ struct pwm_channel *p = dev_get_drvdata(dev);
+
+ if (1 == sscanf(buf, "%lu", &duty_ns))
+ pwm_set_duty_ns(p, duty_ns);
+ return len;
+}
+static DEVICE_ATTR(duty_ns, 0644, pwm_duty_ns_show, pwm_duty_ns_store);
+
+static ssize_t pwm_period_ns_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct pwm_channel *p = dev_get_drvdata(dev);
+ return sprintf(buf, "%lu\n", pwm_get_period_ns(p));
+}
+
+static ssize_t pwm_period_ns_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t len)
+{
+ unsigned long period_ns;
+ struct pwm_channel *p = dev_get_drvdata(dev);
+
+ if (1 == sscanf(buf, "%lu", &period_ns))
+ pwm_set_period_ns(p, period_ns);
+ return len;
+}
+static DEVICE_ATTR(period_ns, 0644, pwm_period_ns_show, pwm_period_ns_store);
+
+static ssize_t pwm_polarity_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct pwm_channel *p = dev_get_drvdata(dev);
+ return sprintf(buf, "%d\n", p->active_low ? 0 : 1);
+}
+
+static ssize_t pwm_polarity_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t len)
+{
+ int polarity;
+ struct pwm_channel *p = dev_get_drvdata(dev);
+
+ if (1 == sscanf(buf, "%d", &polarity))
+ pwm_set_polarity(p, polarity);
+ return len;
+}
+static DEVICE_ATTR(polarity, 0644, pwm_polarity_show, pwm_polarity_store);
+
+static ssize_t pwm_request_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct pwm_channel *p = dev_get_drvdata(dev);
+ mutex_lock(&device_list_mutex);
+ __pwm_request_channel(p, REQUEST_SYSFS);
+ mutex_unlock(&device_list_mutex);
+
+ if (p->pid)
+ return sprintf(buf, "%s %d\n", p->requester, p->pid);
+ else
+ return sprintf(buf, "%s\n", p->requester);
+}
+
+static ssize_t pwm_request_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t len)
+{
+ struct pwm_channel *p = dev_get_drvdata(dev);
+ pwm_free(p);
+ return len;
+}
+static DEVICE_ATTR(request, 0644, pwm_request_show, pwm_request_store);
+
+static const struct attribute *pwm_attrs[] =
+{
+ &dev_attr_run.attr,
+ &dev_attr_polarity.attr,
+ &dev_attr_duty_ns.attr,
+ &dev_attr_period_ns.attr,
+ &dev_attr_request.attr,
+ NULL,
+};
+
+static const struct attribute_group pwm_device_attr_group = {
+ .attrs = (struct attribute **)pwm_attrs,
+};
+
+static int __pwm_create_sysfs(struct pwm_device *pwm)
+{
+ int ret = 0;
+ struct device *dev;
+ int wchan;
+
+ for (wchan = 0; wchan < pwm->nchan; wchan++) {
+ dev = device_create(&pwm_class, pwm->dev, MKDEV(0, 0),
+ pwm->channels + wchan,
+ "%s:%d", pwm->bus_id, wchan);
+ if (!dev)
+ goto err_dev_create;
+ ret = sysfs_create_group(&dev->kobj, &pwm_device_attr_group);
+ if (ret)
+ goto err_dev_create;
+ }
+
+ return ret;
+
+err_dev_create:
+ for (wchan = 0; wchan < pwm->nchan; wchan++) {
+ dev = class_find_device(&pwm_class, NULL,
+ &pwm->channels[wchan],
+ __match_device);
+ if (dev) {
+ put_device(dev);
+ device_unregister(dev);
+ }
+ }
+
+ return ret;
+}
+
+static struct class_attribute pwm_class_attrs[] = {
+ __ATTR_NULL,
+};
+
+static struct class pwm_class = {
+ .name = "pwm",
+ .owner = THIS_MODULE,
+
+ .class_attrs = pwm_class_attrs,
+};
+
+static int __init pwm_init(void)
+{
+ int ret;
+
+ /* TODO: how to deal with devices that register very early? */
+ pr_err("%s\n", __func__);
+ ret = class_register(&pwm_class);
+ if (ret < 0)
+ return ret;
+
+ pwm_handler_workqueue = create_workqueue("pwmd");
+
+ return 0;
+}
+postcore_initcall(pwm_init);
diff --git a/include/linux/pwm.h b/include/linux/pwm.h
deleted file mode 100644
new file mode 100644
index 0000000..ec40774
--- /dev/null
+++ b/include/linux/pwm/pwm.h
@@ -0,0 +1,170 @@
+/*
+ * include/linux/pwm.h
+ *
+ * Copyright (C) 2008 Bill Gatliff < bg...@billgatliff.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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
+
+ const char *bus_id;
+ int nchan;
+
+ int (*request) (struct pwm_channel *p);
+ void (*free) (struct pwm_channel *p);
+ int (*config) (struct pwm_channel *p,
+ struct pwm_channel_config *c);
+ int (*config_nosleep)(struct pwm_channel *p,
+ struct pwm_channel_config *c);
+ int (*synchronize) (struct pwm_channel *p,
+ struct pwm_channel *to_p);
+ int (*unsynchronize)(struct pwm_channel *p,
+ struct pwm_channel *from_p);
+ int (*set_callback) (struct pwm_channel *p,
+ pwm_callback_t callback);
+};
+
+int pwm_register(struct pwm_device *pwm);
+int pwm_unregister(struct pwm_device *pwm);
+
+enum {
+ FLAG_REQUESTED = 0,
+ FLAG_STOP = 1,
+};
+
+struct pwm_channel {
+ struct list_head list;
+ struct pwm_device *pwm;
+ const char *requester;
+ pid_t pid;
+ int chan;
+ unsigned long flags;
+ struct pwm_channel_config *c);
+
+int pwm_config(struct pwm_channel *pwm,
+ struct pwm_channel_config *c);
+
+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.6.5
--
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index 049ff24..b94666c 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -5,7 +5,6 @@
obj-$(CONFIG_IBM_ASM) += ibmasm/
obj-$(CONFIG_HDPU_FEATURES) += hdpuftrs/
obj-$(CONFIG_AD525X_DPOT) += ad525x_dpot.o
-obj-$(CONFIG_ATMEL_PWM) += atmel_pwm.o
obj-$(CONFIG_ATMEL_SSC) += atmel-ssc.o
obj-$(CONFIG_ATMEL_TCLIB) += atmel_tclib.o
obj-$(CONFIG_ICS932S401) += ics932s401.o
diff --git a/drivers/misc/atmel_pwm.c b/drivers/misc/atmel_pwm.c
deleted file mode 100644
index 6aa5294..0000000
--- a/drivers/misc/atmel_pwm.c
+++ /dev/null
-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..4f0ef8c
--- /dev/null
+++ b/drivers/pwm/atmel-pwm.c
@@ -0,0 +1,590 @@
+/*
+ * drivers/pwm/atmel-pwm.c
+ *
+ * Copyright (C) 2010 Bill Gatliff <bg...@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
+ */
+
+ 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)
+{
+__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);
+
+ dev_dbg(p->pwm->dev, "polarity %d\n", c->polarity);
+ return 0;
+}
+
+static inline int
+__atmel_pwm_config_duty_ticks(struct pwm_channel *p,
+ struct pwm_channel_config *c)
+{
+ u32 cmr, cprd, cpre, cdty;
+
+ cmr = pwmc_chan_readl(p, PWMC_CMR);
+ cprd = pwmc_chan_readl(p, PWMC_CPRD);
+
+ cpre = cmr & PWMC_CMR_CPRE_MASK;
+ cmr &= ~BIT(PWMC_CMR_CPD);
+
+ cdty = cprd - (c->duty_ticks >> cpre);
+
+ p->duty_ticks = c->duty_ticks;
+
+ if (__atmel_pwm_is_on(p)) {
+ pwmc_chan_writel(p, PWMC_CMR, cmr);
+ pwmc_chan_writel(p, PWMC_CUPD, cdty);
+ } else
+ pwmc_chan_writel(p, PWMC_CDTY, cdty);
+
+ dev_dbg(p->pwm->dev, "duty_ticks = %lu cprd = %x"
+ " cdty = %x cpre = %x\n", p->duty_ticks,
+ cprd, cdty, cpre);
+
+ return 0;
+}
+
+static inline int
+__atmel_pwm_config_period_ticks(struct pwm_channel *p,
+ struct pwm_channel_config *c)
+{
+ u32 cmr, cprd, cpre;
+
+ cpre = fls(c->period_ticks);
+ if (cpre < 16)
+ cpre = 0;
+ else {
+ cpre -= 15;
+ if (cpre > 10)
+ return -EINVAL;
+ }
+
+ cmr = pwmc_chan_readl(p, PWMC_CMR);
+ cmr &= ~PWMC_CMR_CPRE_MASK;
+ cmr |= cpre;
+
+ cprd = c->period_ticks >> cpre;
+
+ pwmc_chan_writel(p, PWMC_CMR, cmr);
+ pwmc_chan_writel(p, PWMC_CPRD, cprd);
+ p->period_ticks = c->period_ticks;
+
+ dev_dbg(p->pwm->dev, "period_ticks = %lu cprd = %x cpre = %x\n",
+ p->period_ticks, cprd, cpre);
+
+ return 0;
+}
+
+static int
+atmel_pwm_config_nosleep(struct pwm_channel *p,
+ struct pwm_channel_config *c)
+{
+ int ret = 0;
+ unsigned long flags;
+
+ spin_lock_irqsave(&p->lock, flags);
+
+ switch (c->config_mask) {
+
+ case PWM_CONFIG_DUTY_TICKS:
+ __atmel_pwm_config_duty_ticks(p, c);
+ break;
+
+ case PWM_CONFIG_STOP:
+ __atmel_pwm_stop(p);
+ break;
+
+ case PWM_CONFIG_START:
+ __atmel_pwm_start(p);
+ break;
+
+ case PWM_CONFIG_POLARITY:
+ __atmel_pwm_config_polarity(p, c);
+ break;
+
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ spin_unlock_irqrestore(&p->lock, flags);
+ return ret;
+}
+
+static int
+atmel_pwm_stop_sync(struct pwm_channel *p)
+{
+ struct atmel_pwm *ap = container_of(p->pwm, struct atmel_pwm, pwm);
+ int ret;
+ int was_on = __atmel_pwm_is_on(p);
+
+ if (was_on) {
+ do {
+ init_completion(&p->complete);
+ set_bit(FLAG_STOP, &p->flags);
+ pwmc_writel(ap, PWMC_IER, 1 << p->chan);
+
+ dev_dbg(p->pwm->dev, "waiting on stop_sync completion...\n");
+
+ ret = wait_for_completion_interruptible(&p->complete);
+
+ dev_dbg(p->pwm->dev, "stop_sync complete (%d)\n", ret);
+
+ if (ret)
+ return ret;
+ } while (p->flags & BIT(FLAG_STOP));
+ }
+
+ return was_on;
+}
+
+static int
+atmel_pwm_config(struct pwm_channel *p,
+ struct pwm_channel_config *c)
+{
+ int was_on = 0;
+
+ if (p->pwm->config_nosleep) {
+ if (!p->pwm->config_nosleep(p, c))
+ return 0;
+ }
+
+ might_sleep();
+
+ dev_dbg(p->pwm->dev, "config_mask %x\n", c->config_mask);
+
+ was_on = atmel_pwm_stop_sync(p);
+ if (was_on < 0)
+ return was_on;
+
+ if (c->config_mask & PWM_CONFIG_PERIOD_TICKS) {
+ __atmel_pwm_config_period_ticks(p, c);
+ if (!(c->config_mask & PWM_CONFIG_DUTY_TICKS)) {
+ struct pwm_channel_config d = {
+ .config_mask = PWM_CONFIG_DUTY_TICKS,
+ .duty_ticks = p->duty_ticks,
+ };
+ __atmel_pwm_config_duty_ticks(p, &d);
+ }
+ }
+
+ if (c->config_mask & PWM_CONFIG_DUTY_TICKS)
+ __atmel_pwm_config_duty_ticks(p, c);
+
+ if (c->config_mask & PWM_CONFIG_POLARITY)
+ __atmel_pwm_config_polarity(p, c);
+
+ if ((c->config_mask & PWM_CONFIG_START)
+ || (was_on && !(c->config_mask & PWM_CONFIG_STOP)))
+ __atmel_pwm_start(p);
+
+ return 0;
+}
+
+static void
+__atmel_pwm_set_callback(struct pwm_channel *p,
+ pwm_callback_t callback)
+{
+ struct atmel_pwm *ap = container_of(p->pwm, struct atmel_pwm, pwm);
+
+ p->callback = callback;
+ pwmc_writel(ap, p->callback ? PWMC_IER : PWMC_IDR, 1 << p->chan);
+}
+
+static int
+atmel_pwm_set_callback(struct pwm_channel *p,
+ pwm_callback_t callback)
+{
+ struct atmel_pwm *ap = to_atmel_pwm(p);
+ unsigned long flags;
+
+ spin_lock_irqsave(&ap->lock, flags);
+ __atmel_pwm_set_callback(p, callback);
+ spin_unlock_irqrestore(&ap->lock, flags);
+
+ return 0;
+}
+
+static int
+atmel_pwm_request(struct pwm_channel *p)
+{
+ struct atmel_pwm *ap = to_atmel_pwm(p);
+ unsigned long flags;
+
+ spin_lock_irqsave(&p->lock, flags);
+ clk_enable(ap->clk);
+ p->tick_hz = clk_get_rate(ap->clk);
+ __atmel_pwm_unsynchronize(p, NULL);
+ __atmel_pwm_stop(p);
+ spin_unlock_irqrestore(&p->lock, flags);
+
+ return 0;
+}
+
+static void
+atmel_pwm_free(struct pwm_channel *p)
+{
+ struct atmel_pwm *ap = to_atmel_pwm(p);
+ clk_disable(ap->clk);
+}
+
+static irqreturn_t
+atmel_pwmc_irq(int irq, void *data)
+{
+ struct atmel_pwm *ap = data;
+ struct pwm_channel *p;
+ u32 isr;
+ int chid;
+ unsigned long flags;
+
+ spin_lock_irqsave(&ap->lock, flags);
+
+ isr = pwmc_readl(ap, PWMC_ISR);
+ for (chid = 0; isr; chid++, isr >>= 1) {
+ p = &ap->pwm.channels[chid];
+ if (isr & 1) {
+ if (p->callback)
+ p->callback(p);
+ if (p->flags & BIT(FLAG_STOP)) {
+ __atmel_pwm_stop(p);
+ clear_bit(FLAG_STOP, &p->flags);
+ }
+ complete_all(&p->complete);
+ }
+ }
+
+ spin_unlock_irqrestore(&ap->lock, flags);
+
+ return IRQ_HANDLED;
+}
+
+static int __devinit
+atmel_pwmc_probe(struct platform_device *pdev)
+{
+ struct atmel_pwm *ap;
+ struct resource *r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ int ret = 0;
+
+ ap = kzalloc(sizeof(*ap), GFP_KERNEL);
+ if (!ap) {
+ ret = -ENOMEM;
+ goto err_atmel_pwm_alloc;
+ }
+
+ spin_lock_init(&ap->lock);
+ platform_set_drvdata(pdev, ap);
+
+ ap->pwm.dev = &pdev->dev;
+ ap->pwm.bus_id = dev_name(&pdev->dev);
+
+ ap->pwm.nchan = 4; /* TODO: true only for SAM9263 and AP7000 */
+ ap->ccnt_mask = 0xffffUL; /* TODO: true only for SAM9263 */
+
+ ap->sync_mask = kzalloc(ap->pwm.nchan * sizeof(u32), GFP_KERNEL);
+ if (!ap->sync_mask) {
+ ret = -ENOMEM;
+ goto err_alloc_sync_masks;
+ }
+
+ ap->pwm.owner = THIS_MODULE;
+ ap->pwm.request = atmel_pwm_request;
+ ap->pwm.free = atmel_pwm_free;
+ ap->pwm.config_nosleep = atmel_pwm_config_nosleep;
+ ap->pwm.config = atmel_pwm_config;
+ ap->pwm.synchronize = atmel_pwm_synchronize;
+ ap->pwm.unsynchronize = atmel_pwm_unsynchronize;
+ ap->pwm.set_callback = atmel_pwm_set_callback;
+
+ ap->clk = clk_get(&pdev->dev, "pwm_clk");
+ if (PTR_ERR(ap->clk)) {
+ ret = -ENODEV;
+ goto err_clk_get;
+ }
+
+ ap->iobase = ioremap_nocache(r->start, r->end - r->start + 1);
+ if (!ap->iobase) {
+ ret = -ENODEV;
+ goto err_ioremap;
+ }
+
+ clk_enable(ap->clk);
+ pwmc_writel(ap, PWMC_DIS, -1);
+ pwmc_writel(ap, PWMC_IDR, -1);
+ clk_disable(ap->clk);
+
+ ap->irq = platform_get_irq(pdev, 0);
+ if (ap->irq != -ENXIO) {
+ ret = request_irq(ap->irq, atmel_pwmc_irq, 0,
+ ap->pwm.bus_id, ap);
+ if (ret)
+ goto err_request_irq;
+ }
+
+ ret = pwm_register(&ap->pwm);
+ if (ret)
+ goto err_pwm_register;
+
+ return 0;
+
+err_pwm_register:
+ if (ap->irq != -ENXIO)
+ free_irq(ap->irq, ap);
+err_request_irq:
+ iounmap(ap->iobase);
+err_ioremap:
+ clk_put(ap->clk);
+err_clk_get:
+ platform_set_drvdata(pdev, NULL);
+err_alloc_sync_masks:
+ kfree(ap);
+err_atmel_pwm_alloc:
+ return ret;
+}
+
+static int __devexit
+atmel_pwmc_remove(struct platform_device *pdev)
+{
+ struct atmel_pwm *ap = platform_get_drvdata(pdev);
+ int ret;
+
+ /* TODO: what can we do if this fails? */
+ ret = pwm_unregister(&ap->pwm);
+
+ clk_enable(ap->clk);
+ pwmc_writel(ap, PWMC_IDR, -1);
+ pwmc_writel(ap, PWMC_DIS, -1);
+ clk_disable(ap->clk);
+
+ if (ap->irq != -ENXIO)
+ free_irq(ap->irq, ap);
+
+ clk_put(ap->clk);
+ iounmap(ap->iobase);
+
+ kfree(ap);
+
+ return 0;
+}
+
+static struct platform_driver atmel_pwm_driver = {
+ .driver = {
+ .name = "atmel_pwmc",
+ .owner = THIS_MODULE,
+ },
+ .probe = atmel_pwmc_probe,
+ .remove = __devexit_p(atmel_pwmc_remove),
+};
+
+static int __init atmel_pwm_init(void)
+{
+ return platform_driver_register(&atmel_pwm_driver);
+}
+module_init(atmel_pwm_init);
+
+static void __exit atmel_pwm_exit(void)
+{
+ platform_driver_unregister(&atmel_pwm_driver);
+}
+module_exit(atmel_pwm_exit);
+
+MODULE_AUTHOR("Bill Gatliff <bg...@billgatliff.com>");
+MODULE_DESCRIPTION("Driver for Atmel PWMC peripheral");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:atmel_pwmc");
diff --git a/drivers/pwm/gpio.c b/drivers/pwm/gpio.c
new file mode 100644
index 0000000..151536b
--- /dev/null
+++ b/drivers/pwm/gpio.c
@@ -0,0 +1,312 @@
+/*
+ * drivers/pwm/gpio.c
+ *
+ * Models a single-channel PWM device using a kernel interval timer
+ * and a GPIO pin.
+ *
+ * Copyright (C) 2010 Bill Gatliff <bg...@billgatliff.com>
+ *
+ * This program is free software; you may redistribute and/or modify
+ * it under the terms of the GNU General Public License Version 2, as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ * USA
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/hrtimer.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/workqueue.h>
+#include <linux/gpio.h>
+#include <linux/pwm/pwm.h>
+
+struct gpio_pwm {
+ struct pwm_device pwm;
+ struct hrtimer t;
+ 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 void gpio_pwm_start(struct pwm_channel *p)
+{
+ struct gpio_pwm *gp = to_gpio_pwm(p);
+
+ gp->active = 0;
+ gpio_pwm_timeout(&gp->t);
+ pr_debug("%s:%d start, %lu ticks\n",
+ dev_name(p->pwm->dev), p->chan, p->duty_ticks);
+}
+
+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;
+ dev_dbg(p->pwm->dev, ":%d duty_ticks %lu\n",
+ p->chan, p->duty_ticks);
+ break;
+
+ case PWM_CONFIG_START:
+ if (!hrtimer_active(&gp->t)) {
+ 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->t);
+
+ if (was_on) {
+ do {
+ init_completion(&p->complete);
+ set_bit(FLAG_STOP, &p->flags);
+
+ dev_dbg(p->pwm->dev, ":%d waiting on stop_sync completion...\n",
+ p->chan);
+
+ ret = wait_for_completion_interruptible(&p->complete);
+
+ dev_dbg(p->pwm->dev, ":%d stop_sync complete (%d)\n",
+ p->chan, ret);
+
+ if (ret)
+ return ret;
+ } while (p->flags & BIT(FLAG_STOP));
+ }
+
+ dev_dbg(p->pwm->dev, ":%d stop_sync returning %d\n",
+ p->chan, was_on);
+
+ 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();
+ dev_dbg(p->pwm->dev, ":%d config_mask %x\n",
+ p->chan, c->config_mask);
+
+ 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;
+
+ 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->t, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
+ gp->t.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:
+ 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->t);
+ cancel_work_sync(&gp->work);
+ 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 <bg...@billgatliff.com>");
+MODULE_DESCRIPTION("PWM output using GPIO and a high-resolution timer");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:gpio_pwm");
diff --git a/drivers/leds/leds-pwm.c b/drivers/leds/leds-pwm.c
index 88b1dd0..7877af8 100644
--- a/drivers/leds/leds-pwm.c
+++ b/drivers/leds/leds-pwm.c
@@ -1,152 +1,176 @@
/*
- * linux/drivers/leds-pwm.c
+ * drivers/leds/leds-pwm.c
*
- * simple PWM based LED control
+ * Copyright (C) 2010 Bill Gatliff <bg...@billgatliff.com>
+ * Copyright (C) 2009 Loutao Fu, Pengutronix <l....@pengutronix.de>
*
- * Copyright 2009 Luotao Fu @ Pengutronix (l....@pengutronix.de)
+ * Based on leds-gpio.c by Raphael Assenat <ra...@8d.com>
*
- * based on leds-gpio.c by Raphael Assenat <ra...@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
- 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)
- .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....@pengutronix.de>");
-MODULE_DESCRIPTION("PWM LED driver for PXA");
+MODULE_AUTHOR("Bill Gatliff <bg...@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 <bg...@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 */
diff --git a/drivers/Kconfig b/drivers/Kconfig
index 368ae6d..54d7b16 100644
--- a/drivers/Kconfig
+++ b/drivers/Kconfig
@@ -54,6 +54,8 @@ source "drivers/pps/Kconfig"
source "drivers/gpio/Kconfig"
+source "drivers/pwm/Kconfig"
+
source "drivers/w1/Kconfig"
source "drivers/power/Kconfig"
diff --git a/drivers/Makefile b/drivers/Makefile
index 6ee53c7..e6143f3 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -6,6 +6,8 @@
#
obj-y += gpio/
+obj-$(CONFIG_GENERIC_PWM) += pwm/
+
obj-$(CONFIG_PCI) += pci/
obj-$(CONFIG_PARISC) += parisc/
obj-$(CONFIG_RAPIDIO) += rapidio/
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index 8a0e1ec..d9547d4 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -17,12 +17,11 @@ config LEDS_CLASS
comment "LED drivers"
-config LEDS_ATMEL_PWM
- tristate "LED Support using Atmel PWM outputs"
- depends on LEDS_CLASS && 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 "LED Support for PWM connected LEDs"
+ depends on LEDS_CLASS && GENERIC_PWM
+ help
+ Enables support for LEDs connected to PWM outputs.
config LEDS_LOCOMO
tristate "LED Support for Locomo device"
@@ -223,12 +222,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 LEDS_CLASS && HAVE_PWM
- help
- This option enables support for pwm driven LEDs
-
config LEDS_REGULATOR
tristate "REGULATOR driven LED support"
depends on LEDS_CLASS && REGULATOR
@@ -305,6 +298,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"
depends on LEDS_TRIGGERS
@@ -327,6 +328,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"
depends on LEDS_TRIGGERS
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index 9e63869..9b4971e 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -20,6 +20,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
@@ -41,6 +42,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..1c24e11
--- /dev/null
+++ b/drivers/pwm/Kconfig
@@ -0,0 +1,31 @@
+#
+# PWM infrastructure and devices
+#
+
+menuconfig GENERIC_PWM
+ tristate "PWM Support"
+ depends on SYSFS
+ help
+ This enables PWM support through the generic PWM library.
+ If unsure, say N.
+
+if GENERIC_PWM
+
+config ATMEL_PWM
+ tristate "Atmel AT32/AT91 PWM support"
+ depends on AVR32 || ARCH_AT91
+ help
+ This option enables device driver support for the PWMC
+ peripheral channels found on certain Atmel processors.
+ Pulse Width Modulation is used many for purposes, including
+ software controlled power-efficient backlights on LCD
+ displays, motor control, and waveform generation. If
+ unsure, say N.
+
+config GPIO_PWM
+ tristate "PWM emulation using GPIO"
+ help
+ This option enables a single-channel PWM device using
+ a kernel interval timer and a GPIO pin. If unsure, say N.
+
+endif
diff --git a/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
diff --git a/arch/powerpc/platforms/52xx/mpc52xx_gpt.c b/arch/powerpc/platforms/52xx/mpc52xx_gpt.c
index 6f8ebe1..b9aa4a5 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 <bg...@billgatliff.com>
* Copyright (c) 2009 Secret Lab Technologies Ltd.
* Copyright (c) 2008 Sascha Hauer <s.h...@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>
@@ -65,11 +72,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");
/**
@@ -95,6 +103,9 @@ struct mpc52xx_gpt_priv {
#if defined(CONFIG_GPIOLIB)
struct of_gpio_chip of_gc;
#endif
+#if defined(CONFIG_GENERIC_PWM)
+ struct pwm_device pwm;
+#endif
};
LIST_HEAD(mpc52xx_gpt_list);
@@ -125,6 +136,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)
@@ -274,6 +289,182 @@ 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_free(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);
+ 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.free = mpc52xx_gpt_pwm_free;
+ 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)
@@ -737,9 +928,9 @@ static int __devinit mpc52xx_gpt_probe(struct of_device *ofdev,
}
dev_set_drvdata(&ofdev->dev, gpt);
-
mpc52xx_gpt_gpio_setup(gpt, ofdev->node);
mpc52xx_gpt_irq_setup(gpt, ofdev->node);
+ mpc52xx_gpt_pwm_setup(gpt, ofdev->node);
mutex_lock(&mpc52xx_gpt_list_mutex);
list_add(&gpt->list, &mpc52xx_gpt_list);
diff --git a/drivers/leds/ledtrig-dim.c b/drivers/leds/ledtrig-dim.c
new file mode 100644
index 0000000..299865b
--- /dev/null
+++ b/drivers/leds/ledtrig-dim.c
@@ -0,0 +1,95 @@
+/*
+ * LED Dim Trigger
+ *
+ * Copyright (C) 2008 Bill Gatliff <bg...@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/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 <bg...@billgatliff.com>");
+MODULE_DESCRIPTION("Dim LED trigger");
+MODULE_LICENSE("GPL");
Bill,
I'll take a look at the Documentation later.
Couple quick comments below.
[snip]
#include <linux/list.h>
> +
> +static int __pwm_create_sysfs(struct pwm_device *pwm);
> +
> +static const char *REQUEST_SYSFS = "sysfs";
Does this static string save that much? It's only used two places in this
file.
> +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)
> + {
> + pr_err("%s: ENOMEM\n", __func__);
> + return -ENOMEM;
> + }
I assume this pr_err is just a debug message and will be removed.
If not the '{' should be on the previous line.
> +
> + 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);
> + pr_err("%s: err_create_sysfs\n", __func__);
Another debug message?
> + 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;
I need to check the rest of the patch series but I assume you have
fixed the pwm->dev = NULL issue.
This is new.. What's the reason for saving the pid?
Has the 'polarity' logic been verified?
pwm_set_polarity takes an active high flag that is passed to pwm_config.
A write to the sysfs 'polarity' file passes the value directly to pwm_config.
A read from the sysfs 'polarity' file returns struct pwm_channel ->active_low.
Seems like a mis-match in logic.
Any reason not to add a read operation?
Doing a read to request the channel and a write to free it seems wrong...
I think you should be able to read the attr to get the current 'requester'
and write to it to set the 'requester'. Kind of like the 'trigger' attr
for the leds.
Also, if a kernel driver has requested the pwm channel wouldn't a read
of the attr free it? This seems bad...
Just my .02
[snip]
Are these really needed in the header? Other than <linux/list.h>, they are
already included in the .c file.
These might be cleaner without the line breaks.
You use a mix of style in this patch set.
<return type> <fn name> (...)
and
<return type>
<fn_name> (...)
Makes reading a bit difficult. Could you make them consistent?
Regards,
Hartley
For the record, this request activity is mostly advisory. I can't
actually implement something where only the requester is subsequently
allowed to manipulate the state of the channel. You really wouldn't
want to even if it was possible, because then you'd probably lose the
ability to control the output using cat(1) and echo(1).
I'm just trying to provide a tool that allows users to Play Nice if they
choose to. In fact, it's currently possible to manipulate the state of
the channel from userspace without requesting ownership first. Not sure
I'm happy with that, but since I can't enforce any sort of access
restrictions it seems pointless to even go so far as to require ownership...
b.g.
--
Bill Gatliff
Embedded systems training and consulting
http://billgatliff.com
bg...@billgatliff.com
It makes sure the same string is used in both places. I do a strcmp on
it, so I wanted to make sure I didn't screw it up!
>> + p = kcalloc(pwm->nchan, sizeof(*p), GFP_KERNEL);
>> + if (!p)
>> + {
>> + pr_err("%s: ENOMEM\n", __func__);
>> + return -ENOMEM;
>> + }
>>
>
> I assume this pr_err is just a debug message and will be removed.
> If not the '{' should be on the previous line.
>
Yes, it's a debug message. I obviously don't clean up after myself very
well...
>> + list_add_tail(&pwm->list, &pwm_device_list);
>> + ret = __pwm_create_sysfs(pwm);
>> + if (ret) {
>> + mutex_unlock(&device_list_mutex);
>> + pr_err("%s: err_create_sysfs\n", __func__);
>>
>
> Another debug message?
>
Yes. Darn it. :)
>> + 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;
>>
>
> I need to check the rest of the patch series but I assume you have
> fixed the pwm->dev = NULL issue.
>
Yes. Tested it properly, even! :)
>> +
>> + p->requester = requester;
>> + if (!strcmp(requester, REQUEST_SYSFS))
>> + p->pid = current->pid;
>>
>
> This is new.. What's the reason for saving the pid?
>
I've gotten complaints from those who say, "Ok, so it reported 'sysfs'
back to me, but how can I be sure that it's because *I* own it now, and
not another user process?" Seemed easy enough to add the PID so they
can check. Of course, you could argue that I could report just the PID,
and skip the "sysfs" string altogether. I'd be inclined to agree with you.
Of course, if you are like me and do the request with cat(1), then the
PID is pretty meaningless. :)
>> +
>> +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);
>>
>
> Has the 'polarity' logic been verified?
>
> pwm_set_polarity takes an active high flag that is passed to pwm_config.
> A write to the sysfs 'polarity' file passes the value directly to pwm_config.
> A read from the sysfs 'polarity' file returns struct pwm_channel ->active_low.
>
> Seems like a mis-match in logic.
>
It does, indeed! I thought I had checked this before, but I just tried
it now again. Not good news:
opusa5:/sys/class/pwm/f0000660.timer:0# echo 1 > polarity
opusa5:/sys/class/pwm/f0000660.timer:0# cat polarity
1
opusa5:/sys/class/pwm/f0000660.timer:0# echo 0 > polarity
opusa5:/sys/class/pwm/f0000660.timer:0# cat polarity
1
Good catch. I'll have to track this one down. I have verified that the
proper value gets out to the hardware, for all the existing drivers of
my own, anyway. Some of my hardware simply won't work if the polarity
is wrong.
>> +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);
>>
>
> Any reason not to add a read operation?
>
Can't think of one. Just haven't had a need for it myself, I suppose.
>> +static ssize_t pwm_request_store(struct device *dev,
>> + struct device_attribute *attr,
>> + const char *buf,
>> + size_t len)
>> +{
>> + struct pwm_channel *p = dev_get_drvdata(dev);
>> + pwm_free(p);
>> + return len;
>> +}
>> +static DEVICE_ATTR(request, 0644, pwm_request_show, pwm_request_store);
>>
>
> Doing a read to request the channel and a write to free it seems wrong...
>
> I think you should be able to read the attr to get the current 'requester'
> and write to it to set the 'requester'. Kind of like the 'trigger' attr
> for the leds.
>
> Also, if a kernel driver has requested the pwm channel wouldn't a read
> of the attr free it? This seems bad...
>
> Just my .02
>
A read from the attribute shows who the current requester is, and
implies that if the channel isn't currently owned by anyone else then
you'd like to own it. This approach makes the locking operation atomic
in the (unlikely) situation where two user applications are going after
the same PWM channel at the same time. It also means that I don't have
to do a write, followed by a read and compare in order to determine if I
now own the channel.
If the channel is owned when you read the attribute, then the current
owner is reported.
I guess I can see an advantage to writing a unique text string to the
attribute to indicate a request operation, and then reading back that
same text string to confirm that you own the channel. And then you'd
write a null string to un-request it, perhaps? Or would you have an
un-request attribute?
>> +#include <linux/completion.h>
>> +#include <linux/workqueue.h>
>> +#include <linux/spinlock.h>
>> +#include <linux/list.h>
>>
>
> Are these really needed in the header? Other than <linux/list.h>, they are
> already included in the .c file.
>
They certainly aren't _needed_ in the header, unless someone forgets to
include them before they include pwm.h. Any idea what the convention is
on this?
>> +
>> + int (*request) (struct pwm_channel *p);
>> + void (*free) (struct pwm_channel *p);
>> + int (*config) (struct pwm_channel *p,
>> + struct pwm_channel_config *c);
>> + int (*config_nosleep)(struct pwm_channel *p,
>> + struct pwm_channel_config *c);
>> + int (*synchronize) (struct pwm_channel *p,
>> + struct pwm_channel *to_p);
>> + int (*unsynchronize)(struct pwm_channel *p,
>> + struct pwm_channel *from_p);
>> + int (*set_callback) (struct pwm_channel *p,
>> + pwm_callback_t callback);
>>
>
> These might be cleaner without the line breaks.
>
Yea, they just get kinda long...
>> +struct pwm_channel *
>> +pwm_request(const char *bus_id, int chan,
>> + const char *requester);
>>
>
> You use a mix of style in this patch set.
>
> <return type> <fn name> (...)
>
> and
>
> <return type>
> <fn_name> (...)
>
> Makes reading a bit difficult. Could you make them consistent?
>
I'm trying to keep the lines from growing too long. But if you don't
mind them long, I can deal with it. :)
> Regards,
> Hartley
>
Thanks sooo much for your reviews!
b.g.
--
Bill Gatliff
Embedded systems training and consulting
http://billgatliff.com
bg...@billgatliff.com
--
Couldn't hurt. Done.
>> +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);
>>
>
> Maybe the following would be better:
> gpio_direction_output(gp->gpio, gp->polarity ^ gp->active)
> Instead of doing several comparisons.
>
... except that I'm trying to guarantee that only the values '1' or '0'
get sent to gpio_direction_output. There's nothing in the spec that
says other values are legal, although I'll admit that any nonzero value
is unlikely to cause problems. Should I be pedantic here?
>> +
>> + if (gp->active)
>> + hrtimer_start(&gp->t,
>> + ktime_set(0, gp->pwm.channels[0].duty_ticks),
>> + HRTIMER_MODE_REL);
>> + else
>> + hrtimer_start(&gp->t,
>> + ktime_set(0,gp->pwm.channels[0].period_ticks
>> + - gp->pwm.channels[0].duty_ticks),
>> + HRTIMER_MODE_REL);
>>
>
> if (gp->active)
> t = ktime_set(0, gp->pwm.channels[0].duty_ticks));
> else
> t = ktime_set(0, gp->pwm.channels[0].period_ticks - gp->pwm.channels[0].duty_ticks));
>
> htimer_start(&gp->t, t, HRTIMER_MODE_REL);
>
Excellent.
>> +
>> + ret = pwm_register(&gp->pwm);
>> + if (ret)
>> + goto err_pwm_register;
>> +
>> + return 0;
>> +
>> +err_pwm_register:
>>
>
> platform_set_drvdata(pdev, 0);
>
Good catch!
>> +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->t);
>> + cancel_work_sync(&gp->work);
>>
>
> platform_set_drvdata(pdev, 0);
>
Ditto.
> And there are too much pr_debug & dev_dbg calls. Several of them are inside critical sections or in functions called from critical sections (inside spin_lock_irqsave - spin_lock_irqrestore block I mean). Don't think it is good.
>
Ok. Now that the code is relatively mature, they're unnecessary anyway.
b.g.
--
Bill Gatliff
Embedded systems training and consulting
http://billgatliff.com
bg...@billgatliff.com
--
It was. And on top of that, none of the drivers were reflecting their
polarity in their pwm_channel structures.
I renamed the channel structure variable to active_high for consistency,
and updated each of the drivers to set that value consistent with their
actual polarities.
Very good catch.
b.g.
--
Bill Gatliff
Embedded systems training and consulting
http://billgatliff.com
bg...@billgatliff.com
--
Ok.
>>> + p = kcalloc(pwm->nchan, sizeof(*p), GFP_KERNEL);
>>> + if (!p)
>>> + {
>>> + pr_err("%s: ENOMEM\n", __func__);
>>> + return -ENOMEM;
>>> + }
>>>
>>
>> I assume this pr_err is just a debug message and will be removed.
>> If not the '{' should be on the previous line.
>>
>
> Yes, it's a debug message. I obviously don't clean up after myself very
> well...
;-)
>>> + list_add_tail(&pwm->list, &pwm_device_list);
>>> + ret = __pwm_create_sysfs(pwm);
>>> + if (ret) {
>>> + mutex_unlock(&device_list_mutex);
>>> + pr_err("%s: err_create_sysfs\n", __func__);
>>>
>>
>> Another debug message?
>>
>
> Yes. Darn it. :)
>
>
>>> + 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;
>>>
>>
>> I need to check the rest of the patch series but I assume you have
>> fixed the pwm->dev = NULL issue.
>>
>
> Yes. Tested it properly, even! :)
Ok.
>>> +
>>> + p->requester = requester;
>>> + if (!strcmp(requester, REQUEST_SYSFS))
>>> + p->pid = current->pid;
>>>
>>
>> This is new.. What's the reason for saving the pid?
>>
>
> I've gotten complaints from those who say, "Ok, so it reported 'sysfs'
> back to me, but how can I be sure that it's because *I* own it now, and
> not another user process?" Seemed easy enough to add the PID so they
> can check. Of course, you could argue that I could report just the PID,
> and skip the "sysfs" string altogether. I'd be inclined to agree with you.
>
> Of course, if you are like me and do the request with cat(1), then the
> PID is pretty meaningless. :)
I would think a userspace request would typically use cat(1). That's why
I didn't understand why you are saving the pid.
I thought I noticed this issue with your original patches. Glad you
were able to verify it.
>>> +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);
>>>
>>
>> Any reason not to add a read operation?
>>
>
> Can't think of one. Just haven't had a need for it myself, I suppose.
Time will tell... It can easily be added later if needed.
Maybe change the attribute so that it works like the led trigger attribute.
The list of available requesters returned would be something like the
following:
If the pwm channel is free.
# cat request
[none] sysfs <- the channel is free
# echo sysfs > request <- request the channel for syfs use
# cat request
none [sysfs] <- sysfs now owns the channel
If the pwm channel is owned by a kernel driver, i.e. the pwm-led.c driver:
# cat request
[cur_led->name] <- the pwm-led driver owns the channel
I think the other attributes should also be removed then the channel is
owned by a kernel driver since that driver is actually in control of the
configuration. I'm not sure if this is possible.
Also, a better name for this attribute might be 'owner' or something along
that line.
As far as making the request atomic, a user application could always just
'request' the channel without checking if it's available first. If the
channel is unavailable the request could return an error back to user space.
The way it is now I think if a kernel driver were to request a channel,
a user app could rip it away by doing:
# cat request
cur_led->name <- the pwm-led driver owns the channel
# echo anything > request <- oops.. pwm_free just got called
# cat request
sysfs p->pid <- sysfs now owns the channel
>>> +#include <linux/completion.h>
>>> +#include <linux/workqueue.h>
>>> +#include <linux/spinlock.h>
>>> +#include <linux/list.h>
>>>
>>
>> Are these really needed in the header? Other than <linux/list.h>, they are
>> already included in the .c file.
>>
>
> They certainly aren't _needed_ in the header, unless someone forgets to
> include them before they include pwm.h. Any idea what the convention is
> on this?
I think it's cleaner to require the .c file to include the necessary headers.
The only reason I can see to have an include in a header is if there is a
strict dependancy on that header, and normally this can be avoided.
>>> +
>>> + int (*request) (struct pwm_channel *p);
>>> + void (*free) (struct pwm_channel *p);
>>> + int (*config) (struct pwm_channel *p,
>>> + struct pwm_channel_config *c);
>>> + int (*config_nosleep)(struct pwm_channel *p,
>>> + struct pwm_channel_config *c);
>>> + int (*synchronize) (struct pwm_channel *p,
>>> + struct pwm_channel *to_p);
>>> + int (*unsynchronize)(struct pwm_channel *p,
>>> + struct pwm_channel *from_p);
>>> + int (*set_callback) (struct pwm_channel *p,
>>> + pwm_callback_t callback);
>>>
>>
>> These might be cleaner without the line breaks.
>>
>
> Yea, they just get kinda long...
The longest is the (*config) one and it's 87 chars when pulled into
one line. That one, and a bunch of others, could be shortened by
changing the pwm_channel_config name to pwm_config.
BTW, there has been discussion and patches on lkml to increase
the 80 character line limit. Personaly I think it makes sense.
My laptop and desktop both have a 1920 horizontal res but my
Vertical res on the laptop is only 1080. I would much rather
read long, easy to read, lines and see more of them than read
short, broken, lines and see less of them. ;-)
>>> +struct pwm_channel *
>>> +pwm_request(const char *bus_id, int chan,
>>> + const char *requester);
>>>
>>
>> You use a mix of style in this patch set.
>>
>> <return type> <fn name> (...)
>>
>> and
>>
>> <return type>
>> <fn_name> (...)
>>
>> Makes reading a bit difficult. Could you make them consistent?
>>
>
> I'm trying to keep the lines from growing too long. But if you don't
> mind them long, I can deal with it. :)
Well, going with the above comment, making all of these one line each
makes them a lot easier to read and see what the API consists of.
Also, grouping the common ones together makes more sense than having
a white space between very function prototype, i.e.:
struct pwm_channel *pwm_request(...)
void pwm_free(...)
int pwm_config_nosleep(...)
int pwm_config(...)
unsigned long pwm_ns_to_ticks(...)
unsigned long pwm_ticks_to_ns(...)
int pwm_set_period_ns(...)
unsigned long int pwm_get_period_ns(...) <- BTW, the 'int' is redundant
int pwm_set_duty_ns(...)
unsigned long pwm_get_duty_ns(...)
int pwm_set_duty_percent(...)
int pwm_set_polarity(...)
int pwm_start(...)
int pwm_stop(...)
int pwm_set_handler(...)
int pwm_synchronize(...)
int pwm_unsynchronize(...)
>> Regards,
>> Hartley
>>
>
> Thanks sooo much for your reviews!
My pleasure to help.
Regards,
Hartley
A couple minor comments below.
This requires that the pwm producer can generate an interrupt signaling
the end-of-period. If so you might want to mention this so that driver
writers don't spin their wheels trying to get the callback to work. It's
mentioned below but you might want to bring it up here.
> +
> +
> +
> +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():
> +
What about dev? If it's missing you will get the oops.
> +
> +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).
The hint doesn't work. The bus_id needs to be a string, use dev_name
or something along that line.
> +
See above.
> +
> +
> +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.
If the pwm hardware does not implement this callback the API will
return -EINVAL. Does this need to be mentioned?
Also, it appears all your drivers first try to implement the pwm_config
using the config_nosleep then fall back to the config inplementation.
is this the desired standard?
> +
> +
> +
> +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. ;)
> +
Regards,
Hartley
...polarity ^ active ?
--
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html
> +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.
Stop right here. Linux is not hard realtime os, nor is it an os that
never crashes.
> +(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
Exactly; if your hw can be damaged by software, it was misdesigned.
Is the paragraph #1 really neccessary?
> +Using the API to Generate PWM Signals -- Basic Functions for Users
> +
> +
> +pwm_request() -- Returns a pwm_channel pointer, which is subsequently
> +passed to the other user-related PWM functions. Once requested, a PWM
> +channel is marked as in-use and subsequent requests prior to
> +pwm_free() will fail.
> +
> +The names used to refer to PWM devices are defined by driver authors.
> +Typically they are platform device bus identifiers, and this
> +convention is encouraged for consistency.
> +
> +
> +pwm_free() -- Marks a PWM channel as no longer in use. The PWM device
> +is stopped before it is released by the API.
free is normally used for something else. Rename to open/close?
> +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.
What does it mean to stop a signal? What is the difference between 0%
duty cycle and stop() ?
> +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.
Is polarity realy required? Can't driver just replace duty with
100%-duty?
... except that if polarity and/or active are >1, I don't send the
values 1 or 0 to gpio_direction_output. I don't know if the API is
specifically intended to accept nonzero values that are greater than 1.
b.g.
--
Bill Gatliff
Embedded systems training and consulting
http://billgatliff.com
bg...@billgatliff.com
--
It provides a little background on the subject matter. I don't think
it's mandatory, but I don't see the harm in keeping it. I think it
improves the document overall from an editorial perspective, however.
>> +pwm_free() -- Marks a PWM channel as no longer in use. The PWM device
>> +is stopped before it is released by the API.
>>
>
> free is normally used for something else. Rename to open/close?
>
... or request/release?
>> +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.
>>
>
> What does it mean to stop a signal? What is the difference between 0%
> duty cycle and stop() ?
>
Depends on the hardware. For a true PWM peripheral, a 0% duty cycle
might still have the base peripheral clock for the device running.
Whereas a pwm_stop() signal could be used to turn off the clock to the
peripheral.
> Is polarity realy required? Can't driver just replace duty with
> 100%-duty
Actually, yes in some cases. Users can always do the 100%-duty math,
but some hardware asserts a specific output state when you stop the
peripheral that's potentially different from 0% duty. Also, some
hardware begins the PWM cycle with the output high, while others do with
the output low. It isn't necessarily the case that the user cares, but
I was thinking that having the API allow for different polarity might
prevent some applications having to optionally do the %duty vs.
100-%duty conversion themselves.
b.g.
--
Bill Gatliff
Embedded systems training and consulting
http://billgatliff.com
bg...@billgatliff.com
--
!polarity ^ !active ? :-).
Pavel
--
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html
> ... or request/release?
Works for me.
> >> +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.
> >>
> >
> > What does it mean to stop a signal? What is the difference between 0%
> > duty cycle and stop() ?
> >
>
> Depends on the hardware. For a true PWM peripheral, a 0% duty cycle
> might still have the base peripheral clock for the device running.
> Whereas a pwm_stop() signal could be used to turn off the clock to the
> peripheral.
If it is just powersaving... I'd do it automatically when 0% duty is
selected...? Or is that infeasible due to latency...?
> > Is polarity realy required? Can't driver just replace duty with
> > 100%-duty
>
> Actually, yes in some cases. Users can always do the 100%-duty math,
> but some hardware asserts a specific output state when you stop the
> peripheral that's potentially different from 0% duty. Also, some
> hardware begins the PWM cycle with the output high, while others do with
> the output low. It isn't necessarily the case that the user cares, but
> I was thinking that having the API allow for different polarity might
> prevent some applications having to optionally do the %duty vs.
> 100-%duty conversion themselves.
Ok, ok, but this should go into the docs.
Pavel
--
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html
> On Thu 2010-02-11 14:35:14, Bill Gatliff wrote:
>> Pavel Machek wrote:
>>>> +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);
>>>> +}
>>>>
>>>
>>> ...polarity ^ active ?
>>>
>>
>> ... except that if polarity and/or active are >1, I don't send the
>> values 1 or 0 to gpio_direction_output. I don't know if the API is
>> specifically intended to accept nonzero values that are greater than 1.
>
> !polarity ^ !active ? :-).
One the one hand that wouldn't be 100% right because according to ANSI C !(0) is just != 0 but no one says it is 1.
On another hand as far as I can see polarity and active fields are both defined as "unsigned long <name> :1" in the gpio_pwm structure. And that means they can be equal only to 0 or 1. So simple (polarity ^ active) is the right choice as far as the original decision.
What is strange for me is that resulting control flow is not right representation of what is happening. I mean that actually one should perform a call to an external function with an argument that depends on values of two variables. While this code is equal to call function A if some variable is not equal to 0 with argument depending on value of some other variable else call function B. I hope you understood what I'm trying to say.
Therefore even if you wish to leave if statement (not sure that gcc would optimize that) the right control flow representation should be the following:
.....
if (gp->active)
value = ((gp->polarity)?1:0);
else
value = ((gp->polarity)?0:1);
gpio_direction_output(gp->gpio, value);
.....
The second strange thing is "unsigned long <name>:1". I'm not sure but as far as I can remember the right way to define several-bits field is "int <name>:1". But I might be mistaken.
Best regards. Stas.
A bitfield of size 1 should not be signed, as there's no space for a sign bit.
Usually, you want all bitfields to be unsigned anyway.
Gr{oetje,eeting}s,
Geert
--
Geert Uytterhoeven -- There's lots of Linux beyond ia32 -- ge...@linux-m68k.org
In personal conversations with technical people, I call myself a hacker. But
when I'm talking to journalists I just say "programmer" or something like that.
-- Linus Torvalds
I believe you are wrong here.
!!x is common idiom using to turn anything into 0/1, used all over kernel.
Pavel
--
(english) http://www.livejournal.com/~pavelmachek
(cesky, pictures) http://atrey.karlin.mff.cuni.cz/~pavel/picture/horses/blog.html
FWIW, the gpiolib API will accept any non-zero value to "set" a gpio pin
and a zero value to "clear" the pin.
For that matter, some of the gpiolib drivers end up returning the bit
position mask for a given gpio pin and not 1 or 0. For instance if the
gpio pin in question is bit 6 in an 8-bit register and it is set, a
__gpio_get_value will end up returning 0x40 and not '1'.
Regards,
Hartley
It makes me sort of cringe to say this, but I'm going to assume that the
API is intended to work that way so that I can take advantage of it.
But I'd love to someday have the reassurance that gpiolib really *is*
intended to work that way (might be a bad idea though, see below). Call
me paranoid, but I've lost a lot of hair over the years undoing the
damage of similar failed assumptions.
I've found the Linux kernel code to be refreshingly forgiving of such
things, however, so I'm willing to risk it this time. :)
> For that matter, some of the gpiolib drivers end up returning the bit
> position mask for a given gpio pin and not 1 or 0. For instance if the
> gpio pin in question is bit 6 in an 8-bit register and it is set, a
> __gpio_get_value will end up returning 0x40 and not '1'.
>
Who's to say that gpios should always be boolean? On the output side, a
"gpio" that's four bits wide might be a useful way of dealing with bar
graphs, seven-segment displays, etc. (but more specialized abstractions
might be more appropriate, of course).
A two-bit "gpio" input might make it easier to deal with rotary encoders.
But for now, GPIOs are assumed to be booleans and that's why I'm
hesitant to feed the API non-boolean values. Someday, those values
might mean something subtly but disastrously different. And given my
luck lately with such things...
b.g.
--
Bill Gatliff
Embedded systems training and consulting
http://billgatliff.com
bg...@billgatliff.com
--
I think the original intention of gpiolib was to deal with individual pins.
> A two-bit "gpio" input might make it easier to deal with rotary encoders.
True. But a two-bit "gpio" is no longer a "pin" it's now a "port".
I have been messing with a "port" extension for gpiolib but it's not even
close to being mergable yet. But, that's a different topic...
> But for now, GPIOs are assumed to be booleans and that's why I'm
> hesitant to feed the API non-boolean values. Someday, those values
> might mean something subtly but disastrously different. And given my
> luck lately with such things...
True. As such just use ! and !! to make the values boolean.
gpio is bit 6
val = 0x40 -> !(0x40) = 0
val = 0x40 -> !!(0x40) = 1
Just my .02... Regards,
Hartley