Official eQEP driver Support

Hello all,

In an effort to make use of the eQEP modules on the beaglebone black we stumbled across the following encoder patch and were able to compile a loadable kernel module with the help of TI. Hopefully this will make its way to the “thirdparty” script in the debian build farm so the community can use this without recompiling

https://github.com/Teknoman117/beaglebot/tree/master/encoders

I would like to make known a bug we ran into with reading the eQEP module that resulted in position values not updating after exactly 1020 reads without closing the file pointer. If the file pointer is closed and re-opened for every read, the behavior is normal, but opening/closing the file is painfully slow. I unfortunately don’t really know where to begin with finding and fixing this problem, but any insight would be appreciated.

Best,
J Strawson, C Briggs

Hello all,

In an effort to make use of the eQEP modules on the beaglebone black we
stumbled across the following encoder patch and were able to compile a
loadable kernel module with the help of TI. Hopefully this will make its way
to the "thirdparty" script in the debian build farm so the community can use
this without recompiling

https://github.com/Teknoman117/beaglebot/tree/master/encoders

It looks pretty self-contained, i have no problem adding to the
current kernel patches..

I would like to make known a bug we ran into with reading the eQEP module
that resulted in position values not updating after exactly 1020 reads
without closing the file pointer. If the file pointer is closed and
re-opened for every read, the behavior is normal, but opening/closing the
file is painfully slow. I unfortunately don't really know where to begin
with finding and fixing this problem, but any insight would be appreciated.

yikes, it still needs a little work then..

Regards,

Strawson, best thing I personally can think of, would be to use two or more pointers to the same file, and rotate between them. Closing one down while the other continues operating.

I’m using it but in order to be fast ( I know it’s not very linux style … but )

Set up

#ifdef __arm__

#define MAP_SIZE 4096UL //< Constant used for the memory mapping of the eqep
#define MAP_MASK (MAP_SIZE - 1)

// mmaping device register in order to speed up things
if((fd = open("/dev/mem", O_RDWR | O_SYNC)) == -1)
{
LOG(logFATAL) << " Unable to open /dev/mem";
exit(-1);
}

map_base = mmap(0, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd,
(eqepMap.at(channel).phAddress) & ~MAP_MASK );
if(map_base == (void *) -1) {
LOG(logFATAL) << " Unable to mmap eqep";
exit(-1);
}

virt_addr = map_base + ((eqepMap.at(channel).phAddress) & MAP_MASK);
#endif

Read Position

`
inline int32_t get_position_fast() { return *((int32_t *) virt_addr); } ///< not really clean but performing

`

Thank you Guiseppe. I had tried an failed in the past to use mmap, but I see your solution to the error I had before is to mmap starting at the base address of the PWM peripheral instead of just at eQEP with (target&~MAP_MASK). Since ~MAP_MASK is 0xFFFFFFFFF000, the mmap starts at address 0x48300000 instead of 0x48300180 (for eqep0). Then you seem to be getting back to the base eQEP position register by adding target&MAP_MASK (0x180) back onto the virt_addr. Why the mmap function call fails without using the MAP_MASK is not entirely clear to me, but I won’t worry about it.

While my mmap errors are gone, I’m still only ever reading 0x83 when accessing the position value pointed to by virt-addr. If I try to access other parts of the eqep module I only get 0. For example the eqep hardware revision code which is at offset 0x5C according to the AM335xx reference manual returns 0 but should contain a value.

My test code is below. Any thoughts?

Thank you all,
Strawson

`

#include <stdio.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>

#define MAP_SIZE 4096UL
#define MAP_MASK (MAP_SIZE - 1)
#define EQEP0_BASE 0x48300180
#define EQEP1_BASE 0x48302180
#define EQEP2_BASE 0x48304180

int main(){
int fd;
volatile unsigned long *virt_addr;
volatile unsigned long *map_base;
unsigned long read_result;
int i;
for(i=0;i<10000;i++){
unsigned long target=EQEP2_BASE;
//O_SYNC makes the memory uncacheable
if ((fd = open("/dev/mem", O_RDWR | O_SYNC))==-1) {
printf(“Could not open memory\n”);
return 0;
}
//printf(“opened /dev/mem \n”);

//EQEP0_BASE&~MAP_MASK truncates the last 12 bits of the target address
map_base=(ulong*)mmap(0, 4096, PROT_READ|PROT_WRITE,MAP_SHARED,fd, target&~MAP_MASK);
if(map_base == (void *) -1) {
printf(“Unable to mmap eqep\n”);
exit(-1);
}
//add the last 12 bits (0x180 for eqep) back onto to the address
//also add 0x5C to offset to hardware revision code
virt_addr = map_base + (target & MAP_MASK)+0x5C;
read_result = *((unsigned long *) virt_addr);
printf("\rValue at addr: 0x%lX (%p): 0x%lX ", map_base, virt_addr, read_result);

close(fd);
usleep(10000);
}
close(fd);
return 0;
}

`

