PWM lines

This article illustrates a basic use of the PWM lines using the Linux driver developed by Atmel and the user space sysfs interface.

To get an in-depth knowledge please use the Linux documentation and Atmel datasheets.

Two drivers are provided by Atmel in the Kernel mainstream to generate the PWM signals

These drivers exposes a sysfs user space interface to manage the PWM signals.

Using the integrated PWM controller

To include this driver in your Kernel select this item in the Linux Kernel configuration (make ARCH=arm menuconfig).

Device Drivers  ---> 
  [*] Pulse-Width Modulation (PWM) Support  --->
    <*>   Atmel PWM support

and enable the driver pwm inside the .dts file:

Acqua A5

    pwm0: pwm@f002c000 {
        pinctrl-names = "default";
        pinctrl-0 = <   
                        &pinctrl_pwm0_pwmh0
                        &pinctrl_pwm0_pwml0
                        &pinctrl_pwm0_pwmh1 
                        &pinctrl_pwm0_pwml1
                        &pinctrl_pwm0_pwmh2 
                        &pinctrl_pwm0_pwml2
                        &pinctrl_pwm0_pwmh3 
                        &pinctrl_pwm0_pwml3
                        >;
        status = "okay";
    };

    pinctrl@fffff200 {
        board {
            pinctrl_pwm0_pwmh0: pwm0_pwmh0 {
                atmel,pins =
                    < AT91_PIOB 0 AT91_PERIPH_B AT91_PINCTRL_NONE >;
            };
            pinctrl_pwm0_pwml0: pwm0_pwml0 {
                atmel,pins =
                    < AT91_PIOB 1 AT91_PERIPH_B AT91_PINCTRL_NONE >;
            };
            pinctrl_pwm0_pwmh1: pwm0_pwmh1 {
                atmel,pins =
                    < AT91_PIOB 4 AT91_PERIPH_B AT91_PINCTRL_NONE >;
            };
            pinctrl_pwm0_pwml1: pwm0_pwml1 {
                atmel,pins =
                    < AT91_PIOB 5 AT91_PERIPH_B AT91_PINCTRL_NONE >;
            };
            pinctrl_pwm0_pwmh2: pwm0_pwmh2 {
                atmel,pins =
                    < AT91_PIOB 8 AT91_PERIPH_B AT91_PINCTRL_NONE >;
            };
            pinctrl_pwm0_pwml2: pwm0_pwml2 {
                atmel,pins =
                    < AT91_PIOB 9 AT91_PERIPH_B AT91_PINCTRL_NONE >;
            };
            pinctrl_pwm0_pwmh3: pwm0_pwmh3 {
                atmel,pins =
                    < AT91_PIOB 12 AT91_PERIPH_B AT91_PINCTRL_NONE >;
            };
            pinctrl_pwm0_pwml3: pwm0_pwml3 {
                atmel,pins =
                    < AT91_PIOB 13 AT91_PERIPH_B AT91_PINCTRL_NONE >;
            };
        };
    };

Aria G25

pinctrl@fffff400 {
    pwm0 {
        pinctrl_pwm0: pwm0-0 {
            atmel,pins =
            < AT91_PIOC 18 AT91_PERIPH_C AT91_PINCTRL_NONE
              AT91_PIOC 19 AT91_PERIPH_C AT91_PINCTRL_NONE
              AT91_PIOC 20 AT91_PERIPH_C AT91_PINCTRL_NONE
              AT91_PIOC 21 AT91_PERIPH_C AT91_PINCTRL_NONE >;
        };
    };
};
            
pwm0: pwm@f8034000 {
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_pwm0>;
    status = "okay";
};

Arietta G25

If your are using an Arietta G25 you have to use the alternative pin configuration replacing this part to move the PWM signals from PC18-19-20-21 to PB11-12-13-14:

pinctrl@fffff400 {
    pwm0 {
        pinctrl_pwm0: pwm0-0 {
            atmel,pins =
            < AT91_PIOB 11 AT91_PERIPH_B AT91_PINCTRL_NONE
              AT91_PIOB 12 AT91_PERIPH_B AT91_PINCTRL_NONE
              AT91_PIOB 13 AT91_PERIPH_B AT91_PINCTRL_NONE
              AT91_PIOB 14 AT91_PERIPH_B AT91_PINCTRL_NONE >;
        };
    };
};

