Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add PWM support for Linux 4.1+ #103

Closed
pdp7 opened this issue Jun 5, 2016 · 11 comments
Closed

Add PWM support for Linux 4.1+ #103

pdp7 opened this issue Jun 5, 2016 · 11 comments

Comments

@pdp7
Copy link
Collaborator

pdp7 commented Jun 5, 2016

This library only supports PWM for Linux kernel 3.8. The way to utilize PWM on the TI AM3358 in newer kernels has changed.

I'm creating this issue to represent the effort add support for newer kernels, notably 4.1 and 4.4 as those are long-term kernels supported by TI. @RobertCNelson currently using 4.4 for the latest Debian Jessie images.

I'll be working on adding support in my fork and will create a pull request once I have something working.

@pdp7
Copy link
Collaborator Author

pdp7 commented Jun 5, 2016

BeagleBone Bacon Cape PWM testing with Linux 4.1.15-ti-rt-r43 on Debian Jessie 8.3 from the 2016-01-24 SD card image:

pwm-test-jessie-4-1.sh

echo '7 7 7 7' > /proc/sys/kernel/printk
config-pin P9.42 pwm
config-pin -q P9.42
config-pin P9.16 pwm
config-pin -q P9.16
config-pin P9.42 pwm
config-pin -q P9.42

for i in `find /sys |grep pwmchip|grep /export$`
do 
   echo $1 > $i
   ls $i
done

for i in `find /sys |grep pwmchip|grep /pwm$1 |grep enable$`
do 
   echo 1 > $i
   echo $i: `cat $i`
done


for i in `find /sys |grep pwmchip|grep /pwm$1 |grep period$`
do 
   echo $2 > $i
   echo $i: `cat $i`
done

for i in `find /sys |grep pwmchip|grep /pwm$1 |grep duty_cycle$`
do 
   echo $3 > $i
   echo $i: `cat $i`
done

Output:

root@beaglebone:~# bash ./pwm.sh 0 2000 1000
P9_42 Mode: pwm
P9_16 Mode: pwm
P9_42 Mode: pwm
/sys/devices/platform/ocp/48302000.epwmss/48302200.ehrpwm/pwm/pwmchip2/export
/sys/devices/platform/ocp/48304000.epwmss/48304100.ecap/pwm/pwmchip7/export
/sys/devices/platform/ocp/48304000.epwmss/48304200.ehrpwm/pwm/pwmchip4/export
/sys/devices/platform/ocp/48300000.epwmss/48300200.ehrpwm/pwm/pwmchip0/export
/sys/devices/platform/ocp/48300000.epwmss/48300100.ecap/pwm/pwmchip6/export
/sys/devices/platform/ocp/48302000.epwmss/48302200.ehrpwm/pwm/pwmchip2/pwm0/enable: 1
/sys/devices/platform/ocp/48304000.epwmss/48304100.ecap/pwm/pwmchip7/pwm0/enable: 1
/sys/devices/platform/ocp/48304000.epwmss/48304200.ehrpwm/pwm/pwmchip4/pwm0/enable: 1
/sys/devices/platform/ocp/48300000.epwmss/48300200.ehrpwm/pwm/pwmchip0/pwm0/enable: 1
/sys/devices/platform/ocp/48300000.epwmss/48300100.ecap/pwm/pwmchip6/pwm0/enable: 1
/sys/devices/platform/ocp/48302000.epwmss/48302200.ehrpwm/pwm/pwmchip2/pwm0/period: 2000
/sys/devices/platform/ocp/48304000.epwmss/48304100.ecap/pwm/pwmchip7/pwm0/period: 2000
/sys/devices/platform/ocp/48304000.epwmss/48304200.ehrpwm/pwm/pwmchip4/pwm0/period: 2000
/sys/devices/platform/ocp/48300000.epwmss/48300200.ehrpwm/pwm/pwmchip0/pwm0/period: 2000
/sys/devices/platform/ocp/48300000.epwmss/48300100.ecap/pwm/pwmchip6/pwm0/period: 2000
/sys/devices/platform/ocp/48302000.epwmss/48302200.ehrpwm/pwm/pwmchip2/pwm0/duty_cycle: 1000
/sys/devices/platform/ocp/48304000.epwmss/48304100.ecap/pwm/pwmchip7/pwm0/duty_cycle: 1000
/sys/devices/platform/ocp/48304000.epwmss/48304200.ehrpwm/pwm/pwmchip4/pwm0/duty_cycle: 1000
/sys/devices/platform/ocp/48300000.epwmss/48300200.ehrpwm/pwm/pwmchip0/pwm0/duty_cycle: 1000
/sys/devices/platform/ocp/48300000.epwmss/48300100.ecap/pwm/pwmchip6/pwm0/duty_cycle: 1000