Thank you Guiseppe. I had tried an failed in the past to use mmap, but I see your solution to the error I had before is to mmap starting at the base address of the PWM peripheral instead of just at eQEP with (target&~MAP_MASK). Since ~MAP_MASK is 0xFFFFFFFFF000, the mmap starts at address 0x48300000 instead of 0x48300180 (for eqep0). Then you seem to be getting back to the base eQEP position register by adding target&MAP_MASK (0x180) back onto the virt_addr. Why the mmap function call fails without using the MAP_MASK is not entirely clear to me, but I won’t worry about it.

While my mmap errors are gone, I’m still only ever reading 0x83 when accessing the position value pointed to by virt-addr. If I try to access other parts of the eqep module I only get 0. For example the eqep hardware revision code which is at offset 0x5C according to the AM335xx reference manual returns 0 but should contain a value.

Take a look at this E2E posting:

http://e2e.ti.com/support/arm/sitara_arm/f/791/p/330787/1156969.aspx#1156969

I think the address you need is 0xFA300180

I have attached a printout of the MMU translations. Hopefully this helps.

Regards,
John

MMU Translation.txt (314 KB)

Cheers, John! Your suggestion of 0xFA300180 matches the MMU translations and places it in the linux-arm map range: vmalloc : 0xf0000000 - 0xff000000. This address does return values at both the base and the hardware revision offset, but not correct ones :-/

A little more stumbling on the internet turned up the below example of mmapping the GPIO peripheral at GPIO1_START_ADDR 0x4804C000 which matches exactly to the address in page 159 of the AM335x Technical Reference Manual.

https://github.com/chiragnagpal/beaglebone_mmap/blob/master/gpi.c

I’m not sure why this would work for GPIO but not the eQEP module. Guiseppe, do you mind sharing the output of your physical address function call? eqepMap.at(channel).phAddress

Just to confirm, I have the PWM and eQEP modules enabled, pinmux set correctly, and correct encoder readings being displayed with the eqep driver in the original post.

Thanks for any further input,
Strawson

mmapping must be aligned with memory pages. It just so happens that the GPIO module is already aligned, but since the eQEP is a subset of the PWM module, the eQEP base address is not page aligned.

I chose to page align mmap in a more dynamic way to try to avoid some of that confusion later. getpagesize() is included with the unistd.h header. eQEP_address_ is one of the eQEP addresses you expect ending in 180:

int masked_address = eQEP_address_ & ~(getpagesize()-1);
pwm_addr = (void *) mmap(NULL, PWM_BLOCK_LENGTH,
PROT_READ | PROT_WRITE, MAP_SHARED, eQEPFd, masked_address);

int offset = eQEP_address_ & (getpagesize()-1);
position_p = (uint32_t *) ((uint8_t *)pwm_addr + QPOSCNT + offset);
pos_init_p = (uint32_t *) ((uint8_t *)pwm_addr + QPOSINIT + offset);
max_pos_p = (uint32_t *) ((uint8_t *)pwm_addr + QPOSMAX + offset);

Cheers, John! Your suggestion of 0xFA300180 matches the MMU translations and places it in the linux-arm map range: vmalloc : 0xf0000000 - 0xff000000. This address does return values at both the base and the hardware revision offset, but not correct ones :-/

A little more stumbling on the internet turned up the below example of mmapping the GPIO peripheral at GPIO1_START_ADDR 0x4804C000 which matches exactly to the address in page 159 of the AM335x Technical Reference Manual.

https://github.com/chiragnagpal/beaglebone_mmap/blob/master/gpi.c

This example is using mmap() which is adding the translation between virtual and physical address. If you do not use mmap(), you must use the virtual address based on the MMU translations. Are you sure the PWM subsystem is enabled? Check that the PWM is not in IDLE or Standby mode and the clock is enabled.

Regards,
John

Thank you James, it works now! getpagesize() returns 4096, this matches the previous map_size which is a nice sanity check. I like that this would dynamically change with different modules as some are 1 or 2k.

