Re: [PWM 01/10] API to consolidate PWM devices behind a common user and kernel interface

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Fri, Oct 1, 2010 at 5:00 PM, Kevin Hilman
<khilman@xxxxxxxxxxxxxxxxxxx> wrote:
> Bill Gatliff <bgat@xxxxxxxxxxxxxxx> writes:
>
>> Signed-off-by: Bill Gatliff <bgat@xxxxxxxxxxxxxxx>
>
> Hi Bill,
>
> Any plans to post this to a broader audience?  maybe linux-arm-kernel?
>
> On davinci, I recently received a patch[1] for a davinci PWM layer which
> allows the mulitple available PWM devices on davinci to make themselves
> available by using the existing PWM API.  It just added a pwm_add() and
> pwm_remove() function to the existing API to do this.  Not being
> terribly familiar with all the PWM implementations out there, I was
> suprised to see that something like this didn't exist.
>
> I will ask the author of the davinci PWM code to have a look at your
> stuff as well.

Also, Varun and Scott also have submitted PWM patches for OMAP [2] and
I hope they will look at this patch set and comment.  If I get a
chance, and I never do, I will try these myself.  I hope that baseline
PWM support is accepted soon.

[2] http://www.mail-archive.com/linux-embedded@xxxxxxxxxxxxxxx/msg02851.html

