This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

SK-AM62: monitoring event of eQEP on Linux

Part Number: SK-AM62

Tool/software:

Hi,

We are developing the device using am62x with processor SDK 09.02.01.10.

Is there a way to monitor events emitted by eQEP e.g. COUNTER_EVENT_OVERFLOW, COUNTER_EVENT_UNDERFLOW?

According to the following page, this is possible by using COUNTER_ADD_WATCH_IOCTL.

https://docs.kernel.org/driver-api/generic-counter.html#userspace

When I tried to get an OVERFLOW event using tools/counter/counter_example.c in linux-ti-staging, the kernel was unable to read the event and displayed the following message.

"Unable to handle kernel NULL pointer dereference at virtual address 0000000000000010"

If you have any advice or ideas please let me know.

The source code for counter_example.c is attached.

// SPDX-License-Identifier: GPL-2.0-only
/* Counter - example userspace application
 *
 * The userspace application opens /dev/counter0, configures the
 * COUNTER_EVENT_INDEX event channel 0 to gather Count 0 count and Count
 * 1 count, and prints out the data as it becomes available on the
 * character device node.
 *
 * Copyright (C) 2021 William Breathitt Gray
 */
#include <errno.h>
#include <fcntl.h>
#include <linux/counter.h>
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <poll.h>

static struct counter_watch watches[1] = {
	{
		/* Component data: Count 0 count */
		.component.type = COUNTER_COMPONENT_COUNT,
		.component.scope = COUNTER_SCOPE_COUNT,
		.component.parent = 0,
		/* Event type: overflow */
		.event = COUNTER_EVENT_OVERFLOW,
		/* Device event channel 0 */
		.channel = 0,
	},
};

int main(void)
{
	int fd;
	int ret;
	int i;
	struct counter_event event_data[1];

	fd = open("/dev/counter0", O_RDWR);
	if (fd == -1) {
		perror("Unable to open /dev/counter0");
		return 1;
	}
	for (i = 0; i < 1; i++) {
		ret = ioctl(fd, COUNTER_ADD_WATCH_IOCTL, watches + i);
		if (ret == -1) {
			fprintf(stderr, "Error adding watches[%d]: %s\n", i,
				strerror(errno));
			return 1;
		}
	}
	ret = ioctl(fd, COUNTER_ENABLE_EVENTS_IOCTL);

	if (ret == -1) {
		perror("Error enabling events");
		return 1;
	}
	struct pollfd fds = {
            .fd = fd,
            .events = POLLIN,
        };
	for (;;) {
		printf("polling\n");
		int ret = poll(&fds, 1, -1);
		if (ret >= 1) {

			printf("read start\n");
			ret = read(fd, event_data, sizeof(event_data));
			printf("read end\n");
			if (ret == -1) {
				perror("Failed to read event data");
				return 1;
			}

			if (ret != sizeof(event_data)) {
				fprintf(stderr, "Failed to read event data\n");
				return -EIO;
			}

			printf("Timestamp 0: %llu\tCount 0: %llu\n"
			       "Error Message 0: %s\n",
			       event_data[0].timestamp, event_data[0].value,
			       strerror(event_data[0].status));
		} else {
			perror("poll()");
			close(fd);
			return -1;
		}
	}

	return 0;
}

Best regards,