The difference that made this work is casting the virtual address (pwm_addr in your code) as a uint8_t*. I do not understand why the pointer should change based on the type it is pointing to. To demonstrate this, the below code prints both addresses to the console to show the same result.

Also of note is that reading in this manner seems to take ~.36 us. Not PRU fast, but I presume this is as fast as we can get it in userspace land.

I apologize for the long code snipped but I wanted to post a complete solution here for anyone curious.

`
#include <stdio.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>

#define QPOSCNT 0x0000
#define EQEP0_BASE 0x48300180
#define QREVID 0x005C

int main(){
int fd;
volatile unsigned long *map_base;
unsigned long target=EQEP0_BASE;
long eqep0_pos, revid;
struct timeval tv1, tv2;
unsigned long map_size = getpagesize();
unsigned long map_mask = map_size-1;

//O_SYNC makes the memory uncacheable
if ((fd = open("/dev/mem", O_RDWR | O_SYNC))==-1) {
printf(“Could not open /dev/mem\n”);
return 0;
}
printf(“opened /dev/mem\n”);

//&~map_mask truncates the address to the base of the 4K Page
map_base=(uint8_t*)mmap(0, map_size, PROT_READ|PROT_WRITE,MAP_SHARED,fd, target&~map_mask);
if(map_base == (void ) -1) {
printf(“Unable to mmap eqep\n”);
exit(-1);
}
//&map_mask adds the position within the page to the address, this is 0x180 for eEQP0
unsigned long
eqep0_addr = (uint8_t *)map_base + (target&map_mask);

//time a read-loop to assess speed
int num_reads = 1000000;
int i;
gettimeofday(&tv1,NULL);
for(i=0;i<num_reads;i++){
eqep0_pos = *((uint8_t *)eqep0_addr + QPOSCNT);
}
gettimeofday(&tv2,NULL);
revid = *(uint32_t *)((uint8_t *)eqep0_addr+QREVID);

//find difference between start and end time
unsigned long dt_micros = (1000000 * tv2.tv_sec + tv2.tv_usec)-(1000000 * tv1.tv_sec + tv1.tv_usec);
float time_per_read = (float)dt_micros/num_reads;

printf(“last position %ld\n”, eqep0_pos);
printf(“micros per read %f\n”, time_per_read);
printf(“pagesize %d\n”, getpagesize());
printf(“map_base 0x%lX\n”,map_base);
printf("(uint8_t *)map_base 0x%lX\n", (uint8_t *)map_base);
printf(“target&map_mask 0x%lX\n”,target&map_mask);
printf(“revid 0x%lX (should read 44D31103)\n”,revid);
close(fd);
return 0;
}
`

output:

opened /dev/mem last position 71 micros per read 0.389692 pagesize 4096 map_base 0xB6FF1000 (uint8_t *)map_base 0xB6FF1000 target&map_mask 0x180 revid 0x44D31103 (should read 44D31103)

it seems my excitement was short-lived. While reading the position with the previous (and attached) code does work, it only does so when Teknoman’s eqep driver is loaded. I’ve added writes to set up the PWMSS and eQEP configuration registers and have confirmed by reading them back that they are set up the same as the driver does. Any ideas on what I’m missing?

`

// Write the decoder control settings
(unsigned short)(pwm_map_base[0]+EQEP_OFFSET+QDECCTL) = 0;
// set maximum position to two’s compliment of -1, aka UINT_MAX
(unsigned long)(pwm_map_base[0]+EQEP_OFFSET+QPOSMAX)=-1;
// Enable interrupt
(uint16_t)(pwm_map_base[0]+EQEP_OFFSET+QEINT) = UTOF;
// set unit period register
(unsigned long)(pwm_map_base[0]+EQEP_OFFSET+QUPRD)=0x5F5E100;
// enable counter in control register
(unsigned short)(pwm_map_base[0]+EQEP_OFFSET+QEPCTL) = PHEN|IEL0|SWI|UTE|QCLM;

`

SYSCONFIG 0xC CLKCONFIG 0x111 QEPCTL0 0x9E QDECCTL0 0x0 QEINT0 0x800 QUPRD0 0x5F5E100 QPOSMAX0 0xFFFFFFFF QEPSTS0 0xA0 eqep0: -174 eqep1: 544 e^Cp2: 0

mmap_pwm.rar (8.01 KB)

Strawson,

It looks like you’re not turning the PWM EQEP clock on. There should be something to accomplish what this line from the kernel driver does:

`
// Enable the clock to the eQEP unit
status = pwmss_submodule_state_change(pdev->dev.parent, PWMSS_EQEPCLK_EN);

`

I haven’t tried this out, but it should be something like

`
(unsigned long)(pwm_map_base[0]+PWMSS_CLKCONFIG) = PWMSS_EQEPCLK_EN;

`

I believe it is enabled. From my last post: the PWMSS clock config returns

`
CLKCONFIG 0x111 = 000100010001

`

According to pg 1492 of the reference manual, PWMSS_EQEP_EN is bit 4 in this register which appears to be true. Furthermore the interrupt timer register QUTMR
is incrementing away just fine. Good idea, but no dice.

For good measure I will write this bit anyway, but with |= instead of = since some bits are not writable and I wouldn’t want to erase the enable bits for pwm and ecap.

I’ll certainly take a look at this next week. Currently in the middle of finals and Maker Faire is this weekend. And in all honesty, once i figure out whats going on with the driver, its still a good idea to use the kernel based implementation, mainly because you can take advantage of hardware interrupts and not have to busy wait. I know there are sleep methods, but unless something like Xenomai is being used, you’re at the mercy of the scheduler…

Although, just to rule it out - are you still applying a device tree overlay? The supplied DTS files do more than just initialize the driver, they setup the IO configuration, as the default board config doesn’t bring out the eQEP lines.

  • Nathaniel Lewis

Hi Nathaniel. The correct device tree fragments are loaded, pins are multiplexed correctly, and all 3 eqep channels work perfectly with your driver. The supplied mmap code from my last post also works perfectly, but only if I insmod the compiled .ko kernel module first. Strange, I know.

Actually, let me be more specific. I use the pinmux lines and enable the PWM Subsystem as follows

`


fragment@0 {
        target = <&am33xx_pinmux>;
        __overlay__ {
             pinctrl_eqep0: pinctrl_eqep0_pins {
                     pinctrl-single,pins = <
                        0x1A8 0x21  /* P9_41 = GPIO3_20 = EQEP0_index, MODE1 */        
                        0x1AC 0x21  /* P9_25 = GPIO3_21 = EQEP0_strobe, MODE1 */   
                        0x1A0 0x31  /* P9_42 = GPIO3_18 = EQEP0A_in, MODE1 */       
                        0x1A4 0x31  /* P9_27 = GPIO3_19 = EQEP0B_in, MODE1 */       
                       >;
              };
        };
    };
    
    fragment@1 {
          target = <&epwmss0>;
            __overlay__ {
                   status = "okay";
        };
    };

`

I am not using the following fragment passing parameters to the kernel driver

`


fragment@2 {
            target = <&eqep0>;
      __overlay__ {
            pinctrl-names = "default";
            pinctrl-0 = <&pinctrl_eqep0>;
            
            count_mode = <0>;  /* 0 - Quadrature mode, normal 90 phase offset cha & chb.  1 - Direction mode.  cha input = clock, chb input = direction */
            swap_inputs = <0>; /* Are channel A and channel B swapped? (0 - no, 1 - yes) */
            invert_qa = <1>;   /* Should we invert the channel A input?  */
            invert_qb = <1>;   /* Should we invert the channel B input? I invert these because my encoder outputs drive transistors that pull down the pins */
            invert_qi = <0>;   /* Should we invert the index input? */
            invert_qs = <0>;   /* Should we invert the strobe input? */
            
         status = "okay";
        };
    };

`

This is for two reasons. Firstly, I don’t see how this would change the behavior of the hardware if I’m setting the registers anyway. Secondly, it fails to load because eqep is not a part of the default am335x-boneblack.dtb located in /boot/uboot/dtbo in debian

When trying to load this, dmesg returns

`

[ 523.086537] bone-capemgr bone_capemgr.9: slot #10: dtbo ‘bone_eqep0-00A0.dtbo’ loaded; converting to live tree
[ 523.090030] of_resolve: Could not find symbol ‘eqep0’
[ 523.095581] bone-capemgr bone_capemgr.9: slot #10: Failed to resolve tree

`

In Angstrom, this file was located in /boot/am335x-boneblack.dtb. Attached is a modified version of this that was provided by TI and results in the eqep dts loading correctly in angstrom. This file has since changed in the Debian release and the fix no longer works. As stated in the first post in this thread, it would be nice if the correct eqep entries and your driver were included in the public image so that this functionality can be used out of the box like pwm.

am335x-boneblack.dtb (25 KB)