@pdp7
Copy link
Collaborator Author

pdp7 commented Jun 5, 2016

Examination of how PWM currently works in Linux 3.8
This library currently expects to load a device tree overlay for each PWM pin:
c_pwm.c#L160

    snprintf(fragment, sizeof(fragment), "bone_pwm_%s", key);

The device tree overlay causes a directory in /sys to be created for that PWM pin:
c_pwm.c#L169

    snprintf(pwm_test_fragment, sizeof(pwm_test_fragment), "pwm_test_%s", key);

The PWM pin is then controlled via 3 files (period, duty, polarity) inside the /sys sub-directory for the pin:
c_pwm.c#L175

    //create the path for the period and duty
    snprintf(period_path, sizeof(period_path), "%s/period", pwm_test_path);
    snprintf(duty_path, sizeof(duty_path), "%s/duty", pwm_test_path);
    snprintf(polarity_path, sizeof(polarity_path), "%s/polarity", pwm_test_path);

For example, this device tree overlay will configure P9_16 for PWM:
bone_pwm_P9_16-00A0.dts

            pwm_test_P9_16 {
                compatible  = "pwm_test";
                pwms        = <&ehrpwm1 1 500000 1>;
                pwm-names   = "PWM_P9_16";
                pinctrl-names   = "default";
                pinctrl-0   = <&pwm_P9_16>;
                enabled     = <1>;
                duty        = <0>;
                status      = "okay";
            };

Test of PWM for P9_14:

import Adafruit_BBIO.PWM as PWM
PWM.start("P9_14", 50, 2000, 0)
PWM.stop("P9_14")
PWM.cleanup()

Device tree overlays that are loaded by the library:

root@beaglebone:~# cat /sys/devices/bone_capemgr.9/slots
 0: 54:PF--- 
 1: 55:PF--- 
 2: 56:PF--- 
 3: 57:PF--- 
 4: ff:P-O-- Bone-LT-eMMC-2G,00A0,Texas Instrument,BB-BONE-EMMC-2G
 5: ff:P-O-- Bone-Black-HDMI,00A0,Texas Instrument,BB-BONELT-HDMI
 6: ff:P-O-- Bone-Black-HDMIN,00A0,Texas Instrument,BB-BONELT-HDMIN
 7: ff:P-O-L Override Board Name,00A0,Override Manuf,am33xx_pwm
 8: ff:P-O-L Override Board Name,00A0,Override Manuf,bone_pwm_P9_14

Kernel log:

[  178.755012] bone-capemgr bone_capemgr.9: part_number 'am33xx_pwm', version 'N/A'
[  178.755088] bone-capemgr bone_capemgr.9: slot #7: generic override
[  178.755105] bone-capemgr bone_capemgr.9: bone: Using override eeprom data at slot 7
[  178.755122] bone-capemgr bone_capemgr.9: slot #7: 'Override Board Name,00A0,Override Manuf,am33xx_pwm'
[  178.755220] bone-capemgr bone_capemgr.9: slot #7: Requesting part number/version based 'am33xx_pwm-00A0.dtbo
[  178.755235] bone-capemgr bone_capemgr.9: slot #7: Requesting firmware 'am33xx_pwm-00A0.dtbo' for board-name 'Override Board Name', version '00A0'
[  178.755256] bone-capemgr bone_capemgr.9: slot #7: dtbo 'am33xx_pwm-00A0.dtbo' loaded; converting to live tree
[  178.755578] bone-capemgr bone_capemgr.9: slot #7: #8 overlays
[  178.761417] ehrpwm 48300200.ehrpwm: unable to select pin group
[  178.763512] ecap 48300100.ecap: unable to select pin group
[  178.766854] ehrpwm 48302200.ehrpwm: unable to select pin group
[  178.769800] ehrpwm 48304200.ehrpwm: unable to select pin group
[  178.771270] ecap 48304100.ecap: unable to select pin group
[  178.774226] bone-capemgr bone_capemgr.9: slot #7: Applied #8 overlays.
[  178.974797] bone-capemgr bone_capemgr.9: part_number 'bone_pwm_P9_14', version 'N/A'
[  178.974872] bone-capemgr bone_capemgr.9: slot #8: generic override
[  178.974888] bone-capemgr bone_capemgr.9: bone: Using override eeprom data at slot 8
[  178.974905] bone-capemgr bone_capemgr.9: slot #8: 'Override Board Name,00A0,Override Manuf,bone_pwm_P9_14'
[  178.974999] bone-capemgr bone_capemgr.9: slot #8: Requesting part number/version based 'bone_pwm_P9_14-00A0.dtbo
[  178.975015] bone-capemgr bone_capemgr.9: slot #8: Requesting firmware 'bone_pwm_P9_14-00A0.dtbo' for board-name 'Override Board Name', version '00A0'
[  178.975036] bone-capemgr bone_capemgr.9: slot #8: dtbo 'bone_pwm_P9_14-00A0.dtbo' loaded; converting to live tree
[  178.975235] bone-capemgr bone_capemgr.9: slot #8: #2 overlays
[  178.977008] bone-capemgr bone_capemgr.9: slot #8: Applied #2 overlays.