The pin used by the PWM controller are:

Acqua A5

Pin Atmel name Signal
J2.29 PB0 PWMH0
J2.31 PB1 PWML0
J2.23 PB4 PWMH1
J2.25 PB5 PWML1
J2.33 PB8 PWMH2
J2.35 PB9 PWML2
J2.37 PB12 PWMH3
J2.39 PB13 PWML3

Aria G25

Pin Atmel name Signal
N20 PC18 PWM0
N21 PC19 PWM1
N22 PC20 PWM2
N23 PC21 PWM3

Arietta G25

Pin Atmel name Signal
J4.34 PB11 PWM0
J4.36 PB12 PWM1
J4.38 PB13 PWM2
J4.40 PB14 PWM3

Using the Timer Counter Block

To include this driver in your Kernel select this item in the Linux Kernel configuration (make ARCH=arm menuconfig).

Device Drivers  ---> 
  [*] Pulse-Width Modulation (PWM) Support  --->
    <*>   Atmel TC Block PWM support 

and enable the driver inside the .dts file:

pwm {
        compatible = "atmel,tcb-pwm";
        #pwm-cells = <3>;
        tc-block = <1>;
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_tcb1_tioa0
                     &pinctrl_tcb1_tioa1
                     &pinctrl_tcb1_tioa2
                     &pinctrl_tcb1_tiob0
                     &pinctrl_tcb1_tiob1
                     &pinctrl_tcb1_tiob2>;
        status = "okay";
};

A Timer Counter Block provides 6 PWM devices grouped by 2. Devices in the same group must have the same period.

The GPIO pin used for TCB PWM signals are:

Aria G25

Pin Atmel name pwm # Group Timer Signal at91sam9x5.dtsi def
N4 PC2 pwm0 0 TIOA3 pinctrl_tcb1_tioa0
N5 PC3 pwm1 0 TIOB3 pinctrl_tcb1_tiob0
N7 PC5 pwm2 1 TIOA4 pinctrl_tcb1_tioa1
N8 PC6 pwm3 1 TIOB4 pinctrl_tcb1_tiob1
N14 PC12 pwm4 2 TIOA5 pinctrl_tcb1_tioa2
N15 PC13 pwm5 2 TIOB5 pinctrl_tcb1_tiob2

The pinctrl configuration is responsible for setting up the GPIO output which definition is in arch/arm/boot/dts/at91sam9x5.dtsi.

If don't need to use all the 6 PWM signals remove the pinctrl_tcb1_tioXX simbols from pinctrl-0 definitions related to the GPIO line to set free.

Using the PWM sysfs user space interface

The PWM sysfs at startup interface will create a directory called /sys/class/pwm/pwmchip0.

For any PWM line you want to use you have to export it.

~# echo 0 > /sys/class/pwm/pwmchip0/export
~# echo 1 > /sys/class/pwm/pwmchip0/export
~# echo 2 > /sys/class/pwm/pwmchip0/export
~# echo 3 > /sys/class/pwm/pwmchip0/export
...

To use again that line ad generic GPIO you can unexport it:

~# echo 0 > /sys/class/pwm/pwmchip0/unexport
~# echo 1 > /sys/class/pwm/pwmchip0/unexport
~# echo 2 > /sys/class/pwm/pwmchip0/unexport
~# echo 3 > /sys/class/pwm/pwmchip0/unexport
...

For any exported channel a directory called pwmX wil be created with the following structure:

/sys/class/pwm/pwmchip0/pwmX/
      |-- duty_cycle (r/w) duty cycle (in nanoseconds)
      |-- enable     (r/w) enable/disable PWM
      |-- period     (r/w) period (in nanoseconds)
      |-- polarity   (r/w) polarity of PWM
      |-- power
      `-- uevent

The follow example illustrate how enable a PWM signale with a period of 1mS with a 0.5mS of duty cycle:

~# echo 1000000 > /sys/class/pwm/pwmchip0/pwm0/period
~# echo 500000 > /sys/class/pwm/pwmchip0/pwm0/duty_cycle
~# echo 1 > /sys/class/pwm/pwmchip0/pwm0/enable

Related links

Credits

Many thanks to Nicolas Ferre and Boris Brezillon for their help.