Generic PWM Device API February 1, 2010 Bill Gatliff 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. ;)