These entries are created in /sys for P9_14:

/sys/bus/platform/devices/pwm_test_P9_14.11
/sys/bus/platform/drivers/pwm_test/pwm_test_P9_14.11
/sys/devices/ocp.3/pwm_test_P9_14.11
/sys/devices/ocp.3/pwm_test_P9_14.11/driver
/sys/devices/ocp.3/pwm_test_P9_14.11/duty
/sys/devices/ocp.3/pwm_test_P9_14.11/modalias
/sys/devices/ocp.3/pwm_test_P9_14.11/period
/sys/devices/ocp.3/pwm_test_P9_14.11/polarity
/sys/devices/ocp.3/pwm_test_P9_14.11/power
/sys/devices/ocp.3/pwm_test_P9_14.11/run
/sys/devices/ocp.3/pwm_test_P9_14.11/subsystem
/sys/devices/ocp.3/pwm_test_P9_14.11/uevent

Period and duty cycle for P9_14:

root@beaglebone:~# cat /sys/devices/ocp.3/pwm_test_P9_14.11/duty
250000
root@beaglebone:~# cat /sys/devices/ocp.3/pwm_test_P9_14.11/period
500000
root@beaglebone:~# cat /sys/devices/ocp.3/pwm_test_P9_14.11/run
1
root@beaglebone:~# cat /sys/devices/ocp.3/pwm_test_P9_14.11/polarity 
0

pwm_test driver comes from am33x-v3.8 branch of https://github.com/RobertCNelson/bb-kernel:

./KERNEL/drivers/pwm/pwm_test.c
./patches/pwm/0003-pwm-pwm_test-Driver-support-for-PWM-module-testing.patch
./patches/not-capebus/0161-pwm_test-Clean-up-and-make-it-work-on-DT-correctly.patch
./patches/not-capebus/0160-pwm_test-fix-some-issues.patch

source in Github Gist for reference:
https://gist.github.com/pdp7/6612aa76db76382a470241a142776dd4

EHRPWM_TEST is added to the kernel config.

@pdp7
Copy link
Collaborator Author

pdp7 commented Jun 6, 2016

I'm reviewing Bonescript by @jadonk to see how it handles PWM on 4.1+ kernels.

The current development branch is: bonescript/0.5.x

@MarkAYoder has been several commits this year related to PWM:

@pdp7
Copy link
Collaborator Author

pdp7 commented Jun 8, 2016

PWM related code from PyBBIO by @graycatlabs

# Predefined resolutions for analogWrite():
RES_16BIT = 2**16
RES_8BIT  = 2**8
PERCENT   = 100

# Default frequency in Hz of PWM modules (must be >0):
PWM_DEFAULT_FREQ = 100000

# PWM config dict in form:
#  ['overlay_file', 'path/to/ocp_helper_dir', ['required', 'overlays']]