>
> Thanks,
>
> Kevin
>
> [1] http://linux.davincidsp.com/pipermail/davinci-linux-open-source/2010-September/020338.html
>
>> ---
>>  Documentation/pwm.txt   |  260 +++++++++++++++++++
>>  drivers/pwm/pwm.c       |  635 +++++++++++++++++++++++++++++++++++++++++++++++
>>  include/linux/pwm.h     |   31 ---
>>  include/linux/pwm/pwm.h |  128 ++++++++++
>>  4 files changed, 1023 insertions(+), 31 deletions(-)
>>  create mode 100644 Documentation/pwm.txt
>>  create mode 100644 drivers/pwm/pwm.c
>>  delete mode 100644 include/linux/pwm.h
>>  create mode 100644 include/linux/pwm/pwm.h
>>
>> diff --git a/Documentation/pwm.txt b/Documentation/pwm.txt
>> new file mode 100644
>> index 0000000..34b1e5a
>> --- /dev/null
>> +++ b/Documentation/pwm.txt
>> @@ -0,0 +1,260 @@
>> +                       Generic PWM Device API
>> +
>> +                          August 5, 2010
>> +                            Bill Gatliff
>> +                        <bgat@xxxxxxxxxxxxxxx>
>> +
>> +
>> +
>> +The code in drivers/pwm and include/linux/pwm/ implements an API for
>> +applications involving pulse-width-modulation signals.  This document
>> +describes how the API implementation facilitates both PWM-generating
>> +devices, and users of those devices.
>> +
>> +
>> +
>> +Motivation
>> +
>> +The primary goals for implementing the "generic PWM API" are to
>> +consolidate the various PWM implementations within a consistent and
>> +redundancy-reducing framework, and to facilitate the use of
>> +hotpluggable PWM devices.
>> +
>> +Previous PWM-related implementations within the Linux kernel achieved
>> +their consistency via cut-and-paste, but did not need to (and didn't)
>> +facilitate more than one PWM-generating device within the system---
>> +hotplug or otherwise.  The Generic PWM Device API might be most
>> +appropriately viewed as an update to those implementations, rather
>> +than a complete rewrite.
>> +
>> +
>> +
>> +Challenges
>> +
>> +One of the difficulties in implementing a generic PWM framework is the
>> +fact that pulse-width-modulation applications involve real-world
>> +signals, which often must be carefully managed to prevent destruction
>> +of hardware that is linked to those signals.  A DC motor that
>> +experiences a brief interruption in the PWM signal controlling it
>> +might destructively overheat; it could suddenly change speed, losing
>> +synchronization with a sensor; it could even suddenly change direction
>> +or torque, breaking the mechanical device connected to it.
>> +
>> +(A generic PWM device framework is not directly responsible for
>> +preventing the above scenarios: that responsibility lies with the
>> +hardware designer, and the application and driver authors.  But it
>> +must to the greatest extent possible make it easy to avoid such
>> +problems).
>> +
>> +A generic PWM device framework must accommodate the substantial
>> +differences between available PWM-generating hardware devices, without
>> +becoming sub-optimal for any of them.
>> +
>> +Finally, a generic PWM device framework must be relatively
>> +lightweight, computationally speaking.  Some PWM users demand
>> +high-speed outputs, plus the ability to regulate those outputs
>> +quickly.  A device framework must be able to "keep up" with such
>> +hardware, while still leaving time to do real work.
>> +
>> +The Generic PWM Device API is an attempt to meet all of the above
>> +requirements.  At its initial publication, the API was already in use
>> +managing small DC motors, sensors and solenoids through a
>> +custom-designed, optically-isolated H-bridge driver.
>> +
>> +
>> +
>> +Functional Overview
>> +
>> +The Generic PWM Device API framework is implemented in
>> +include/linux/pwm/pwm.h and drivers/pwm/pwm.c.  The functions therein
>> +use information from pwm_device, pwm_channel and pwm_channel_config
>> +structures to invoke services in PWM peripheral device drivers.
>> +Consult drivers/pwm/atmel-pwm.c for an example driver.
>> +
>> +There are two classes of adopters of the PWM framework:
>> +
>> +  "Users" -- those wishing to employ the API merely to produce PWM
>> +  signals; once they have identified the appropriate physical output
>> +  on the platform in question, they don't care about the details of
>> +  the underlying hardware
>> +
>> +  "Driver authors" -- those wishing to bind devices that can generate
>> +  PWM signals to the Generic PWM Device API, so that the services of
>> +  those devices become available to users. Assuming the hardware can
>> +  support the needs of a user, driver authors don't care about the
>> +  details of the user's application
>> +
>> +Generally speaking, users will first invoke pwm_request() to obtain a
>> +handle to a PWM device.  They will then pass that handle to functions
>> +like pwm_duty_ns() and pwm_period_ns() to set the duty cycle and
>> +period of the PWM signal, respectively.  They will also invoke
>> +pwm_start() and pwm_stop() to turn the signal on and off.
>> +
>> +The Generic PWM API framework also provides a sysfs interface to PWM
>> +devices, which is adequate for basic application needs and testing.
>> +
>> +Driver authors fill out a pwm_device structure, which describes the
>> +capabilities of the PWM hardware being constructed--- including the
>> +number of distinct output "channels" the peripheral offers.  They then
>> +invoke pwm_register() (usually from within their device's probe()
>> +handler) to make the PWM API aware of their device.  The framework
>> +will call back to the methods described in the pwm_device structure as
>> +users begin to configure and utilize the hardware.
>> +
>> +Note that PWM signals can be produced by a variety of peripherals,
>> +beyond the true "PWM hardware" offered by many system-on-chip devices.
>> +Other possibilities include timer/counters with compare-match
>> +capabilities, carefully-programmed synchronous serial ports
>> +(e.g. SPI), and GPIO pins driven by kernel interval timers.  With a
>> +proper pwm_device structure, these devices and pseudo-devices can all
>> +be accommodated by the Generic PWM Device API framework.
>> +
>> +
>> +
>> +Using the API to Generate PWM Signals -- Basic Functions for Users
>> +
>> +
>> +pwm_request() -- Returns a pwm_channel pointer, which is subsequently
>> +passed to the other user-related PWM functions.  Once requested, a PWM
>> +channel is marked as in-use and subsequent requests prior to
>> +pwm_release() will fail.
>> +
>> +The names used to refer to PWM devices are defined by driver authors.
>> +Typically they are platform device bus identifiers, and this
>> +convention is encouraged for consistency.
>> +
>> +
>> +pwm_release() -- Marks a PWM channel as no longer in use.  The PWM
>> +device is stopped before it is released by the API.
>> +
>> +
>> +pwm_period_ns() -- Specifies the PWM signal's period, in nanoseconds.
>> +
>> +
>> +pwm_duty_ns() -- Specifies the PWM signal's active duration, in nanoseconds.
>> +
>> +
>> +pwm_duty_percent() -- Specifies the PWM signal's active duration, as a
>> +percentage of the current period of the signal.  NOTE: this value is
>> +not recalculated if the period of the signal is subsequently changed.
>> +
>> +
>> +pwm_start(), pwm_stop() -- Turns the PWM signal on and off.  Except
>> +where stated otherwise by a driver author, signals are stopped at the
>> +end of the current period, at which time the output is set to its
>> +inactive state.
>> +
>> +
>> +pwm_polarity() -- Defines whether the PWM signal output's active
>> +region is "1" or "0".  A 10% duty-cycle, polarity=1 signal will
>> +conventionally be at 5V (or 3.3V, or 1000V, or whatever the platform
>> +hardware does) for 10% of the period.  The same configuration of a
>> +polarity=0 signal will be at 5V (or 3.3V, or ...) for 90% of the
>> +period.
>> +
>> +
>> +
>> +Using the API to Generate PWM Signals -- Advanced Functions
>> +
>> +
>> +pwm_config() -- Passes a pwm_channel_config structure to the
>> +associated device driver.  This function is invoked by pwm_start(),
>> +pwm_duty_ns(), etc. and is one of two main entry points to the PWM
>> +driver for the hardware being used.  The configuration change is
>> +guaranteed atomic if multiple configuration changes are specified.
>> +This function might sleep, depending on what the device driver has to
>> +do to satisfy the request.  All PWM device drivers must support this
>> +entry point.
>> +
>> +
>> +pwm_config_nosleep() -- Passes a pwm_channel_config structure to the
>> +associated device driver.  If the driver must sleep in order to
>> +implement the requested configuration change, -EWOULDBLOCK is
>> +returned.  Users may call this function from interrupt handlers, for
>> +example.  This is the other main entry point into the PWM hardware
>> +driver, but not all device drivers support this entry point.
>> +
>> +
>> +pwm_synchronize(), pwm_unsynchronize() -- "Synchronizes" two or more
>> +PWM channels, if the underlying hardware permits.  (If it doesn't, the
>> +framework facilitates emulating this capability but it is not yet
>> +implemented).  Synchronized channels will start and stop
>> +simultaneously when any single channel in the group is started or
>> +stopped.  Use pwm_unsynchronize(..., NULL) to completely detach a
>> +channel from any other synchronized channels.  By default, all PWM
>> +channels are unsynchronized.
>> +
>> +
>> +pwm_set_handler() -- Defines an end-of-period callback.  The indicated
>> +function will be invoked in a worker thread at the end of each PWM
>> +period, and can subsequently invoke pwm_config(), etc.  Must be used
>> +with extreme care for high-speed PWM outputs.  Set the handler
>> +function to NULL to un-set the handler.
>> +
>> +
>> +
>> +Implementing a PWM Device API Driver -- Functions for Driver Authors
>> +
>> +
>> +Fill out the appropriate fields in a pwm_device structure, and submit
>> +to pwm_register():
>> +
>> +
>> +bus_id -- the plain-text name of the device.  Users will bind to a
>> +channel on the device using this name plus the channel number.  For
>> +example, the Atmel PWMC's bus_id is "atmel_pwmc", the same as used by
>> +the platform device driver (recommended).  The first device registered
>> +thereby receives bus_id "atmel_pwmc.0", which is what you put in
>> +pwm_device.bus_id.  Channels are then named "atmel_pwmc.0:[0-3]".
>> +(Hint: just use pdev->dev.bus_id in your probe() method).
>> +
>> +
>> +nchan -- the number of distinct output channels provided by the device.
>> +
>> +
>> +request -- (optional) Invoked each time a user requests a channel.
>> +Use to turn on clocks, clean up register states, etc.  The framework
>> +takes care of device locking/unlocking; you will see only successful
>> +requests.
>> +
>> +
>> +free -- (optional) Callback for each time a user relinquishes a
>> +channel.  The framework will have already stopped, unsynchronized and
>> +un-handled the channel.  Use to turn off clocks, etc. as necessary.
>> +
>> +
>> +synchronize, unsynchronize -- (optional) Callbacks to
>> +synchronize/unsynchronize channels.  Some devices provide this
>> +capability in hardware; for others, it can be emulated (see
>> +atmel_pwmc.c's sync_mask for an example).
>> +
>> +
>> +set_callback -- (optional) Invoked when a user requests a handler.  If
>> +the hardware supports an end-of-period interrupt, invoke the function
>> +indicated during your interrupt handler.  The callback function itself
>> +is always internal to the API, and does not map directly to the user's
>> +callback function.
>> +
>> +
>> +config -- Invoked to change the device configuration, always from a
>> +sleep-capable context.  All the changes indicated must be performed
>> +atomically, ideally synchronized to an end-of-period event (so that
>> +you avoid short or long output pulses).  You may sleep, etc. as
>> +necessary within this function.
>> +
>> +
>> +config_nosleep -- (optional) Invoked to change device configuration
>> +from within a context that is not allowed to sleep.  If you cannot
>> +perform the requested configuration changes without sleeping, return
>> +-EWOULDBLOCK.
>> +
>> +
>> +
>> +Acknowledgements
>> +
>> +
>> +The author expresses his gratitude to the countless developers who
>> +have reviewed and submitted feedback on the various versions of the
>> +Generic PWM Device API code, and those who have submitted drivers and
>> +applications that use the framework.  You know who you are.  ;)
>> +
>> diff --git a/drivers/pwm/pwm.c b/drivers/pwm/pwm.c
>> new file mode 100644
>> index 0000000..2774116
>> --- /dev/null
>> +++ b/drivers/pwm/pwm.c
>> @@ -0,0 +1,635 @@
>> +/*
>> + * drivers/pwm/pwm.c
>> + *
>> + * Copyright (C) 2010 Bill Gatliff <bgat@xxxxxxxxxxxxxxx>
>> + *
>> + * This program is free software; you may redistribute and/or modify
>> + * it under the terms of the GNU General Public License version 2 as
>> + * published by the Free Software Foundation.
>> + *
>> + * This program is distributed in the hope that it will be useful, but
>> + * WITHOUT ANY WARRANTY; without even the implied warranty of
>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
>> + * General Public License for more details.
>> + *
>> + * You should have received a copy of the GNU General Public License
>> + * along with this program; if not, write to the Free Software
>> + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
>> + * USA
>> + */
>> +
>> +#include <linux/module.h>
>> +#include <linux/kernel.h>
>> +#include <linux/init.h>
>> +#include <linux/slab.h>
>> +#include <linux/device.h>
>> +#include <linux/spinlock.h>
>> +#include <linux/fs.h>
>> +#include <linux/completion.h>
>> +#include <linux/workqueue.h>
>> +#include <linux/list.h>
>> +#include <linux/sched.h>
>> +#include <linux/pwm/pwm.h>
>> +
>> +static int __pwm_create_sysfs(struct pwm_device *pwm);
>> +
>> +static const char *REQUEST_SYSFS = "sysfs";
>> +static LIST_HEAD(pwm_device_list);
>> +static DEFINE_MUTEX(device_list_mutex);
>> +static struct class pwm_class;
>> +static struct workqueue_struct *pwm_handler_workqueue;
>> +
>> +int pwm_register(struct pwm_device *pwm)
>> +{
>> +     struct pwm_channel *p;
>> +     int wchan;
>> +     int ret;
>> +
>> +     spin_lock_init(&pwm->list_lock);
>> +
>> +     p = kcalloc(pwm->nchan, sizeof(*p), GFP_KERNEL);
>> +     if (!p)
>> +             return -ENOMEM;
>> +
>> +     for (wchan = 0; wchan < pwm->nchan; wchan++) {
>> +             spin_lock_init(&p[wchan].lock);
>> +             init_completion(&p[wchan].complete);
>> +             p[wchan].chan = wchan;
>> +             p[wchan].pwm = pwm;
>> +     }
>> +
>> +     pwm->channels = p;
>> +
>> +     mutex_lock(&device_list_mutex);
>> +
>> +     list_add_tail(&pwm->list, &pwm_device_list);
>> +     ret = __pwm_create_sysfs(pwm);
>> +     if (ret) {
>> +             mutex_unlock(&device_list_mutex);
>> +             goto err_create_sysfs;
>> +     }
>> +
>> +     mutex_unlock(&device_list_mutex);
>> +
>> +     dev_info(pwm->dev, "%d channel%s\n", pwm->nchan,
>> +              pwm->nchan > 1 ? "s" : "");
>> +     return 0;
>> +
>> +err_create_sysfs:
>> +     kfree(p);
>> +
>> +     return ret;
>> +}
>> +EXPORT_SYMBOL(pwm_register);
>> +
>> +static int __match_device(struct device *dev, void *data)
>> +{
>> +     return dev_get_drvdata(dev) == data;
>> +}
>> +
>> +int pwm_unregister(struct pwm_device *pwm)
>> +{
>> +     int wchan;
>> +     struct device *dev;
>> +
>> +     mutex_lock(&device_list_mutex);
>> +
>> +     for (wchan = 0; wchan < pwm->nchan; wchan++) {
>> +       if (pwm->channels[wchan].flags & BIT(FLAG_REQUESTED)) {
>> +                     mutex_unlock(&device_list_mutex);
>> +                     return -EBUSY;
>> +             }
>> +     }
>> +
>> +     for (wchan = 0; wchan < pwm->nchan; wchan++) {
>> +             dev = class_find_device(&pwm_class, NULL,
>> +                                     &pwm->channels[wchan],
>> +                                     __match_device);
>> +             if (dev) {
>> +                     put_device(dev);
>> +                     device_unregister(dev);
>> +             }
>> +     }
>> +
>> +     kfree(pwm->channels);
>> +     list_del(&pwm->list);
>> +     mutex_unlock(&device_list_mutex);
>> +
>> +     return 0;
>> +}
>> +EXPORT_SYMBOL(pwm_unregister);
>> +
>> +static struct pwm_device *
>> +__pwm_find_device(const char *bus_id)
>> +{
>> +     struct pwm_device *p;
>> +
>> +     list_for_each_entry(p, &pwm_device_list, list) {
>> +             if (!strcmp(bus_id, p->bus_id))
>> +                     return p;
>> +     }
>> +     return NULL;
>> +}
>> +
>> +static int
>> +__pwm_request_channel(struct pwm_channel *p,
>> +                   const char *requester)
>> +{
>> +     int ret;
>> +
>> +     if (test_and_set_bit(FLAG_REQUESTED, &p->flags))
>> +             return -EBUSY;
>> +
>> +     if (p->pwm->request) {
>> +             ret = p->pwm->request(p);
>> +             if (ret) {
>> +                     clear_bit(FLAG_REQUESTED, &p->flags);
>> +                     return ret;
>> +             }
>> +     }
>> +
>> +     p->requester = requester;
>> +     if (!strcmp(requester, REQUEST_SYSFS))
>> +             p->pid = current->pid;
>> +
>> +     return 0;
>> +}
>> +
>> +struct pwm_channel *
>> +pwm_request(const char *bus_id,
>> +         int chan,
>> +         const char *requester)
>> +{
>> +     struct pwm_device *p;
>> +     int ret;
>> +
>> +     mutex_lock(&device_list_mutex);
>> +
>> +     p = __pwm_find_device(bus_id);
>> +     if (!p || chan >= p->nchan)
>> +             goto err_no_device;
>> +
>> +     if (!try_module_get(p->owner))
>> +             goto err_module_get_failed;
>> +
>> +     ret = __pwm_request_channel(&p->channels[chan], requester);
>> +     if (ret)
>> +             goto err_request_failed;
>> +
>> +     mutex_unlock(&device_list_mutex);
>> +     return &p->channels[chan];
>> +
>> +err_request_failed:
>> +     module_put(p->owner);
>> +err_module_get_failed:
>> +err_no_device:
>> +     mutex_unlock(&device_list_mutex);
>> +     return NULL;
>> +}
>> +EXPORT_SYMBOL(pwm_request);
>> +
>> +void pwm_release(struct pwm_channel *p)
>> +{
>> +     mutex_lock(&device_list_mutex);
>> +
>> +     if (!test_and_clear_bit(FLAG_REQUESTED, &p->flags))
>> +             goto done;
>> +
>> +     pwm_stop(p);
>> +     pwm_unsynchronize(p, NULL);
>> +     pwm_set_handler(p, NULL, NULL);
>> +
>> +     if (p->pwm->release)
>> +             p->pwm->release(p);
>> +     module_put(p->pwm->owner);
>> +done:
>> +     mutex_unlock(&device_list_mutex);
>> +}
>> +EXPORT_SYMBOL(pwm_release);
>> +
>> +unsigned long pwm_ns_to_ticks(struct pwm_channel *p,
>> +                           unsigned long nsecs)
>> +{
>> +     unsigned long long ticks;
>> +
>> +     ticks = nsecs;
>> +     ticks *= p->tick_hz;
>> +     do_div(ticks, 1000000000);
>> +     return ticks;
>> +}
>> +EXPORT_SYMBOL(pwm_ns_to_ticks);
>> +
>> +unsigned long pwm_ticks_to_ns(struct pwm_channel *p,
>> +                           unsigned long ticks)
>> +{
>> +     unsigned long long ns;
>> +
>> +     if (!p->tick_hz)
>> +             return 0;
>> +
>> +     ns = ticks;
>> +     ns *= 1000000000UL;
>> +     do_div(ns, p->tick_hz);
>> +     return ns;
>> +}
>> +EXPORT_SYMBOL(pwm_ticks_to_ns);
>> +
>> +static void
>> +pwm_config_ns_to_ticks(struct pwm_channel *p,
>> +                    struct pwm_channel_config *c)
>> +{
>> +     if (c->config_mask & PWM_CONFIG_PERIOD_NS) {
>> +             c->period_ticks = pwm_ns_to_ticks(p, c->period_ns);
>> +             c->config_mask &= ~PWM_CONFIG_PERIOD_NS;
>> +             c->config_mask |= PWM_CONFIG_PERIOD_TICKS;
>> +     }
>> +
>> +     if (c->config_mask & PWM_CONFIG_DUTY_NS) {
>> +             c->duty_ticks = pwm_ns_to_ticks(p, c->duty_ns);
>> +             c->config_mask &= ~PWM_CONFIG_DUTY_NS;
>> +             c->config_mask |= PWM_CONFIG_DUTY_TICKS;
>> +     }
>> +}
>> +
>> +static void
>> +pwm_config_percent_to_ticks(struct pwm_channel *p,
>> +                         struct pwm_channel_config *c)
>> +{
>> +     if (c->config_mask & PWM_CONFIG_DUTY_PERCENT) {
>> +             if (c->config_mask & PWM_CONFIG_PERIOD_TICKS)
>> +                     c->duty_ticks = c->period_ticks;
>> +             else
>> +                     c->duty_ticks = p->period_ticks;
>> +
>> +             c->duty_ticks *= c->duty_percent;
>> +             c->duty_ticks /= 100;
>> +             c->config_mask &= ~PWM_CONFIG_DUTY_PERCENT;
>> +             c->config_mask |= PWM_CONFIG_DUTY_TICKS;
>> +     }
>> +}
>> +
>> +int pwm_config_nosleep(struct pwm_channel *p,
>> +                    struct pwm_channel_config *c)
>> +{
>> +     if (!p->pwm->config_nosleep)
>> +             return -EINVAL;
>> +
>> +     pwm_config_ns_to_ticks(p, c);
>> +     pwm_config_percent_to_ticks(p, c);
>> +
>> +     return p->pwm->config_nosleep(p, c);
>> +}
>> +EXPORT_SYMBOL(pwm_config_nosleep);
>> +
>> +int pwm_config(struct pwm_channel *p,
>> +            struct pwm_channel_config *c)
>> +{
>> +     int ret = 0;
>> +
>> +     if (unlikely(!p->pwm->config))
>> +             return -EINVAL;
>> +
>> +     pwm_config_ns_to_ticks(p, c);
>> +     pwm_config_percent_to_ticks(p, c);
>> +
>> +     switch (c->config_mask & (PWM_CONFIG_PERIOD_TICKS
>> +                               | PWM_CONFIG_DUTY_TICKS)) {
>> +     case PWM_CONFIG_PERIOD_TICKS:
>> +             if (p->duty_ticks > c->period_ticks) {
>> +                     ret = -EINVAL;
>> +                     goto err;
>> +             }
>> +             break;
>> +     case PWM_CONFIG_DUTY_TICKS:
>> +             if (p->period_ticks < c->duty_ticks) {
>> +                     ret = -EINVAL;
>> +                     goto err;
>> +             }
>> +             break;
>> +     case PWM_CONFIG_DUTY_TICKS | PWM_CONFIG_PERIOD_TICKS:
>> +             if (c->duty_ticks > c->period_ticks) {
>> +                     ret = -EINVAL;
>> +                     goto err;
>> +             }
>> +             break;
>> +     default:
>> +             break;
>> +     }
>> +
>> +err:
>> +     if (ret)
>> +             return ret;
>> +     return p->pwm->config(p, c);
>> +}
>> +EXPORT_SYMBOL(pwm_config);
>> +
>> +int pwm_set_period_ns(struct pwm_channel *p,
>> +                   unsigned long period_ns)
>> +{
>> +     struct pwm_channel_config c = {
>> +             .config_mask = PWM_CONFIG_PERIOD_TICKS,
>> +             .period_ticks = pwm_ns_to_ticks(p, period_ns),
>> +     };
>> +
>> +     return pwm_config(p, &c);
>> +}
>> +EXPORT_SYMBOL(pwm_set_period_ns);
>> +
>> +unsigned long pwm_get_period_ns(struct pwm_channel *p)
>> +{
>> +     return pwm_ticks_to_ns(p, p->period_ticks);
>> +}
>> +EXPORT_SYMBOL(pwm_get_period_ns);
>> +
>> +int pwm_set_duty_ns(struct pwm_channel *p,
>> +                 unsigned long duty_ns)
>> +{
>> +     struct pwm_channel_config c = {
>> +             .config_mask = PWM_CONFIG_DUTY_TICKS,
>> +             .duty_ticks = pwm_ns_to_ticks(p, duty_ns),
>> +     };
>> +     return pwm_config(p, &c);
>> +}
>> +EXPORT_SYMBOL(pwm_set_duty_ns);
>> +
>> +unsigned long pwm_get_duty_ns(struct pwm_channel *p)
>> +{
>> +     return pwm_ticks_to_ns(p, p->duty_ticks);
>> +}
>> +EXPORT_SYMBOL(pwm_get_duty_ns);
>> +
>> +int pwm_set_duty_percent(struct pwm_channel *p,
>> +                      int percent)
>> +{
>> +     struct pwm_channel_config c = {
>> +             .config_mask = PWM_CONFIG_DUTY_PERCENT,
>> +             .duty_percent = percent,
>> +     };
>> +     return pwm_config(p, &c);
>> +}
>> +EXPORT_SYMBOL(pwm_set_duty_percent);
>> +
>> +int pwm_set_polarity(struct pwm_channel *p,
>> +                  int active_high)
>> +{
>> +     struct pwm_channel_config c = {
>> +             .config_mask = PWM_CONFIG_POLARITY,
>> +             .polarity = !!active_high,
>> +     };
>> +     return pwm_config(p, &c);
>> +}
>> +EXPORT_SYMBOL(pwm_set_polarity);
>> +
>> +int pwm_start(struct pwm_channel *p)
>> +{
>> +     struct pwm_channel_config c = {
>> +             .config_mask = PWM_CONFIG_START,
>> +     };
>> +     return pwm_config(p, &c);
>> +}
>> +EXPORT_SYMBOL(pwm_start);
>> +
>> +int pwm_stop(struct pwm_channel *p)
>> +{
>> +     struct pwm_channel_config c = {
>> +             .config_mask = PWM_CONFIG_STOP,
>> +     };
>> +     return pwm_config(p, &c);
>> +}
>> +EXPORT_SYMBOL(pwm_stop);
>> +
>> +int pwm_synchronize(struct pwm_channel *p,
>> +                 struct pwm_channel *to_p)
>> +{
>> +     if (p->pwm != to_p->pwm) {
>> +             /* TODO: support cross-device synchronization */
>> +             return -EINVAL;
>> +     }
>> +
>> +     if (!p->pwm->synchronize)
>> +             return -EINVAL;
>> +
>> +     return p->pwm->synchronize(p, to_p);
>> +}
>> +EXPORT_SYMBOL(pwm_synchronize);
>> +
>> +int pwm_unsynchronize(struct pwm_channel *p,
>> +                   struct pwm_channel *from_p)
>> +{
>> +     if (from_p && (p->pwm != from_p->pwm)) {
>> +             /* TODO: support cross-device synchronization */
>> +             return -EINVAL;
>> +     }
>> +
>> +     if (!p->pwm->unsynchronize)
>> +             return -EINVAL;
>> +
>> +     return p->pwm->unsynchronize(p, from_p);
>> +}
>> +EXPORT_SYMBOL(pwm_unsynchronize);
>> +
>> +static void pwm_handler(struct work_struct *w)
>> +{
>> +     struct pwm_channel *p = container_of(w, struct pwm_channel,
>> +                                          handler_work);
>> +     if (p->handler && p->handler(p, p->handler_data))
>> +             pwm_stop(p);
>> +}
>> +
>> +static void __pwm_callback(struct pwm_channel *p)
>> +{
>> +     queue_work(pwm_handler_workqueue, &p->handler_work);
>> +}
>> +
>> +int pwm_set_handler(struct pwm_channel *p,
>> +                 pwm_handler_t handler,
>> +                 void *data)
>> +{
>> +     if (p->pwm->set_callback) {
>> +             p->handler_data = data;
>> +             p->handler = handler;
>> +             INIT_WORK(&p->handler_work, pwm_handler);
>> +             return p->pwm->set_callback(p, handler ? __pwm_callback : NULL);
>> +     }
>> +     return -EINVAL;
>> +}
>> +EXPORT_SYMBOL(pwm_set_handler);
>> +
>> +static ssize_t pwm_run_store(struct device *dev,
>> +                          struct device_attribute *attr,
>> +                          const char *buf,
>> +                          size_t len)
>> +{
>> +     struct pwm_channel *p = dev_get_drvdata(dev);
>> +     if (sysfs_streq(buf, "1"))
>> +             pwm_start(p);
>> +     else if (sysfs_streq(buf, "0"))
>> +             pwm_stop(p);
>> +     return len;
>> +}
>> +static DEVICE_ATTR(run, 0200, NULL, pwm_run_store);
>> +
>> +static ssize_t pwm_duty_ns_show(struct device *dev,
>> +                             struct device_attribute *attr,
>> +                             char *buf)
>> +{
>> +     struct pwm_channel *p = dev_get_drvdata(dev);
>> +     return sprintf(buf, "%lu\n", pwm_get_duty_ns(p));
>> +}
>> +
>> +static ssize_t pwm_duty_ns_store(struct device *dev,
>> +                              struct device_attribute *attr,
>> +                              const char *buf,
>> +                              size_t len)
>> +{
>> +     unsigned long duty_ns;
>> +     struct pwm_channel *p = dev_get_drvdata(dev);
>> +
>> +     if (1 == sscanf(buf, "%lu", &duty_ns))
>> +             pwm_set_duty_ns(p, duty_ns);
>> +     return len;
>> +}
>> +static DEVICE_ATTR(duty_ns, 0644, pwm_duty_ns_show, pwm_duty_ns_store);
>> +
>> +static ssize_t pwm_period_ns_show(struct device *dev,
>> +                               struct device_attribute *attr,
>> +                               char *buf)
>> +{
>> +     struct pwm_channel *p = dev_get_drvdata(dev);
>> +     return sprintf(buf, "%lu\n", pwm_get_period_ns(p));
>> +}
>> +
>> +static ssize_t pwm_period_ns_store(struct device *dev,
>> +                                struct device_attribute *attr,
>> +                                const char *buf,
>> +                                size_t len)
>> +{
>> +     unsigned long period_ns;
>> +     struct pwm_channel *p = dev_get_drvdata(dev);
>> +
>> +     if (1 == sscanf(buf, "%lu", &period_ns))
>> +             pwm_set_period_ns(p, period_ns);
>> +     return len;
>> +}
>> +static DEVICE_ATTR(period_ns, 0644, pwm_period_ns_show, pwm_period_ns_store);
>> +
>> +static ssize_t pwm_polarity_show(struct device *dev,
>> +                              struct device_attribute *attr,
>> +                              char *buf)
>> +{
>> +     struct pwm_channel *p = dev_get_drvdata(dev);
>> +     return sprintf(buf, "%d\n", !!p->active_high);
>> +}
>> +
>> +static ssize_t pwm_polarity_store(struct device *dev,
>> +                               struct device_attribute *attr,
>> +                               const char *buf,
>> +                               size_t len)
>> +{
>> +     int polarity;
>> +     struct pwm_channel *p = dev_get_drvdata(dev);
>> +
>> +     if (1 == sscanf(buf, "%d", &polarity))
>> +             pwm_set_polarity(p, polarity);
>> +     return len;
>> +}
>> +static DEVICE_ATTR(polarity, 0644, pwm_polarity_show, pwm_polarity_store);
>> +
>> +static ssize_t pwm_request_show(struct device *dev,
>> +                             struct device_attribute *attr,
>> +                             char *buf)
>> +{
>> +     struct pwm_channel *p = dev_get_drvdata(dev);
>> +     mutex_lock(&device_list_mutex);
>> +     __pwm_request_channel(p, REQUEST_SYSFS);
>> +     mutex_unlock(&device_list_mutex);
>> +
>> +     if (p->pid)
>> +             return sprintf(buf, "%s %d\n", p->requester, p->pid);
>> +     else
>> +             return sprintf(buf, "%s\n", p->requester);
>> +}
>> +
>> +static ssize_t pwm_request_store(struct device *dev,
>> +                              struct device_attribute *attr,
>> +                              const char *buf,
>> +                              size_t len)
>> +{
>> +     struct pwm_channel *p = dev_get_drvdata(dev);
>> +     pwm_release(p);
>> +     return len;
>> +}
>> +static DEVICE_ATTR(request, 0644, pwm_request_show, pwm_request_store);
>> +
>> +static const struct attribute *pwm_attrs[] =
>> +{
>> +     &dev_attr_run.attr,
>> +     &dev_attr_polarity.attr,
>> +     &dev_attr_duty_ns.attr,
>> +     &dev_attr_period_ns.attr,
>> +     &dev_attr_request.attr,
>> +     NULL,
>> +};
>> +
>> +static const struct attribute_group pwm_device_attr_group = {
>> +     .attrs = (struct attribute **)pwm_attrs,
>> +};
>> +
>> +static int __pwm_create_sysfs(struct pwm_device *pwm)
>> +{
>> +     int ret = 0;
>> +     struct device *dev;
>> +     int wchan;
>> +
>> +     for (wchan = 0; wchan < pwm->nchan; wchan++) {
>> +             dev = device_create(&pwm_class, pwm->dev, MKDEV(0, 0),
>> +                                 pwm->channels + wchan,
>> +                                 "%s:%d", pwm->bus_id, wchan);
>> +             if (!dev)
>> +                     goto err_dev_create;
>> +             ret = sysfs_create_group(&dev->kobj, &pwm_device_attr_group);
>> +             if (ret)
>> +                     goto err_dev_create;
>> +     }
>> +
>> +     return ret;
>> +
>> +err_dev_create:
>> +     for (wchan = 0; wchan < pwm->nchan; wchan++) {
>> +             dev = class_find_device(&pwm_class, NULL,
>> +                                     &pwm->channels[wchan],
>> +                                     __match_device);
>> +             if (dev) {
>> +                     put_device(dev);
>> +                     device_unregister(dev);
>> +             }
>> +     }
>> +
>> +     return ret;
>> +}
>> +
>> +static struct class_attribute pwm_class_attrs[] = {
>> +     __ATTR_NULL,
>> +};
>> +
>> +static struct class pwm_class = {
>> +     .name = "pwm",
>> +     .owner = THIS_MODULE,
>> +
>> +     .class_attrs = pwm_class_attrs,
>> +};
>> +
>> +static int __init pwm_init(void)
>> +{
>> +     int ret;
>> +
>> +     /* TODO: how to deal with devices that register very early? */
>> +     ret = class_register(&pwm_class);
>> +     if (ret < 0)
>> +             return ret;
>> +
>> +     pwm_handler_workqueue = create_workqueue("pwmd");
>> +
>> +     return 0;
>> +}
>> +postcore_initcall(pwm_init);
>> diff --git a/include/linux/pwm.h b/include/linux/pwm.h
>> deleted file mode 100644
>> index 7c77575..0000000
>> --- a/include/linux/pwm.h
>> +++ /dev/null
>> @@ -1,31 +0,0 @@
>> -#ifndef __LINUX_PWM_H
>> -#define __LINUX_PWM_H
>> -
>> -struct pwm_device;
>> -
>> -/*
>> - * pwm_request - request a PWM device
>> - */
>> -struct pwm_device *pwm_request(int pwm_id, const char *label);
>> -
>> -/*
>> - * pwm_free - free a PWM device
>> - */
>> -void pwm_free(struct pwm_device *pwm);
>> -
>> -/*
>> - * pwm_config - change a PWM device configuration
>> - */
>> -int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns);
>> -
>> -/*
>> - * pwm_enable - start a PWM output toggling
>> - */
>> -int pwm_enable(struct pwm_device *pwm);
>> -
>> -/*
>> - * pwm_disable - stop a PWM output toggling
>> - */
>> -void pwm_disable(struct pwm_device *pwm);
>> -
>> -#endif /* __LINUX_PWM_H */
>> diff --git a/include/linux/pwm/pwm.h b/include/linux/pwm/pwm.h
>> new file mode 100644
>> index 0000000..a10824c
>> --- /dev/null
>> +++ b/include/linux/pwm/pwm.h
>> @@ -0,0 +1,128 @@
>> +/*
>> + * include/linux/pwm.h
>> + *
>> + * Copyright (C) 2010 Bill Gatliff < bgat@xxxxxxxxxxxxxxx>
>> + *
>> + * This program is free software; you may redistribute and/or modify
>> + * it under the terms of the GNU General Public License version 2, as
>> + * published by the Free Software Foundation.
>> + *
>> + * This program is distributed in the hope that it will be useful, but
>> + * WITHOUT ANY WARRANTY; without even the implied warranty of
>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
>> + * General Public License for more details.
>> + *
>> + * You should have received a copy of the GNU General Public License
>> + * along with this program; if not, write to the Free Software
>> + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
>> + * USA
>> + */
>> +#ifndef __LINUX_PWM_H
>> +#define __LINUX_PWM_H
>> +
>> +enum {
>> +     PWM_CONFIG_DUTY_TICKS = BIT(0),
>> +     PWM_CONFIG_PERIOD_TICKS = BIT(1),
>> +     PWM_CONFIG_POLARITY = BIT(2),
>> +     PWM_CONFIG_START = BIT(3),
>> +     PWM_CONFIG_STOP = BIT(4),
>> +
>> +     PWM_CONFIG_HANDLER = BIT(5),
>> +
>> +     PWM_CONFIG_DUTY_NS = BIT(6),
>> +     PWM_CONFIG_DUTY_PERCENT = BIT(7),
>> +     PWM_CONFIG_PERIOD_NS = BIT(8),
>> +};
>> +
>> +struct pwm_channel;
>> +struct work_struct;
>> +
>> +typedef int (*pwm_handler_t)(struct pwm_channel *p, void *data);
>> +typedef void (*pwm_callback_t)(struct pwm_channel *p);
>> +
>> +struct pwm_channel_config {
>> +     int config_mask;
>> +     unsigned long duty_ticks;
>> +     unsigned long period_ticks;
>> +     int polarity;
>> +
>> +     pwm_handler_t handler;
>> +
>> +     unsigned long duty_ns;
>> +     unsigned long period_ns;
>> +     int duty_percent;
>> +};
>> +
>> +struct pwm_device {
>> +     struct list_head list;
>> +     spinlock_t list_lock;
>> +     struct device *dev;
>> +     struct module *owner;
>> +     struct pwm_channel *channels;
>> +
>> +     const char *bus_id;
>> +     int nchan;
>> +
>> +     int     (*request)      (struct pwm_channel *p);
>> +     void    (*release)      (struct pwm_channel *p);
>> +     int     (*config)       (struct pwm_channel *p, struct pwm_channel_config *c);
>> +     int     (*config_nosleep)(struct pwm_channel *p, struct pwm_channel_config *c);
>> +     int     (*synchronize)  (struct pwm_channel *p, struct pwm_channel *to_p);
>> +     int     (*unsynchronize)(struct pwm_channel *p, struct pwm_channel *from_p);
>> +     int     (*set_callback) (struct pwm_channel *p, pwm_callback_t callback);
>> +};
>> +
>> +int pwm_register(struct pwm_device *pwm);
>> +int pwm_unregister(struct pwm_device *pwm);
>> +
>> +enum {
>> +     FLAG_REQUESTED = 0,
>> +     FLAG_STOP = 1,
>> +};
>> +
>> +struct pwm_channel {
>> +     struct list_head list;
>> +     struct pwm_device *pwm;
>> +     const char *requester;
>> +     pid_t pid;
>> +     int chan;
>> +     unsigned long flags;
>> +     unsigned long tick_hz;
>> +
>> +     spinlock_t lock;
>> +     struct completion complete;
>> +
>> +     pwm_callback_t callback;
>> +
>> +     struct work_struct handler_work;
>> +     pwm_handler_t handler;
>> +     void *handler_data;
>> +
>> +     int active_high;
>> +     unsigned long period_ticks;
>> +     unsigned long duty_ticks;
>> +};
>> +
>> +struct gpio_pwm_platform_data {
>> +     int gpio;
>> +};
>> +
>> +struct pwm_channel *pwm_request(const char *bus_id, int chan, const char *requester);
>> +void pwm_release(struct pwm_channel *pwm);
>> +int pwm_config_nosleep(struct pwm_channel *pwm, struct pwm_channel_config *c);
>> +int pwm_config(struct pwm_channel *pwm, struct pwm_channel_config *c);
>> +unsigned long pwm_ns_to_ticks(struct pwm_channel *pwm, unsigned long nsecs);
>> +unsigned long pwm_ticks_to_ns(struct pwm_channel *pwm, unsigned long ticks);
>> +int pwm_set_period_ns(struct pwm_channel *pwm, unsigned long period_ns);
>> +unsigned long pwm_get_period_ns(struct pwm_channel *pwm);
>> +int pwm_set_duty_ns(struct pwm_channel *pwm, unsigned long duty_ns);
>> +int pwm_set_duty_percent(struct pwm_channel *pwm, int percent);
>> +unsigned long pwm_get_duty_ns(struct pwm_channel *pwm);
>> +int pwm_set_polarity(struct pwm_channel *pwm, int active_high);
>> +int pwm_start(struct pwm_channel *pwm);
>> +int pwm_stop(struct pwm_channel *pwm);
>> +int pwm_set_handler(struct pwm_channel *pwm, pwm_handler_t handler, void *data);
>> +int pwm_synchronize(struct pwm_channel *p, struct pwm_channel *to_p);
>> +int pwm_unsynchronize(struct pwm_channel *p, struct pwm_channel *from_p);
>> +
>> +#endif /* __LINUX_PWM_H */
> --
> To unsubscribe from this list: send the line "unsubscribe linux-embedded" in
> the body of a message to majordomo@xxxxxxxxxxxxxxx
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
>
--
To unsubscribe from this list: send the line "unsubscribe linux-embedded" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [Gstreamer Embedded]     [Linux MMC Devel]     [U-Boot V2]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux ARM Kernel]     [Linux OMAP]     [Linux SCSI]

  Powered by Linux