In Angstrom, this file was located in /boot/am335x-boneblack.dtb. Attached
is a modified version of this that was provided by TI and results in the
eqep dts loading correctly in angstrom. This file has since changed in the
Debian release and the fix no longer works. As stated in the first post in
this thread, it would be nice if the correct eqep entries and your driver
were included in the public image so that this functionality can be used out
of the box like pwm.

<cough>

Well it didn't make the time deadline for "2014-04-23"..

</cough>

But if you do:

cd /opt/scripts/tools
git pull
sudo ./update_kernel.sh

It'll pull something newer, as the (TIEQEP) was added in 3.8.13-bone48 release.

https://github.com/RobertCNelson/linux-dev/commit/b7bc988c048b69f03b71c72f7c544f2348f378d0

Although if you find a bug, i expect a patch. :wink:

Regards,

I don’t believe that actually will change the I/O configuration. For the pin ctrl entry to be adopted, it needs to be used by some driver. Turns out there is a “pinmux helper” device. Check out this blog post: http://hipstercircuits.com/enable-serialuarttty-on-beaglebone-black/. More specifically, this section:

`

fragment@2 {
        target = <&ocp>;
        __overlay__ {
            test_helper: helper {
                compatible = "bone-pinmux-helper";
                pinctrl-names = "default";
                pinctrl-0 = <&pinctrl_uart5>;
                status = "okay";
            };
        };
    };

`

  • Nathaniel

Robert held true to his word: The 2014-5-14 debian image includes the tieqep driver and works correctly when teknoman’s device tree overlays (or my own) are loaded. Thank you Thank you Thank you!

I am still not sure why the mmap code I attached above only works with that driver, but I suppose I will never find out since it works out of the box on the 2014-5-14 debian image.

Thank you everyone for your help!

With cape-universal, I believe our new path is to include stuff like this
in GitHub - cdsteinkuehler/beaglebone-universal-io: Device tree overlay and support scripts for using most available hardware I/O on the BeagleBone without editing dts files or rebuilding the kernel. In that case,
you could simply do something like:
root@beaglebone:~# config-pin P8.16 qep
root@beaglebone:~# config-pin -q P8.16
P8_16 Mode: qep
root@beaglebone:~# cat /sys/devices/ocp.3/P8_16_pinmux.24/state
qep

However, with HDMI enabled, I'm not able to find a valid set of pins to put
together an entire eQEP. Further, the entries for an eqep don't seem to be
in cape-universal. There has been some discussion if they are necessary,
but I haven't been able to expose an eqep as of yet. I'll disable HDMI and
reboot next, but can those with experience comment on if something like
this is necessary:

diff --git a/cape-universal-00A0.dts b/cape-universal-00A0.dts
index ad5b388..bc6c005 100755
--- a/cape-universal-00A0.dts
+++ b/cape-universal-00A0.dts
@@ -602,7 +602,7 @@
             P9_27_gpio_pd_pin: pinmux_P9_27_gpio_pd_pin {
                 pinctrl-single,pins = <0x1a4 0x27>; }; /* Mode
7, Pull-Down, RxActive */
             P9_27_qep_pin: pinmux_P9_27_qep_pin {
- pinctrl-single,pins = <0x1a4 0x21>; }; /* Mode
1, Pull-Down, RxActive */
+ pinctrl-single,pins = <0x1a4 0x31>; }; /* Mode
1, Pull-Up, RxActive */
             P9_27_pruout_pin: pinmux_P9_27_pruout_pin {
                 pinctrl-single,pins = <0x1a4 0x25>; }; /* Mode
5, Pull-Down, RxActive */
             P9_27_pruin_pin: pinmux_P9_27_pruin_pin {
@@ -752,7 +752,7 @@
             P9_92_gpio_pd_pin: pinmux_P9_92_gpio_pd_pin {
                 pinctrl-single,pins = <0x1a0 0x27>; }; /* Mode
7, Pull-Down, RxActive */
             P9_92_qep_pin: pinmux_P9_92_qep_pin {
- pinctrl-single,pins = <0x1a0 0x21>; }; /* Mode
1, Pull-Down, RxActive */
+ pinctrl-single,pins = <0x1a0 0x31>; }; /* Mode
1, Pull-Up, RxActive */
             P9_92_pruout_pin: pinmux_P9_92_pruout_pin {
                 pinctrl-single,pins = <0x1a0 0x25>; }; /* Mode
5, Pull-Down, RxActive */
             P9_92_pruin_pin: pinmux_P9_92_pruin_pin {
@@ -1727,4 +1727,24 @@
         };
     };