PWM_PINS = {
  'PWM1A' : ['bone_pwm_P9_14', '%s/pwm_test_P9_14.*' % OCP_PATH, 
             ['PyBBIO-epwmss1', 'PyBBIO-ehrpwm1']],
  'PWM1B' : ['bone_pwm_P9_16', '%s/pwm_test_P9_16.*' % OCP_PATH, 
             ['PyBBIO-epwmss1', 'PyBBIO-ehrpwm1']],

  'PWM2A' : ['bone_pwm_P8_19', '%s/pwm_test_P8_19.*' % OCP_PATH, 
             ['PyBBIO-epwmss2', 'PyBBIO-ehrpwm2']],
  'PWM2B' : ['bone_pwm_P8_13', '%s/pwm_test_P8_13.*' % OCP_PATH, 
             ['PyBBIO-epwmss2', 'PyBBIO-ehrpwm2']],

  'ECAP0' : ['bone_pwm_P9_42', '%s/pwm_test_P9_42.*' % OCP_PATH, 
             ['PyBBIO-epwmss0', 'PyBBIO-ecap0']],
  'ECAP1' : ['bone_pwm_P9_28', '%s/pwm_test_P9_28.*' % OCP_PATH, 
             ['PyBBIO-epwmss1', 'PyBBIO-ecap1']],

}
# Using the built-in pin overlays for now, I see no need for custom ones 

PWM1A = 'PWM1A'
PWM1B = 'PWM1B'
PWM2A = 'PWM2A'
PWM2B = 'PWM2B'
ECAP0 = 'ECAP0'
ECAP1 = 'ECAP1'

# ocp helper filenames:
PWM_RUN      = 'run'
PWM_DUTY     = 'duty'
PWM_PERIOD   = 'period'
PWM_POLARITY = 'polarity'

PWM_DEFAULT_PERIOD = int(1e9/PWM_DEFAULT_FREQ)

@MatthewWest
Copy link
Contributor

@pdp7 Any update on progress? I am also available to work on this issue, since my company would like to update to Debian 8, but we need PWM in this library to work.

Also I'm not sure where to ask, but do you have any idea who is responsible for releasing a new package version released with the 4.1+ support?

@pdp7
Copy link
Collaborator Author

pdp7 commented Jul 20, 2016

Hi, I've been travelling recently and just getting back to this now. If we can develop a solution that doesn't cause a regression for 3.8 then it will be merged. As for PyPi package, I'll have to check with @jwcooper or @tdicola

@MatthewWest
Copy link
Contributor

I thought I'd do a similar summary of how PWM / IO generally works in 4.1+.

/sys changes

The locations of a number of key pieces of hardware have changed. This is probably the easiest fix to make (and has been mostly made for other pieces of the library).

/sys/devices/bone_capemgr.*/slots => /sys/devices/platform/bone_capemgr/slots
/sys/devices/ocp.* => /sys/devices/platform/ocp/

Capes loaded on boot

There are two main changes. First, HDMI and EMMC are no longer virtual capes loaded on boot, but are handled natively. I haven't looked in detail into how this works.

Second, and more significantly, the idea of loading a separate overlay for each pin has been basically abandoned, in favor of using a Universal IO cape. The universal cape is enabled on boot by default, with an option to disable it in boot/uEnv.txt by removing the cape_universal=enable argument from the cmdline= line (more detail here).

Depending on the dtb= line of uEnv.txt, different univeral capes are loaded. The dtbs which previously disabled the HDMI or EMMC behave the same way (see the boot script for details).

Given that a universal cape is loaded at boot, attempting to load a device tree overlay which conflicts with any of its pins will fail. The universal cape uses almost all the pins on the board: all but the ADC and I2C pins, and depending on the dtb= line of uEnv.txt, the HDMI, HDMI audio, or EMMC pins.

So 4.1+ replaces the concept of loading in new device tree overlays for each pin / group of pins with a combination of a universal cape and pinmuxing. There is no need to reinvent the wheel with the pinmuxing---the config-pin executable provided by beaglebone-universal-io works fine. However, since it's not included with the console images, we'll need to figure out whether we want to introduce a dependency or whether we should just pull it in ourselves.

BoneScript approach to PWM

Now I'll describe the approach taken by BoneScript.

