Using PWM - FrankBau/meta-marsboard-bsp GitHub Wiki

Starting with commit 64701b8 (12 Dec 2016) all 4 PWMs are already enabled in the device tree and routed to pins 11, 9, 7, and 1 of header J10.

Prerequisites:

Extending the Device Tree for PWM4 function

Lets say, we want to use PWM4 function of the i.MX6 SoC. The [i.MX6 Reference Manual](iMX6 Reference Manual) shows that PWM4_OUT can be muxed to pad SD1_CMD (pad function Alt2) or SD4_DAT2 (pad function Alt2).

The MarS Board schematics show that SD1_CMD is available on extension header J10 pin 1 and not used otherwise, so we want to use this pin for PWM4_OUT.

We have to edit, re-compile and re-deploy the Device Tree in order to reflect this new board function:

The Device Tree on the Build Host

A working copy of the device tree is located under the BSPDIR in build/tmp/work/marsboard-poky-linux-gnueabi/linux-marsboard/3.14.52-r0/git/arch/arm/boot/dts/imx6q-marsboard.dts Note that this copy will be overwritten by a clean build (among others). So, finally we want to save the results under sources close to the kernel recipe: sources/meta-marsboard-bsp/recipes-kernel/linux/linux-marsboard-3.14.52/marsboard/imx6q-marsboard.dts. But, using the working copy gives you a faster turn-around cycle during development.

Edit the Device Tree: Pad Configuration and Muxing

Near the end of the &iomuxc section, after the closing } of pinctrl_usbotg: usbotggrp { add a new pin group:

pinctrl_pwm4: pwm4grp {
    fsl,pins = <
        MX6QDL_PAD_SD1_CMD__PWM4_OUT            0x1b0b1
    >;
};

See iMX6 Pad Mux and Pad Control for detailed explanation of the settings.

Edit the Device Tree: Enabling PWM4 function of the i.MX6 SoC

At the very end of the Device Tree, add an entirely new top-level section:

&pwm4 {
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_pwm4>;
    status = "okay";
};

The line pinctrl-0 = <&pinctrl_pwm4>; connects PWM4 to the pin group pinctrl_pwm4 defined above and the line status = "okay"; enables the PWM4 function of the i.MX6 SoC.

For the curious: &iomuxc and &pwm4 are labels referring to Device Tree nodes defined in the included file imx6qdl.dtsi. See Device Tree for further information.

Compiling the Device Tree

Open a bitbake shell on the Build Host and enter:

bitbake -c devshell virtual/kernel

This will open a new window with a devshell (a command prompt prepared for cross-compilation of the Linux Kernel).

The Linux Kernel build system has the ability to build a Device Tree Blob (.dtb) by typing:

make imx6q-marsboard.dtb

Make finds out that the source (.dts) has changed and compiles it again. The resulting Device Tree Blob is located under the BSPDIR in build/tmp/work/marsboard-poky-linux-gnueabi/linux-marsboard/3.14.52-r0/build/arch/arm/boot/dts/imx6q-marsboard.dtb

Build errors are reported from the Device Tree Compiler (DTC) on the command line with a line number indicating the error position. In most cases, errors result from misplaced braces, semi-colons or mistyped identifiers.

Deploy the Device Tree Blob

Remove the micro SD card from the target and insert it into the host. Copy file imx6q-marsboard.dtb to the BOOT partition of the microSD card. Use a plain old cp command from a command prompt and do not use GUI tools directly as they tend to create a recycle bins and other fancy stuff on your precious (space limited) microSD card.

If you like to leave the microSD card on the target, you can mount the BOOT partition from a running Linux and copy the .dtb to it. In all cases, a reboot is needed to make the new Device Tree effective.

Now you are ready to use the PWM4 function on the MarS Board.

PWM Access from the Command Line

Use case: This is primarily a debugging aid.

Export the PWM pin

echo 0 > /sys/class/pwm/pwmchip3/export

Each pwmchip represents a single pwm device on the iMX6. There are 4 pwm devices pwmchip0 .. pwmchip3, so PWM4 is the last. Each pwmchip implements exactly one pwm pin with the number 0, therefore the echo 0.

Set PWM Period and Duty Cycle

The values are in ns, we set a period of 1 ms and a duty cycle of 200 µs:

echo 1000000 >  /sys/class/pwm/pwmchip3/pwm0/period
echo  200000 >  /sys/class/pwm/pwmchip3/pwm0/duty_cycle

Enable the PWM pin

echo 1 > /sys/class/pwm/pwmchip3/pwm0/enable 

If you connect a LED between header J10 pin 1 (SD1_CMD) and pin 2 (GND), it should be dimmed. By changing the duty_cycle you may change the LED brightness.

Shut down

To undo the effects, issue the following commands:

echo 0 > /sys/class/pwm/pwmchip3/pwm0/enable 
echo 0 > /sys/class/pwm/pwmchip3/unexport

PWM Access from a standard Kernel Module

There are some common use cases like

  • Backlight Control for a LCD display (pwm-backlight)
  • PWM LED control (pwm-leds)

where kernel module drivers are readily available. They can be activated by extending the device tree.

See http://developer.toradex.com/knowledge-base/pwm-%28linux%29 for an example.

PWM Access from a custom Kernel Module

Use case: use when the available PWM kernel modules do not fit, e.g. when you need to control several PWMs at once for servo motor control, etc..

The above steps can be executed in a similar way from a kernel module:

define global variables

static const int my_pwm_id = 3; // header J10 pin 1 == GPIO1_18 == PWM4
struct pwm_device *my_pwm_dev;

request the pwm in the kernel module init function call

my_pwm_dev = pwm_request( my_pwm_id, "my pwm" );

to disable/enable the pwm pin call

pwm_enable(my_pwm_dev);
pwm_disable(my_pwm_dev);

to set the period and duty cycle call

pwm_config( my_pwm_dev, 200000, 1000000 );

to free the resource call in the module exit function

pwm_free(my_pwm_dev)

Discussion

The kernel module, as is, is a nice starting point during development. It has several drawbacks, however:

  • it needs manual loading (insmod) and unloading (rmmod) which is convenient during development but not suitable for autonomous embedded systems.
  • it uses board specific knowledge, because my_pwm_id is hard-coded in the source code.

Improvements (t.b.d.)

  1. There are "of" functions to get the pwm number from the Device Tree. this is more portable as the same kernel module driver can be used on different boards with different pin assignments by simply using unique device trees for each board.

  2. There are "devm" functions for resource management that automatically free resources when init fails or the module is unloaded. devm_of_pwm_get uses both approaches. Todo: give a working example using devm_of_pwm_get.

  3. The module could be extended to a platform device driver which is listed in the Device Tree and available through sysfs. This unifies the resource access pattern and allows for automatic module loading on start-up.

Further Reading