Nothing but Embedded systems

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:

PWM signal representation

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:

#include <linux/pwm.h>

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:

struct pwm_chip {
struct device        *dev;
const struct pwm_ops *ops;
int                  base;
unsigned int         npwm;

struct pwm_device * (*of_xlate)(struct pwm_chip *pc,
const struct of_phandle_args *args);
unsigned int         of_pwm_n_cells;
struct list_head list;
struct pwm_device    *pwms;
};

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->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 to of_pwm_simple_xlate by the PWM core, which will force of_pwm_n_cells to 2 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 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:

int pwmchip_add(struct pwm_chip *chip)
int pwmchip_remove(struct pwm_chip *chip)

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:

struct pwm_ops {
int (*request)(struct pwm_chip *chip,
struct pwm_device *pwm);
void (*free)(struct pwm_chip *chip,
struct pwm_device *pwm);
int (*capture)(struct pwm_chip *chip,
struct pwm_device *pwm,
struct pwm_capture *result,
unsigned long timeout);
int (*apply)(struct pwm_chip *chip,
struct pwm_device *pwm,
const struct pwm_state *state);
void (*get_state)(struct pwm_chip *chip,
struct pwm_device *pwm,
struct pwm_state *state);

struct module   *owner;
int (*config)(struct pwm_chip *chip,
struct pwm_device *pwm,
int duty_ns, int period_ns);
int (*set_polarity)(struct pwm_chip *chip,
struct pwm_device *pwm,
enum pwm_polarity polarity);
int (*enable)(struct pwm_chip *chip,
struct pwm_device *pwm);
void (*disable)(struct pwm_chip *chip,
struct pwm_device *pwm);
};

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 and pwm is the target PWM device on the chip. Both chip and pwm 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, and 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 THIS_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:

struct pwm_state {
u64 period;
u64 duty_cycle;
enum pwm_polarity polarity;
bool enabled;
};

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:

#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pwm.h>

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:

struct fake_chip {
struct pwm_chip chip;
int foo;
int bar;
/* put the client structure here (SPI/I2C) */
};

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:

static inline struct fake_chip *to_fake_chip(struct pwm_chip *chip)
{
return container_of(chip, struct fake_chip, 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:

static int fake_pwm_request(struct pwm_chip *chip,
struct pwm_device *pwm)
{
/*
* One may need to do some initialization when a
* PWM channel of the controller is requested.
* This should be done here.
* One may do something like
*     prepare_pwm_device(struct pwm_chip *chip, pwm->hwpwm);
*/
return 0;
}

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:

static int fake_pwm_apply(struct pwm_chip *chip,
struct pwm_device *pwm,
const struct pwm_state *state)
{
/*
* In this function, one can do something like:
*      fake_pwm_chip_round_pwm_state(chip, state);
*
*      return fake_pwm_chip_apply_pwm_state(chip, pwm,
*                                           state);
*/
return 0;
}

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:

static void fake_pwm_get_state(struct pwm_chip *chip,
struct pwm_device *pwm,
const struct pwm_state *state)
{
/*
* In this function, state is an output-only parameter,
* and driver can do something like the following:
// get period
* state->period = fake_pwm_get_period(chip, pwm);
*
// get duty cycle
* state->duty_cycle = fake_pwm_get_duty_cycle(chip, pwm);
*
// get polarity
* state->polarity =
*   fake_pwm_get_polarity_normal(chip, pwm) ?
*       PWM_POLARITY_NORMAL : PWM_POLARITY_INVERSED;
*
// get channel status
* state->enabled = fake_pwm_chip_pwm_is_on(chip, pwm);
*/
}

Now that the PMW controller operations have been implemented, they can be used to fill an instance of struct pwm_ops, as shown here:

static const struct pwm_ops fake_pwm_ops = {
.request = fake_pwm_request,
.apply = fake_pwm_apply,
.get_state = fake_pwm_get_state,
.owner = THIS_MODULE,
};

Now, the next logical function to implement is the probe method, which we can write like so:

static int fake_pwm_probe(struct platform_device *pdev)
{
struct fake_chip *priv;

priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;

priv->chip.ops = &fake_pwm_ops;
priv->chip.dev = &pdev->dev;
priv->chip.base = -1;  /* Dynamic base */
/* This controller has 3 PWM devices */
priv->chip.npwm = 3;

platform_set_drvdata(pdev, priv);

return pwmchip_add(&priv->chip);
}

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:

static int fake_pwm_remove(struct platform_device *pdev)
{
struct fake_chip *priv = platform_get_drvdata(pdev);
return pwmchip_remove(&priv->chip);
}

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:

static const struct of_device_id fake_pwm_dt_ids[] = {
{ .compatible = "packt,fake-pwm", },
{ }
};

MODULE_DEVICE_TABLE(of, fake_pwm_dt_ids);

static struct platform_driver fake_pwm_driver = {
.driver = {
.name = KBUILD_MODNAME,
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(fake_pwm_dt_ids),
},
.probe = fake_pwm_probe,
.remove = fake_pwm_remove,
};

module_platform_driver(fake_pwm_driver);

MODULE_AUTHOR("John Madieu <john.madieu@gmail.com>");
MODULE_DESCRIPTION("Fake pwm driver");
MODULE_LICENSE("GPL");

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:

pwm3: pwm@02088000 {
#pwm-cells = <2>;
compatible = "fsl,imx6q-pwm", "fsl,imx27-pwm";
reg = <0x02088000 0x4000>;
interrupts = <0 85 IRQ_TYPE_LEVEL_HIGH>;

[...]
status = "disabled";
};

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:

fake_pwm: pwm@0 {
#pwm-cells = <2>;
compatible = "packt,fake-pwm";

/*
* Our driver does not use resource
* neither mem, IRQ, nor Clock)
*/
};

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:

struct pwm_device {
const char *label;
unsigned long flags;
unsigned int hwpwm;
unsigned int pwm;
struct pwm_chip *chip;
void *chip_data;

struct pwm_args args;
struct pwm_state state;
struct pwm_state last;
};

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:

struct pwm_args {
u64 period; /* Device’s initial period*/
enum pwm_polarity polarity;
};

While period represents the PWM device’s initial period in nanoseconds, polarity must be one of the values in the following enum:

enum pwm_polarity {
PWM_POLARITY_NORMAL,
PWM_POLARITY_INVERSED,
};

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:

structl pwm_state {
unsigned int period; /* PWM period in nanoseconds */
unsigned int duty_cycle; /*PWM duty cycle in nanoseconds */
enum pwm_polarity polarity; /* PWM polarity */
bool enabled; /* PWM enabled status */
}

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:

struct pwm_device *pwm_get(struct device *dev,
const char *con_id)

void pwm_put(struct pwm_device *pwm)

 

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:

int pwm_config(struct pwm_device *pwm, int duty_ns, int period_ns);

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:

int pwm_enable(struct pwm_device *pwm)
void pwm_disable(struct pwm_device *pwm)

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):

#define PWM_NAME           "lcd-backlight"

static void pwm_led_drive(struct pwm_device *pwm,
struct private_data *priv)
{
/* Configure the PWM, applying a period and duty cycle */
pwm_config(pwm, priv->duty, priv->pwm_period);

/* Start toggling */
pwm_enable(pchip->pwmd);

[...] /* Do some work */

/* And then stop toggling*/
pwm_disable(pchip->pwmd);
}

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:

struct pwm_device *pwm;
pwm = devm_pwm_get(dev, PWM_NAME);
if (IS_ERR(pwm)) {
dev_err(dev, "cannot get PWM device\n");
return PTR_ERR(pwm);
}

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):

pwm: pwm {
#pwm-cells = <2>;
Compatible = “manufacturer,controller-compatible-string”
};

[...]

bl: backlight {
pwms = <&pwm 0 5000000>;
pwm-names = "backlight";
compatible = “my-compatible-string”
};

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:

bl: backlight {
pwms = <&pwm 0 5000000 PWM_POLARITY_INVERTED>;
pwm-names = "backlight";
compatible = “my-compatible-string”
};

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:

static int my_consummer_probe(struct platform_device *pdev)
{
struct pwm_device *pwm;


pwm = devm_pwm_get(&pdev->dev, "backlight");
if (IS_ERR(pwm)) {
pr_err("unable to request PWM, trying legacy API\n");
return PTR_ERR(pwm);
/*
* Some drivers use the legacy API as fallback,
* in order to request a PWM ID, global to
* the system: pwm = pwm_request(global_pwm_id,
* "pwm beeper");
*/
}

[...]
return 0;
}

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: Disabled
    • 1: 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.

Your email address will not be published. Required fields are marked *