src/hw_mainline.js

    } else if(template == 'bspwm') {
        fs.writeFileSync(pinmux+"/state", 'pwm');
        // Buld a path like: /sys/devices/platform/ocp/48304000.epwmss/48304200.ehrpwm/pwm/pwmchip5
        // pin.pwm.chip looks up the first address and pin.pwm.addr looks up the second
        // file_find figures which pwmchip to use
        // pin.pwm.index tells with half of the PWM to use (0 or 1)
        var pwmPath = my.file_find('/sys/devices/platform/ocp/'+pin.pwm.chip
                + '.epwmss/'+pin.pwm.addr+'.pwm/pwm', 'pwmchip', 1);
        pwmPrefix[pin.pwm.name] = pwmPath + '/pwm' + pin.pwm.index;
        if(debug) winston.debug("pwmPrefix[pin.pwm.name] = " + pwmPrefix[pin.pwm.name]);
        if(debug) winston.debug("pin.pwm.sysfs = " + pin.pwm.sysfs);
        if(!my.file_existsSync(pwmPrefix[pin.pwm.name])) {
            fs.appendFileSync(pwmPath+'/export', pin.pwm.index);
        }
        fs.appendFileSync(pwmPrefix[pin.pwm.name]+'/enable', 1);

Given a pin ("P9_21" or similar), this section of code sets the pinmux state to PWM. It finds the path to the PWM chip. I'm not sure why it uses a path with '.pwm/pwm' instead of '.ehrpwm/pwm', as in my system running 4.4.15-bone11, the correct path uses '.ehrpwm'. It saves the path to the pwmPrefix dictionary, which makes future look-ups easy.

There was another step in the changes made by BoneScript at kernel version 4.1. They depended on exporting PWMs from the /sys/class/pwm directory. That strategy can still be found in src/hw_universal.js. As of kernel 4.4, that strategy no longer works, so they use the strategy shown above in src/hw_mainline.js.

Implementation thoughts

We'll need to keep track of some additional information about pins which have pwm as an output choice. Specifically, we'll need the chip address, and the device address. When initializing a pin for PWM, there are two steps: first, mux the pin to the correct value. This can be done by calling config-pin or by writing our own function and maintaining some sort of data structure with all the pins and mux settings, then writing to /sys/devices/platform/ocp/ocp:P#_##_pinmux/state.

One possible implementation would be to define an interface in a header file, which is implemented by two separate source files, one for <=3.8 and the other for >=4.1.

Calling @RobertCNelson - would you be willing to read through and make sure I don't have any major misunderstandings of what's going on? From what I can tell you appear to understand well how all the pieces fit together.

@cocasema
Copy link
Contributor

I'm not sure why it uses a path with '.pwm/pwm' instead of '.ehrpwm/pwm', as in my system running 4.4.15-bone11, the correct path uses '.ehrpwm'.

@MatthewWest
Just a note, on my bbb with 4.4.12-ti-r30 some pwm path contain '.ecap/pwm'.

# uname -a
Linux arm 4.4.12-ti-r30 #1 SMP Thu Jun 9 07:50:17 UTC 2016 armv7l armv7l armv7l GNU/Linux

# ll /sys/class/pwm/
 pwmchip0 -> ../../devices/platform/ocp/48300000.epwmss/48300100.ecap/pwm/pwmchip0/
 pwmchip1 -> ../../devices/platform/ocp/48304000.epwmss/48304100.ecap/pwm/pwmchip1/
 pwmchip2 -> ../../devices/platform/ocp/48300000.epwmss/48300200.pwm/pwm/pwmchip2/
 pwmchip4 -> ../../devices/platform/ocp/48302000.epwmss/48302200.pwm/pwm/pwmchip4/
 pwmchip6 -> ../../devices/platform/ocp/48304000.epwmss/48304200.pwm/pwm/pwmchip6/

@pdp7
Copy link
Collaborator Author

pdp7 commented Jul 21, 2016

@MatthewWest Thank you for the summary and your thoughts. That is very helpful. I think you are right that the necessary information for a pin capable of PWM can be borrowed from bonescript/src/bone.js

For example, pin P8_13 has name EHRPWM2B at Line 282:

    {
        "name": "EHRPWM2B",
        "gpio": 23,
        "mux": "gpmc_ad9",
        "eeprom": 15,
        "pwm": {
            "module": "ehrpwm2",
            "sysfs": 6,
            "index": 1,
            "muxmode": 4,
            "path": "ehrpwm.2:1",
            "name": "EHRPWM2B",
            "chip": "48304000",
            "addr": "48304200"
        },
        "key": "P8_13",
        "muxRegOffset": "0x024",
        "options": [
            "gpmc_ad9",
            "lcd_data22",
            "mmc1_dat1",
            "mmc2_dat5",
            "ehrpwm2B",
            "pr1_mii0_col",
            "NA",
            "gpio0_23"
        ]
    },

I agree it would be good to get @RobertCNelson's thoughts

@MatthewWest
Copy link
Contributor

I've implemented a patch which I have confirmed works for Linux 4.1+, though I haven't had a chance to test it with Linux 3.8. The pull request is at #108.

@pdp7
Copy link
Collaborator Author

pdp7 commented Aug 20, 2016

Resolved by @MatthewWest's excellent work in #108

@pdp7 pdp7 closed this as completed Aug 20, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants