Leveraging the Linux kernel PWM subsystem
Pulse Wide Modulation (PWM) operates like a switch that constantly cycles on and off. In the control and command world, it is a hardware feature that’s used to control servomotors, for voltage regulation purposes, and so on. The most well-known applications of PWM are motor speed control, light dimming, voltage regulation, and, in some cases, frequency conversion.
In this tutorial, we will start by introducing the concept of PWM and working with the PWM controller interface before addressing the consumer interface. Finally, we will discuss PWM from the user space by using the sysfs interface.
In this tutorial, we will cover the following topics:
- Introducing the concept of PWM
- Working with the PWM controller interface
- Dealing with the PWM consumer interface
- Leveraging the PWM interface with sysfs
Introducing the concept of PWM
The following diagram shows the concept of PWM:
Figure 1 – PWM signal representation
The preceding diagram shows a complete PWM cycle and introduces some terms we need to clarify before digging deeper into the kernel PWM framework:
- Ton: This is the duration of the signal in a high state (on).
- Toff: This is the duration of the signal in a low state (off).
- Period: This is the duration of a complete PWM cycle. This is represented in the preceding diagram as Complete cycle. It represents the sum of Ton and Toff of the PWM signal.
- Duty cycle: This is represented as the percentage of the time that the signal remains on during the period of the PWM signal (during a complete cycle).
Different formulas can be used here, as follows:
- PWM period: Ton +Toff
- Duty cycle:
Insert formula 1 here.
The Linux PWM framework has two interfaces:
- Controller interface: This interface exposes the PWM line. Such drivers can be found in
drivers/pwmdirectory in the kernel sources. They drive the PWM chips and expose their PWM lines. They are producers.
- Consumer interface: This interface contains devices and drivers that consume PWM lines that are exposed by the controller. Such drivers can be found anywhere according to their main subsystem (such as backlight drivers). They use helper functions that are exported by the controller using a generic PWM framework.
Either the consumer or producer interface depends on the following header file:
Now that we are familiar with the PWM concept and its terminology, we will learn how to work with the PWM controller interface from the Linux kernel.
Working with the PWM controller interface
Since you need
struct gpio_chip when writing GPIO controller drivers and struct irq_chip when writing IRQ controller drivers, a PWM controller is represented in the kernel as an instance of the
struct pwm_chip structure:
Figure 2 – The PWM controller and client interface
The following code shows the declaration of a PWM chip data structure:
The followings are the meanings of each element in the structure:
dev: This represents the device associated with this chip.
ops: This is a data structure that provides the callback functions this chip exposes to consumer drivers.
base: This is the number of the first PWM line that’s controlled by this chip. If
chip->baseis negative (
< 0), then the kernel will dynamically assign a base.
npwm: This is the number of PWM lines (PWM devices) this chip provides.
of_xlate: This is an optional callback that’s used to request a PWM device, given a DT PWM specifier. If not defined, it will be set to
of_pwm_simple_xlateby the PWM core, which will force
of_pwm_n_cells: This is the number of cells that are expected in the device tree for a PWM specifier.
list: This is a list entry that’s used to insert the PWM chip into a system global list of PWM chips; that is,
pwm_chips, which is defined in
drivers/pwm/core.c. This list can be used by the PWM core for PWM chip lookup purposes.
pwms: This is the array of PWM devices of this chip, allocated by the framework, to consumer drivers.
For a PWM controller/chip to be registered with the system, the driver must call
pwmchip_add() on its data structure once the mandatory fields have been initialized. Unregistering can be done using
pwmchip_remove(), after which the chip won’t be visible to the system anymore. Both functions must be given a filled-in
struct pwm_chip structure as an argument. Their respective prototypes are as follows:
Unlike other framework removal functions that do not have return values,
pwmchip_remove() has a return value, which makes sense if an attempt to unregister is performed while the chip still has a line in use. It returns 0 on success, or
-EBUSY if the chip has a PWM line that’s still in use (still requested).
Each PWM driver must implement some hooks through the
struct pwm_ops field, which is used by the PWM core or the consumer interface to configure and make full use of its PWM devices. While some of them are optional, the main ones are as follows:
Let’s look at each element in this data structure:
request: This is an optional hook that, if provided, is executed during a PWM line request.
free: This is an optional callback that’s run when we’re freeing the PWM device.
apply: This is mandatory and the most important callback. It is invoked to atomically apply a new PWM config. The state argument is a bidirectional parameter that initially hosts the requested PWM config and should be adjusted with the real hardware config.
get_state: This function must return the current PWM state. In terms of its parameters,
chipis the PWM chip and
pwmis the target PWM device on the chip. Both
pwmare input parameters.
stateis an output parameter that must be updated by the driver to reflect the real hardware config. In this parameter, driver must report the period, the polarity, the duty cycle, and the enabled status of the PWM device.
disable: These are all legacy callback operations that have been scheduled for removal. They were invoked to separately set a given PWM parameter (such as the duty cycle, the period length, the polarity, and the enable state). All these operations must now be performed atomically in the apply callback in new drivers.
owner: This is the module that owns this chip. Usually, this is
The two most important callbacks in
struct pwm_ops have a state parameter, which is of the
struct pwm_state type. This data structure is used to host the state of a PMW channel and is defined as follows:
In this PWM state data structure, period is the PWM period in nanoseconds,
duty_cycle is the duty cycle in nanoseconds, polarity is the PWM polarity, and enabled is the enable state of the PWM channel.
In the probe method of the PWM controller driver, it is common practice to grab the device tree resources if needed, initialize the hardware, fill
struct pwm_chip and its struct
pwm_ops, and add the PWM chip with the
pwmchip_add() function. However, when this is done with the PWM chip or when the driver is unloaded, the PWM chip must be removed with
Example of writing a PWM controller driver
Now, let’s summarize what we have learned so far by writing a dummy driver for a PWM controller that has three PWM channels.
First, let’s include the headers we will need for our driver to work:
Now that our headers have been included, let’s define our driver state data structure. It can contain whatever is necessary to track the driver or the device state. In our example, it looks as follows:
Because our PWM chip data structure has been embedded into a bigger driver state structure, we can implement a helper function that will return the driver state data structure (the enclosing structure), given the PWM chip data structure. Let’s call it
Now, let’s start the PWM-related code, starting from the function that will be invoked when a client driver requests a PWM channel:
config, enable, and disable are the legacy callback operations that must be replaced by the apply callback in new drivers. Our implementation example could look like this:
In the preceding snippet, the (bidirectional) state parameter is rounded to a value that is supported by the hardware before it’s applied.
fake_pwm_chip_apply_pwm_state() must apply each element in the state argument; that is, the duty cycle, the period, the polarity and the enable state.
Now that the configuration setter callback has been implemented, we can switch to the getter callback, which can be implemented like so:
Now that the PMW controller operations have been implemented, they can be used to fill an instance of
struct pwm_ops, as shown here:
Now, the next logical function to implement is the probe method, which we can write like so:
The preceding method does nothing but allocate memory for the driver state data structure, initialize a PWM chip data structure, and register the PWM chip using
When the driver is unloaded or if the PWM chip ever leaves the system, the chip must be removed using
pwm_remove() as we do in the driver remove method. This can be implemented like so:
With that, the remove method has been implemented. Now, we need to register our driver and the data structures that are mandatory to probe it, like so:
The preceding code describes the skeleton of a PWM chip driver and the essential callbacks it must expose. Now, we can switch to its description in the device tree, also known as its binding.
Understanding PWM controller binding
When it comes to describing a PWM controller in the DT, the most important property to consider is
#pwm-cells. It represents the number of cells that are used to represent the PWM device of this controller. If you remember the
struct pwm_chip structure, the
of_xlate hook is used to translate a given PWM device specifier. If the hook has not been set,
pwm-cells must be set to
2; otherwise, it should be set with the same value as
of_pwm_n_cells. The following is an example of a PWM controller node in the DT, for an i.MX6 SoC:
While the preceding code shows an example of a concrete PWM controller, the following code corresponds to what our
fake-pwm binding may look like:
With that, you have learned how to implement a dedicated PWM chip driver. We can consider discussing the consumer interface, which is likely the most used part of this framework.
In this section, we learned how to use the device tree for PWM binding. At this stage, you should be able to write a complete PWM chip driver. Now that we are done with the PWM controller interface, let’s learn how to implement client drivers that can request and consume the PWM channels that are exposed by the controller.
Dealing with the PWM consumer interface
Consumers are devices or drivers that use PWM devices that are exposed by PWM chips. A PWM device (that is, a PWM line) is represented in the kernel as an instance of the
struct pwm_device structure. It’s declared like so:
In the previous data structure,
label is the name of the PWM device and
flags represents the flags that are associated with the PWM device.
hwpw is the relative index of the PWM device, local to the chip.
pwmis the system global index of the PWM device,
chip is a PWM chip and is the controller that provides this PWM device.
chip_data is chip-private data that’s associated with this PWM device.
args represents the board-dependent PWM arguments that are attached to this PWM device, which are usually retrieved from the PWM lookup table or device tree. This structure must not be confused with the PWM state. PWM arguments reflect the initial configuration that users want to use on this PWM device rather than the current PWM hardware state. the corresponding data structure is defined as the following:
period represents the PWM device’s initial period in nanoseconds, polarity must be one of the values in the following
Let’s look at each entry in more detail:
PWM_POLARITY_NORMAL: This is the duration of the duty cycle that corresponds to the duration of the high state of the signal, followed by a low signal for the remainder of the pulse period.
PWM_POLARITY_INVERSED: This is the duration of the duty cycle that corresponds to the duration of the low state of the signal, followed by a high signal for the remainder of the pulse period.
state is a data structure that holds the last state that was applied to the PWM channel. It is a data structure whose elements are straightforward and defined as follows:
last is the last implemented state and used for debugging purposes; that is, when
CONFIG_PWM_DEBUG is enabled.
With the evolution of the Linux kernel, the PWM framework has been subject to several changes regarding the way PWM devices are requested and released by the consumer driver. Though the legacy and deprecated
pwm_free() still exist in version 5.10 of the Linux kernel, it is strongly recommended to use the
pwm_put() functions, which are the new APIs. The former is given the consumer device, along with a channel string formatted name, as arguments to request the PWM device, while the latter is given the PWM device to be freed as a parameter:
pwm_put() can be called from an atomic context, since the PWM core uses mutexes for resource sharing, which may sleep.
If the driver requires more than one PWM device, it is recommended for the string that’s been given to
pwm_get() to match the PWM name that’s been assigned to the PWM device in the DT; otherwise, the first declared PWM device will be returned. Once again, it is recommended for new drivers to use the managed version of these APIs –
devm_pwm_put() – even though the former is enough, even on an error path.
After being requested, a PWM device can be configured using
pwm_config(), given the duty cycle (the “on” time) and the period’s duration in nanoseconds:
This API returns
0 on success.
To start/stop toggling the PWM output, drivers can use
pwm_disable(). Both functions take a pointer to
struct pwm_device as a parameter and are wrappers around the hooks that are exposed by the controller through the
0 on success or a negative error code on failure. A good example of a PWM consumer driver is
drivers/leds/leds-pwm.c in the kernel source tree. The following is an example of some consumer code driving a PWM-controlled LED (a backlight, in our case):
The preceding code shows a common use case for a PMW device. It assumes that the following code has been executed in the probe method of the driver:
Because the preceding code used the managed method, the devres core will take care of releasing the PWM device at the appropriate time.
Implementing PWM client binding
PWM devices can be assigned to the consumer from the following places:
- Device trees
- Static lookup tables, in board
This section only addresses the device tree use case as it tends to be the de facto binding method. When assigning a PWM device to a consumer device node, you need to provide the phandle of the controller that this PWM device originates from.
It is recommended to assign PWM devices via a property named
pwms. Since PWMs are named resources, you can provide an optional property,
pwm-names, that contains a list of strings to name each of the PWM devices listed in the
pwms property. If no
pwm-names property is defined, the name of the user node will be used as a fallback. However, drivers that require more than a single PWM device must take care of the
pwm-names property, which maps the name of each PWM device that’s requested by the
pwm_get() call to an index into the
pwms list property. This is the only way for the PWM core to return the right PWM device; otherwise, the first PWM device in the list will always be returned.
The following example shows a PWM-based backlight device. This is an excerpt from some kernel documentation on PWM device binding (see
The PWM specifier typically encodes the chip-relative PWM number and the PWM period in nanoseconds. This is what the
pwms = <&pwm 0 5000000>; line does. Optionally, PWM specifier can also encode an flag (defined in
<dt-bindings/pwm/pwm.h>) in a third cell:
When omitted, the additional flag doesn’t affect the PWM device, which is then considered as non-inverted.
In our excerpt,
0 corresponds to the PWM index that’s relative to the controller, while
5000000 represents the period in nanoseconds. Note that in the preceding example, specifying
pwm-names is redundant because the name “backlight” would be used as a fallback anyway. Therefore, the driver would have to do the following:
The preceding code shows how consumers can drive a PWM channel. At the time of writing, you should be able to request and drive PWM channels from your driver.
Now that we have created the consumer in-kernel interface, we are done with (both the controller and consumer drivers) PWM kernel-side management. However, you might be interested in not writing a client driver. In this case, you can leverage the user space sysfs control interface, as we’ll see in the next section.
Leveraging the PWM user space interface with sysfs
As other subsystems do, the PWM framework supports management from the user space. It does this via sysfs, whose root path is
/sys/class/pwm/. Each PWM controller/chip that’s registered with the system creates a
pwmchip<N> directory entry under this sysfs root path, where
<N> is the base index of the PWM chip. The directory contains the following files:
npwm: This is a read-only file that prints the number of PWM lines (PWM devices) that this chip supports.
export: This is a write-only file that lets you export a PWM device for use with sysfs (this functionality is equivalent to the GPIO sysfs interface).
unexport: This is a write-only file that’s used to unexport a PWM channel from sysfs.
The PWM channels are numbered using indexes from
pwm<n-1>. These numbers are local to the chip. Each PWM channel that’s exported creates a
pwmX directory in
pwmchip<N>, which is the same directory that contains the export file that was used.
X is the index of the channel that was exported. Each channel directory contains the following files:
period: This is a readable/writable file that’s used to get/set the total period of the PWM signal. The value is in nanoseconds.
duty_cycle: This is a readable/writable file that’s used to get/set the duty cycle of the PWM signal. It represents the active time of the PWM signal. The value is in nanoseconds and must always be less than the period.
polarity: This is a readable/writable file that you should only use if the chip of this PWM device supports polarity inversion. It is better to change the polarity when this PWM is not enabled. Its accepted values are normal or inversed.
enable: This is a readable/writable file that’s used to enable (start toggling)/disable (stop toggling) the PWM signal. Its accepted values are as follows:
The following is an example of using PWM from the user space through the sysfs interface:
- Enable a PWM channel:
echo 1 > /sys/class/pwm/pwmchip<pwmchipnr>/pwm<pwmnr>/enable
- Set the PWM period:
echo <value in nanoseconds> > /sys/class/pwm/pwmchip<pwmchipnr>/pwm<pwmnr>/period
- Set the PWM duty cycle, whose value must be less than the value of the PWM period:
echo <value in nanoseconds> > /sys/class/pwm/pwmchip<pwmchipnr>/pwm<pwmnr>/duty_cycle
- Disable PWM:
echo 0 > /sys/class/pwm/pwmchip<pwmchipnr>/pwm<pwmnr>/enable
The complete PWM framework API and sysfs description are available in the
Documentation/pwm.txt file, in the kernel source tree.
At this point, you should be ready to use any PWM controller. The API that was described in this tutorial will be sufficient to write, as well as to enhance, a controller driver or a consumer device driver. If you are not comfortable with the PWM kernel side yet, you can use the user space sysfs interface.