Raspberry Pi PWM Support in User or Kernel Space - cu-ecen-aeld/buildroot-assignments-base GitHub Wiki

Pulse Width Modulation (PWM) Devices

This section was last tested on Buildroot release 2022.02.11 on a Raspberry Pi 4 (RPi4).

Documentation:


Using sysfs in Userspace

The simplest way to use PWM on the RPi4 from userspace is via the sysfs interface. To enable the PWM controller, add the following to your config.txt:

  • dtoverlay=pwm — for one channel (PWM0)
  • dtoverlay=pwm-2chan — for both channels (PWM0 and PWM1)

Once enabled, PWM devices can be accessed through /sys/class/pwm/pwmchip<number>/. You must export each channel before configuring it.

Example for PWM0:

# Export pwm0
echo 0 > /sys/class/pwm/pwmchip0/export

# Set period in nanoseconds
echo 1000000 > /sys/class/pwm/pwmchip0/pwm0/period

# Set duty cycle in nanoseconds
echo 500000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycle

# Enable PWM output
echo 1 > /sys/class/pwm/pwmchip0/pwm0/enable

# Disable PWM output
echo 0 > /sys/class/pwm/pwmchip0/pwm0/enable

Using a Device Tree Overlay for Kernel Space

To access a PWM device from kernel space, you need to define it in the Device Tree. A working overlay example is shown below. The overlay must be compiled using (dtc -@ -I dts -O dtb -o my-overlay.dtbo my-overlay.dts), then placed in buildroot/output/images/rpi-firmware/overlays, and finally enabled by adding dtoverlay=my-overlay to config.txt. While Buildroot can be configured to automatically build and install overlays, this was not tested at the time of writing.

/dts-v1/;
/plugin/;

/ {
    compatible = "brcm,bcm2835";

    fragment@0 {
        target-path = "/soc/gpio@7e200000";
        __overlay__ {
            my_pwm_pins: my_pwm_pins {
                brcm,pins = <18 19>;
                brcm,function = <2 2>; // ALT5
            };
        };
    };

    fragment@1 {
        target-path = "/soc/pwm@7e20c000";
        __overlay__ {
            status = "okay";
            pinctrl-names = "default";
            pinctrl-0 = <&my_pwm_pins>;
        };
    };

    fragment@2 {
        target-path = "/soc";
        __overlay__ {
            my_pwm_device: my_pwm_device {
                compatible = "mycompany,my-pwm-device";
                pwms = <&pwm 0 20000000>, <&pwm 1 20000000>;
                pwm-names = "pwm0", "pwm1";
                status = "okay";
            };
        };
    };
};
  • Fragment 0 sets up the correct GPIO pinmux for PWM by assigning ALT5 mode to the GPIOs used (typically GPIO18 and GPIO19).
  • Fragment 1 enables the PWM controller and attaches the pin configuration.
  • Fragment 2 defines a custom platform device (my_pwm_device) that uses both PWM channels and assigns human-readable names (pwm0, pwm1).

Fragments 0 and 1 are similar to the pwm-2chan overlay used for userspace control. A reference implementation is available here.

Fragment 2 creates a platform device that can be accessed by a kernel module using the PWM interface described in [1]. The compatible property identifies the device, following the format "vendor,device" (e.g., "mycompany,my-pwm-device"). This string is matched in the driver via the of_device_id table.

static const struct of_device_id pwm_dt_ids[] = {
    {.compatible = "mycompany,my-pwm-device"}, {}}; // From device tree property
MODULE_DEVICE_TABLE(of, pwm_dt_ids);                // Matches driver to device

The driver must declare an of_device_id match table and call MODULE_DEVICE_TABLE(of, ...) so the kernel can match the overlay to the module. A platform_driver structure is then used to register the driver, providing pointers to the probe and remove functions. These are similar in purpose to file_operations used in character drivers.

static struct platform_driver pwm_driver = {
    .probe = pwm_probe,
    .remove = pwm_remove,
    .driver =
        {
            .name = DEVICE_NAME,
            .of_match_table = pwm_dt_ids,
        },
};

module_platform_driver(pwm_driver); // Handles init and exit

When the kernel matches the device from the overlay with the loaded driver, it calls the driver's probe() function. Inside probe(), the driver can call devm_pwm_get() using the names defined in the pwm-names property to retrieve handles to the PWM channels. Once acquired, the driver can use the standard PWM API to enable, configure, and disable the PWM signals (pwm_enable(), pwm_config(), pwm_get_state(), and pwm_disable()). At this point, the driver is fully in control of the PWM hardware from kernel space.

#include <linux/pwm.h>

struct pwm_device *pwm_0 = devm_pwm_get(pdev, "pwm0");

pwm_enable(pwm_0);

pwm_config(pwm_0, 10000000 /* duty cycle in ns */, 20000000 /* period in ns */);

struct pwm_state state;
pwm_get_state(pwm_0, &state);

pwm_disable(pwm_0);
⚠️ **GitHub.com Fallback** ⚠️