The files have last been used with 2.6.x kernel. Signed-off-by: Luka Perkov <luka@openwrt.org> SVN-Revision: 37810master
parent
a2dccb958c
commit
4f679c1775
@ -1,260 +0,0 @@ |
|||||||
Generic PWM Device API |
|
||||||
|
|
||||||
February 1, 2010 |
|
||||||
Bill Gatliff |
|
||||||
<bgat@billgatliff.com> |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
The code in drivers/pwm and include/linux/pwm/ implements an API for |
|
||||||
applications involving pulse-width-modulation signals. This document |
|
||||||
describes how the API implementation facilitates both PWM-generating |
|
||||||
devices, and users of those devices. |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Motivation |
|
||||||
|
|
||||||
The primary goals for implementing the "generic PWM API" are to |
|
||||||
consolidate the various PWM implementations within a consistent and |
|
||||||
redundancy-reducing framework, and to facilitate the use of |
|
||||||
hotpluggable PWM devices. |
|
||||||
|
|
||||||
Previous PWM-related implementations within the Linux kernel achieved |
|
||||||
their consistency via cut-and-paste, but did not need to (and didn't) |
|
||||||
facilitate more than one PWM-generating device within the system--- |
|
||||||
hotplug or otherwise. The Generic PWM Device API might be most |
|
||||||
appropriately viewed as an update to those implementations, rather |
|
||||||
than a complete rewrite. |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Challenges |
|
||||||
|
|
||||||
One of the difficulties in implementing a generic PWM framework is the |
|
||||||
fact that pulse-width-modulation applications involve real-world |
|
||||||
signals, which often must be carefully managed to prevent destruction |
|
||||||
of hardware that is linked to those signals. A DC motor that |
|
||||||
experiences a brief interruption in the PWM signal controlling it |
|
||||||
might destructively overheat; it could suddenly change speed, losing |
|
||||||
synchronization with a sensor; it could even suddenly change direction |
|
||||||
or torque, breaking the mechanical device connected to it. |
|
||||||
|
|
||||||
(A generic PWM device framework is not directly responsible for |
|
||||||
preventing the above scenarios: that responsibility lies with the |
|
||||||
hardware designer, and the application and driver authors. But it |
|
||||||
must to the greatest extent possible make it easy to avoid such |
|
||||||
problems). |
|
||||||
|
|
||||||
A generic PWM device framework must accommodate the substantial |
|
||||||
differences between available PWM-generating hardware devices, without |
|
||||||
becoming sub-optimal for any of them. |
|
||||||
|
|
||||||
Finally, a generic PWM device framework must be relatively |
|
||||||
lightweight, computationally speaking. Some PWM users demand |
|
||||||
high-speed outputs, plus the ability to regulate those outputs |
|
||||||
quickly. A device framework must be able to "keep up" with such |
|
||||||
hardware, while still leaving time to do real work. |
|
||||||
|
|
||||||
The Generic PWM Device API is an attempt to meet all of the above |
|
||||||
requirements. At its initial publication, the API was already in use |
|
||||||
managing small DC motors, sensors and solenoids through a |
|
||||||
custom-designed, optically-isolated H-bridge driver. |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Functional Overview |
|
||||||
|
|
||||||
The Generic PWM Device API framework is implemented in |
|
||||||
include/linux/pwm/pwm.h and drivers/pwm/pwm.c. The functions therein |
|
||||||
use information from pwm_device, pwm_channel and pwm_channel_config |
|
||||||
structures to invoke services in PWM peripheral device drivers. |
|
||||||
Consult drivers/pwm/atmel-pwm.c for an example driver. |
|
||||||
|
|
||||||
There are two classes of adopters of the PWM framework: |
|
||||||
|
|
||||||
"Users" -- those wishing to employ the API merely to produce PWM |
|
||||||
signals; once they have identified the appropriate physical output |
|
||||||
on the platform in question, they don't care about the details of |
|
||||||
the underlying hardware |
|
||||||
|
|
||||||
"Driver authors" -- those wishing to bind devices that can generate |
|
||||||
PWM signals to the Generic PWM Device API, so that the services of |
|
||||||
those devices become available to users. Assuming the hardware can |
|
||||||
support the needs of a user, driver authors don't care about the |
|
||||||
details of the user's application |
|
||||||
|
|
||||||
Generally speaking, users will first invoke pwm_request() to obtain a |
|
||||||
handle to a PWM device. They will then pass that handle to functions |
|
||||||
like pwm_duty_ns() and pwm_period_ns() to set the duty cycle and |
|
||||||
period of the PWM signal, respectively. They will also invoke |
|
||||||
pwm_start() and pwm_stop() to turn the signal on and off. |
|
||||||
|
|
||||||
The Generic PWM API framework also provides a sysfs interface to PWM |
|
||||||
devices, which is adequate for basic application needs and testing. |
|
||||||
|
|
||||||
Driver authors fill out a pwm_device structure, which describes the |
|
||||||
capabilities of the PWM hardware being constructed--- including the |
|
||||||
number of distinct output "channels" the peripheral offers. They then |
|
||||||
invoke pwm_register() (usually from within their device's probe() |
|
||||||
handler) to make the PWM API aware of their device. The framework |
|
||||||
will call back to the methods described in the pwm_device structure as |
|
||||||
users begin to configure and utilize the hardware. |
|
||||||
|
|
||||||
Note that PWM signals can be produced by a variety of peripherals, |
|
||||||
beyond the true "PWM hardware" offered by many system-on-chip devices. |
|
||||||
Other possibilities include timer/counters with compare-match |
|
||||||
capabilities, carefully-programmed synchronous serial ports |
|
||||||
(e.g. SPI), and GPIO pins driven by kernel interval timers. With a |
|
||||||
proper pwm_device structure, these devices and pseudo-devices can all |
|
||||||
be accommodated by the Generic PWM Device API framework. |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Using the API to Generate PWM Signals -- Basic Functions for Users |
|
||||||
|
|
||||||
|
|
||||||
pwm_request() -- Returns a pwm_channel pointer, which is subsequently |
|
||||||
passed to the other user-related PWM functions. Once requested, a PWM |
|
||||||
channel is marked as in-use and subsequent requests prior to |
|
||||||
pwm_free() will fail. |
|
||||||
|
|
||||||
The names used to refer to PWM devices are defined by driver authors. |
|
||||||
Typically they are platform device bus identifiers, and this |
|
||||||
convention is encouraged for consistency. |
|
||||||
|
|
||||||
|
|
||||||
pwm_free() -- Marks a PWM channel as no longer in use. The PWM device |
|
||||||
is stopped before it is released by the API. |
|
||||||
|
|
||||||
|
|
||||||
pwm_period_ns() -- Specifies the PWM signal's period, in nanoseconds. |
|
||||||
|
|
||||||
|
|
||||||
pwm_duty_ns() -- Specifies the PWM signal's active duration, in nanoseconds. |
|
||||||
|
|
||||||
|
|
||||||
pwm_duty_percent() -- Specifies the PWM signal's active duration, as a |
|
||||||
percentage of the current period of the signal. NOTE: this value is |
|
||||||
not recalculated if the period of the signal is subsequently changed. |
|
||||||
|
|
||||||
|
|
||||||
pwm_start(), pwm_stop() -- Turns the PWM signal on and off. Except |
|
||||||
where stated otherwise by a driver author, signals are stopped at the |
|
||||||
end of the current period, at which time the output is set to its |
|
||||||
inactive state. |
|
||||||
|
|
||||||
|
|
||||||
pwm_polarity() -- Defines whether the PWM signal output's active |
|
||||||
region is "1" or "0". A 10% duty-cycle, polarity=1 signal will |
|
||||||
conventionally be at 5V (or 3.3V, or 1000V, or whatever the platform |
|
||||||
hardware does) for 10% of the period. The same configuration of a |
|
||||||
polarity=0 signal will be at 5V (or 3.3V, or ...) for 90% of the |
|
||||||
period. |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Using the API to Generate PWM Signals -- Advanced Functions |
|
||||||
|
|
||||||
|
|
||||||
pwm_config() -- Passes a pwm_channel_config structure to the |
|
||||||
associated device driver. This function is invoked by pwm_start(), |
|
||||||
pwm_duty_ns(), etc. and is one of two main entry points to the PWM |
|
||||||
driver for the hardware being used. The configuration change is |
|
||||||
guaranteed atomic if multiple configuration changes are specified. |
|
||||||
This function might sleep, depending on what the device driver has to |
|
||||||
do to satisfy the request. All PWM device drivers must support this |
|
||||||
entry point. |
|
||||||
|
|
||||||
|
|
||||||
pwm_config_nosleep() -- Passes a pwm_channel_config structure to the |
|
||||||
associated device driver. If the driver must sleep in order to |
|
||||||
implement the requested configuration change, -EWOULDBLOCK is |
|
||||||
returned. Users may call this function from interrupt handlers, for |
|
||||||
example. This is the other main entry point into the PWM hardware |
|
||||||
driver, but not all device drivers support this entry point. |
|
||||||
|
|
||||||
|
|
||||||
pwm_synchronize(), pwm_unsynchronize() -- "Synchronizes" two or more |
|
||||||
PWM channels, if the underlying hardware permits. (If it doesn't, the |
|
||||||
framework facilitates emulating this capability but it is not yet |
|
||||||
implemented). Synchronized channels will start and stop |
|
||||||
simultaneously when any single channel in the group is started or |
|
||||||
stopped. Use pwm_unsynchronize(..., NULL) to completely detach a |
|
||||||
channel from any other synchronized channels. By default, all PWM |
|
||||||
channels are unsynchronized. |
|
||||||
|
|
||||||
|
|
||||||
pwm_set_handler() -- Defines an end-of-period callback. The indicated |
|
||||||
function will be invoked in a worker thread at the end of each PWM |
|
||||||
period, and can subsequently invoke pwm_config(), etc. Must be used |
|
||||||
with extreme care for high-speed PWM outputs. Set the handler |
|
||||||
function to NULL to un-set the handler. |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Implementing a PWM Device API Driver -- Functions for Driver Authors |
|
||||||
|
|
||||||
|
|
||||||
Fill out the appropriate fields in a pwm_device structure, and submit |
|
||||||
to pwm_register(): |
|
||||||
|
|
||||||
|
|
||||||
bus_id -- the plain-text name of the device. Users will bind to a |
|
||||||
channel on the device using this name plus the channel number. For |
|
||||||
example, the Atmel PWMC's bus_id is "atmel_pwmc", the same as used by |
|
||||||
the platform device driver (recommended). The first device registered |
|
||||||
thereby receives bus_id "atmel_pwmc.0", which is what you put in |
|
||||||
pwm_device.bus_id. Channels are then named "atmel_pwmc.0:[0-3]". |
|
||||||
(Hint: just use pdev->dev.bus_id in your probe() method). |
|
||||||
|
|
||||||
|
|
||||||
nchan -- the number of distinct output channels provided by the device. |
|
||||||
|
|
||||||
|
|
||||||
request -- (optional) Invoked each time a user requests a channel. |
|
||||||
Use to turn on clocks, clean up register states, etc. The framework |
|
||||||
takes care of device locking/unlocking; you will see only successful |
|
||||||
requests. |
|
||||||
|
|
||||||
|
|
||||||
free -- (optional) Callback for each time a user relinquishes a |
|
||||||
channel. The framework will have already stopped, unsynchronized and |
|
||||||
un-handled the channel. Use to turn off clocks, etc. as necessary. |
|
||||||
|
|
||||||
|
|
||||||
synchronize, unsynchronize -- (optional) Callbacks to |
|
||||||
synchronize/unsynchronize channels. Some devices provide this |
|
||||||
capability in hardware; for others, it can be emulated (see |
|
||||||
atmel_pwmc.c's sync_mask for an example). |
|
||||||
|
|
||||||
|
|
||||||
set_callback -- (optional) Invoked when a user requests a handler. If |
|
||||||
the hardware supports an end-of-period interrupt, invoke the function |
|
||||||
indicated during your interrupt handler. The callback function itself |
|
||||||
is always internal to the API, and does not map directly to the user's |
|
||||||
callback function. |
|
||||||
|
|
||||||
|
|
||||||
config -- Invoked to change the device configuration, always from a |
|
||||||
sleep-capable context. All the changes indicated must be performed |
|
||||||
atomically, ideally synchronized to an end-of-period event (so that |
|
||||||
you avoid short or long output pulses). You may sleep, etc. as |
|
||||||
necessary within this function. |
|
||||||
|
|
||||||
|
|
||||||
config_nosleep -- (optional) Invoked to change device configuration |
|
||||||
from within a context that is not allowed to sleep. If you cannot |
|
||||||
perform the requested configuration changes without sleeping, return |
|
||||||
-EWOULDBLOCK. |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Acknowledgements |
|
||||||
|
|
||||||
|
|
||||||
The author expresses his gratitude to the countless developers who |
|
||||||
have reviewed and submitted feedback on the various versions of the |
|
||||||
Generic PWM Device API code, and those who have submitted drivers and |
|
||||||
applications that use the framework. You know who you are. ;) |
|
||||||
|
|
@ -1,165 +0,0 @@ |
|||||||
/*
|
|
||||||
* include/linux/pwm.h |
|
||||||
* |
|
||||||
* Copyright (C) 2008 Bill Gatliff < bgat@billgatliff.com> |
|
||||||
* |
|
||||||
* This program is free software; you may redistribute and/or modify |
|
||||||
* it under the terms of the GNU General Public License version 2, as |
|
||||||
* published by the Free Software Foundation. |
|
||||||
* |
|
||||||
* This program is distributed in the hope that it will be useful, but |
|
||||||
* WITHOUT ANY WARRANTY; without even the implied warranty of |
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
||||||
* General Public License for more details. |
|
||||||
* |
|
||||||
* You should have received a copy of the GNU General Public License |
|
||||||
* along with this program; if not, write to the Free Software |
|
||||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 |
|
||||||
* USA |
|
||||||
*/ |
|
||||||
#ifndef __LINUX_PWM_H |
|
||||||
#define __LINUX_PWM_H |
|
||||||
|
|
||||||
enum { |
|
||||||
PWM_CONFIG_DUTY_TICKS = BIT(0), |
|
||||||
PWM_CONFIG_PERIOD_TICKS = BIT(1), |
|
||||||
PWM_CONFIG_POLARITY = BIT(2), |
|
||||||
PWM_CONFIG_START = BIT(3), |
|
||||||
PWM_CONFIG_STOP = BIT(4), |
|
||||||
|
|
||||||
PWM_CONFIG_HANDLER = BIT(5), |
|
||||||
|
|
||||||
PWM_CONFIG_DUTY_NS = BIT(6), |
|
||||||
PWM_CONFIG_DUTY_PERCENT = BIT(7), |
|
||||||
PWM_CONFIG_PERIOD_NS = BIT(8), |
|
||||||
}; |
|
||||||
|
|
||||||
struct pwm_channel; |
|
||||||
struct work_struct; |
|
||||||
|
|
||||||
typedef int (*pwm_handler_t)(struct pwm_channel *p, void *data); |
|
||||||
typedef void (*pwm_callback_t)(struct pwm_channel *p); |
|
||||||
|
|
||||||
struct pwm_channel_config { |
|
||||||
int config_mask; |
|
||||||
unsigned long duty_ticks; |
|
||||||
unsigned long period_ticks; |
|
||||||
int polarity; |
|
||||||
|
|
||||||
pwm_handler_t handler; |
|
||||||
|
|
||||||
unsigned long duty_ns; |
|
||||||
unsigned long period_ns; |
|
||||||
int duty_percent; |
|
||||||
}; |
|
||||||
|
|
||||||
struct pwm_device { |
|
||||||
struct list_head list; |
|
||||||
spinlock_t list_lock; |
|
||||||
struct device *dev; |
|
||||||
struct module *owner; |
|
||||||
struct pwm_channel *channels; |
|
||||||
|
|
||||||
const char *bus_id; |
|
||||||
int nchan; |
|
||||||
|
|
||||||
int (*request) (struct pwm_channel *p); |
|
||||||
void (*free) (struct pwm_channel *p); |
|
||||||
int (*config) (struct pwm_channel *p, |
|
||||||
struct pwm_channel_config *c); |
|
||||||
int (*config_nosleep)(struct pwm_channel *p, |
|
||||||
struct pwm_channel_config *c); |
|
||||||
int (*synchronize) (struct pwm_channel *p, |
|
||||||
struct pwm_channel *to_p); |
|
||||||
int (*unsynchronize)(struct pwm_channel *p, |
|
||||||
struct pwm_channel *from_p); |
|
||||||
int (*set_callback) (struct pwm_channel *p, |
|
||||||
pwm_callback_t callback); |
|
||||||
}; |
|
||||||
|
|
||||||
int pwm_register(struct pwm_device *pwm); |
|
||||||
int pwm_unregister(struct pwm_device *pwm); |
|
||||||
|
|
||||||
enum { |
|
||||||
FLAG_REQUESTED = 0, |
|
||||||
FLAG_STOP = 1, |
|
||||||
}; |
|
||||||
|
|
||||||
struct pwm_channel { |
|
||||||
struct list_head list; |
|
||||||
struct pwm_device *pwm; |
|
||||||
const char *requester; |
|
||||||
pid_t pid; |
|
||||||
int chan; |
|
||||||
unsigned long flags; |
|
||||||
unsigned long tick_hz; |
|
||||||
|
|
||||||
spinlock_t lock; |
|
||||||
struct completion complete; |
|
||||||
|
|
||||||
pwm_callback_t callback; |
|
||||||
|
|
||||||
struct work_struct handler_work; |
|
||||||
pwm_handler_t handler; |
|
||||||
void *handler_data; |
|
||||||
|
|
||||||
int active_high; |
|
||||||
unsigned long period_ticks; |
|
||||||
unsigned long duty_ticks; |
|
||||||
}; |
|
||||||
|
|
||||||
struct gpio_pwm_platform_data { |
|
||||||
int gpio; |
|
||||||
}; |
|
||||||
|
|
||||||
struct pwm_channel * |
|
||||||
pwm_request(const char *bus_id, int chan, |
|
||||||
const char *requester); |
|
||||||
|
|
||||||
void pwm_free(struct pwm_channel *pwm); |
|
||||||
|
|
||||||
int pwm_config_nosleep(struct pwm_channel *pwm, |
|
||||||
struct pwm_channel_config *c); |
|
||||||
|
|
||||||
int pwm_config(struct pwm_channel *pwm, |
|
||||||
struct pwm_channel_config *c); |
|
||||||
|
|
||||||
unsigned long pwm_ns_to_ticks(struct pwm_channel *pwm, |
|
||||||
unsigned long nsecs); |
|
||||||
|
|
||||||
unsigned long pwm_ticks_to_ns(struct pwm_channel *pwm, |
|
||||||
unsigned long ticks); |
|
||||||
|
|
||||||
int pwm_set_period_ns(struct pwm_channel *pwm, |
|
||||||
unsigned long period_ns); |
|
||||||
|
|
||||||
unsigned long int pwm_get_period_ns(struct pwm_channel *pwm); |
|
||||||
|
|
||||||
int pwm_set_duty_ns(struct pwm_channel *pwm, |
|
||||||
unsigned long duty_ns); |
|
||||||
|
|
||||||
int pwm_set_duty_percent(struct pwm_channel *pwm, |
|
||||||
int percent); |
|
||||||
|
|
||||||
unsigned long pwm_get_duty_ns(struct pwm_channel *pwm); |
|
||||||
|
|
||||||
int pwm_set_polarity(struct pwm_channel *pwm, |
|
||||||
int active_high); |
|
||||||
|
|
||||||
int pwm_start(struct pwm_channel *pwm); |
|
||||||
|
|
||||||
int pwm_stop(struct pwm_channel *pwm); |
|
||||||
|
|
||||||
int pwm_set_handler(struct pwm_channel *pwm, |
|
||||||
pwm_handler_t handler, |
|
||||||
void *data); |
|
||||||
|
|
||||||
int pwm_synchronize(struct pwm_channel *p, |
|
||||||
struct pwm_channel *to_p); |
|
||||||
|
|
||||||
|
|
||||||
int pwm_unsynchronize(struct pwm_channel *p, |
|
||||||
struct pwm_channel *from_p); |
|
||||||
|
|
||||||
|
|
||||||
#endif /* __LINUX_PWM_H */ |
|
Loading…
Reference in new issue