Takayuki

  • Hello Takayuki-san,

    First off to set expectations, we are going to be limited in the support that we can offer when it comes to writing custom userspace code.

    It does look like the ti-eqep driver gives the option to configure events for COUNTER_EVENT_OVERFLOW and COUNTER_EVENT_UNDERFLOW:
    drivers/counter/ti-eqep.c

    static int ti_eqep_events_configure(struct counter_device *counter)
    {
            struct ti_eqep_cnt *priv = ti_eqep_count_from_counter(counter);
            struct counter_event_node *event_node;
            u32 qeint = 0;
    
            list_for_each_entry(event_node, &counter->events_list, l) {
                    switch (event_node->event) {
                    case COUNTER_EVENT_OVERFLOW:
                            qeint |= QEINT_PCO;
                            break;
                    case COUNTER_EVENT_UNDERFLOW:
                            qeint |= QEINT_PCU;
                            break;
                    case COUNTER_EVENT_DIRECTION_CHANGE:
                            qeint |= QEINT_QDC;
                            break;
                    case COUNTER_EVENT_TIMEOUT:
                            qeint |= QEINT_UTO;
                            break;
                    }
            }
    
            return regmap_write_bits(priv->regmap16, QEINT, qeint, ~0);
    }
    

    I can see where overflow and underflow are being used by some sysfs entries:
    https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-bus-counter

    but I won't be able to provide guidance on how to implement an event based on the generic counter API.

    I'm assuming you already found this page, but for future readers, you can find the TI documentation around the EQEP driver here:
    https://software-dl.ti.com/processor-sdk-linux/esd/AM62X/09_02_01_10/exports/docs/linux/Foundational_Components/Kernel/Kernel_Drivers/EQEP.html

    Regards,

    Nick

  • Hi, Nick-san,
    Thank you for your reply.
    In order to monitor the event, I am checking whether the interrupt set in the eQEP driver is being called.
    When an overflow occurs, the interrupt function does not appear to be called.
    Here's how to check. 
    1. Configure the device tree
    2. Insert printk into ti_eqep_irq_handler of eQEP driver. Compile and deploy
    3. Connect the connectors EXP_GPIO0_41 and EQEP0_A
    4. Switching GPIO between High and Low
    5. Confirm that /sys/bus/counter/devices/counter0/count0/count is incremented.
    6. Repeat the above and confirm that count returns to 0 when it reaches the value of /sys/bus/counter/devices/counter0/count0/ceiling.
    When it reached 6, the character I had entered in printk was not printed.
    I am attaching the device tree settings, so please let me know if there are any settings missing.
     
    &main_pmx0 {
            main_eqep0_pins_default: main-eqep0-pins-default {
                    pinctrl-single,pins = <
                            AM62X_IOPAD(0x0194, PIN_INPUT, 8) /* (B19) MCASP0_AXR3.EQEP0_A */
                            AM62X_IOPAD(0x0198, PIN_INPUT, 8) /* (A19) MCASP0_AXR2.EQEP0_B */
                            AM62X_IOPAD(0x01a0, PIN_INPUT, 8) /* (E18) MCASP0_AXR0.EQEP0_I */
                            AM62X_IOPAD(0x019c, PIN_INPUT, 8) /* (B18) MCASP0_AXR1.EQEP0_S */
                    >;
            };
    
            main_gpio0_pins_default: main-gpio0-pins-default {
                    pinctrl-single,pins = <
                            AM62X_IOPAD(0x00a8, PIN_OUTPUT, 7) /* (J18) GPMC0_CSn0.GPIO0_41 */
                    >;
            };
    
    &eqep0 {
            status = "okay";
            pinctrl-names = "default";
            pinctrl-0 = <&main_eqep0_pins_default>;
    };
    
    &main_gpio0 {
            pinctrl-names = "default";
            pinctrl-0 = <&main_gpio0_pins_default>;
    };
    Regards,
    Takayuki
  • Hello Takayuki-san,

    It looks like the driver is writing to the correct register address.

    Off the top of my head I am not sure whether this is the proper way to call .events_configure from the driver:
    ret = ioctl(fd, COUNTER_ADD_WATCH_IOCTL, watches + i);

    Please use a tool like devmem2 to check the value of the QEINT register after you expect it to be set.
    I would expect that to be at offset 0x30 from 0x0023200000

    Regards,

    Nick

  • Hi Nick-san,

    Thank you for your reply.

    I can call the interrupt function by setting QEINT using devmem2.

    However, a NULL pointer dereference error occurs in the counter_push_event() function.

    Is there any way to deal with this?

    Regards,

    Takayuki

  • Hello Takayuki-san,

    It will take me a couple of days to take another look at this.

    To confirm, you are not seeing the QEINT register values getting set by the userspace code, right?

    When does the NULL pointer dereference error occur? Do you have terminal output I can look at?

    Regards,

    Nick

  • Hello Nick-san,

    Here are the steps to reproduce the issue.

    Run the following command:

    echo 461 > /sys/class/gpio/export
    echo out > /sys/class/gpio/gpio461/direction
    echo 0 > /sys/class/gpio/gpio461/value
    
    echo increase > /sys/bus/counter/devices/counter0/count0/function
    echo 2 > /sys/bus/counter/devices/counter0/count0/ceiling
    echo 1 > /sys/bus/counter/devices/counter0/count0/enable

    Use devmem2 to set the PCO for QEINT.

    root@am62xx-evm:~# devmem2 0x0023200030 l 0x00000040
    /dev/mem opened.
    Memory mapped at address 0xffff96f67000.
    Read at address 0x23200030 (0xffff96f67030): 0x0000000000000000
    Write at address 0x23200030 (0xffff96f67030): 0x0000000000000040, readback 0x0000000000000040

    An overflow event will be generated by switching the GPIO between High and Low with the following command.

    At that point, a kernel error will be displayed on the console.

    echo 1 > /sys/class/gpio/gpio461/value
    echo 0 > /sys/class/gpio/gpio461/value
    echo 1 > /sys/class/gpio/gpio461/value

    The same error occurs if a process to set QEINT in the kernel driver is added.

    Regards,

    Takayuki

  • Hello Nick-san,

    I found a way to resolve the issue. The following patch will fix the error.

    diff --git a/drivers/counter/ti-eqep.c b/drivers/counter/ti-eqep.c
    index b33e94f82c01..50436e22fbf4 100644
    --- a/drivers/counter/ti-eqep.c
    +++ b/drivers/counter/ti-eqep.c
    @@ -765,8 +765,8 @@ static struct counter_comp ti_eqep_device_ext[] = {
     
     static irqreturn_t ti_eqep_irq_handler(int irq, void *dev_id)
     {
    -	struct ti_eqep_cnt *priv = dev_id;
    -	struct counter_device *counter = &priv->counter;
    +	struct counter_device *counter = dev_id;
    +	struct ti_eqep_cnt *priv = counter_priv(counter);
     	u32 qflg;
     	u32 qclr = 0;
     
    @@ -861,7 +861,7 @@ static int ti_eqep_probe(struct platform_device *pdev)
     		return irq;
     
     	err = devm_request_threaded_irq(dev, irq, NULL, ti_eqep_irq_handler,
    -					IRQF_ONESHOT, dev_name(dev), priv);
    +					IRQF_ONESHOT, dev_name(dev), counter);
     	if (err < 0)
     		return err;
     
    

    This allows us to get counter events.

    Can you confirm the validity of this patch?

    Regards,

    Takayuki