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/pwm
directory 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. Ifchip->base
is 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 toof_pwm_simple_xlate
by the PWM core, which will forceof_pwm_n_cells
to2
as well.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 indrivers/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,chip
is the PWM chip andpwm
is the target PWM device on the chip. Bothchip
andpwm
are input parameters.state
is 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.config
,set_polarity
,enable
, anddisable
: 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 isTHIS_MODULE
.
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 pwmchip_remove()
.
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 to_fake_chip
:
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 pwmchip_add()
.
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.pwm
is 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:
While period
represents the PWM device’s initial period in nanoseconds, polarity must be one of the values in the following enum
:
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_request()
and pwm_free()
still exist in version 5.10 of the Linux kernel, it is strongly recommended to use the pwm_get()
and 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:
Neither pwm_request()
/pwm_get()
nor pwm_free()
/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_get()
and 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_enable()
and 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 pwm_chip.pwm_ops
field:
pwm_enable()
returns 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
.init
files
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 Documentation/devicetree/bindings/pwm/pwm.txt
):
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 0
to 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:0
: Disabled1
: Enabled
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.
Summary
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.