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.

AM62A7: Unable to get eCAPs to trigger interrupt

Part Number: AM62A7
Other Parts Discussed in Thread: SYSCONFIG

Hi Experts,

I'm currently using a custom board with AM62A7 SoC. Using the A53 cores running Linux, I'm trying to write a composite driver that read in eCAP latched values and trigger SPI transfer. While the driver seem to compiled file and no complaints about the DTS, I'm unable to get any interrupt triggers. Sure enough, when looking at /proc/interrupts, I see zero counts for interrupts. I'm suspecting I described something wrong with my DTS but unable to pinpoint what.

Purpose of the driver:

  1. PPS signal to eCAP0 - read latched value, stores it
  2. IMU trigger signal to eCAP1 - read latched value, stores it, initiate SPI transfer over spi0 with the IMU.
  3. Userspace reads back PPS ticks, IMU trigger ticks, and IMU data

DTS snippet:

 &main_pmx0 {
        imu_ecap_pins_default: imu_ecap-default-pins {
                pinctrl-single,pins = <
                    AM62AX_IOPAD(0x019c, PIN_INPUT, 2) /* (B18) MCASP0_AXR1.ECAP1_IN_APWM_OUT */
                >;
        };
        pps_ecap_pins_default: pps_ecap-default-pins {
                pinctrl-single,pins = <
                    AM62AX_IOPAD(0x01f0, PIN_INPUT, 8) /* (B16) EXT_REFCLK1.ECAP0_IN_APWM_OUT */
                >;
        };
        spi_imu_pins_default: spi_imu-default-pins {
		pinctrl-single,pins = <
			AM62AX_IOPAD(0x01bc, PIN_INPUT, 0) /* (A17) SPI0_CLK */
			AM62AX_IOPAD(0x01c0, PIN_OUTPUT, 0) /* (B15) SPI0_D0 */
			AM62AX_IOPAD(0x01c4, PIN_INPUT, 0) /* (E15) SPI0_D1 */
			AM62AX_IOPAD(0x01b4, PIN_OUTPUT, 0) /* (D16) SPI0_CS0 */
			AM62AX_IOPAD(0x01d4, PIN_OUTPUT, 1) /* (C15) UART0_RTSn.SPI0_CS3 */
		>;
	};
}; 

/* Enable the hardware blocks so they receive clocks and power */
 &ecap0 {
    status = "okay";
 };

 &ecap1 {
    status = "okay";
 };

 &main_spi0 {
        status = "okay";
        pinctrl-names = "default";
        pinctrl-0 = <&spi_imu_pins_default>; /* Pinmux for SPI CLK, D0, D1, CS */
        #address-cells = <1>;
        #size-cells = <0>;

        /* Define our Device as a SPI Slave */
        am62ax-pps-imu@0 {
            compatible = "ti,am62ax-pps-imu";
            reg = <0>; /* Chip Select 0 */
            
            spi-max-frequency = <10000000>;
            
            /* Pass eCAP nodes as references */
            ti,ecap-pps = <&ecap0>;
            ti,ecap-imu = <&ecap1>;

            /* Interrupts */
            interrupt-names = "pps_irq", "imu_irq";
            interrupts = <GIC_SPI 113 IRQ_TYPE_EDGE_RISING>, 
                                <GIC_SPI 114 IRQ_TYPE_EDGE_RISING>;

            pinctrl-names = "default";
            pinctrl-0 =  <&pps_ecap_pins_default &imu_ecap_pins_default>;
        };
};

Also, when I checked cat /sys/kernel/debug/clk/clk_summary , I do see "23100000.pwm" and "23110000.pwm" have a rate of 125MHz. So it appears that eCAP0 and eCAP1 are enabled.

Any ideas what I have been doing wrong?

Regards,

Danny 

  • Hello Danny,

    Real-time requirements

    Do you have any real-time requirements for the timing between receiving the PPS signal and triggering the SPI transfer? Or can the SPI transfer happen "whenever"?

    If you do have real-time requirements, please let me know what those are (even something like "within 10 msec" is a real-time requirement).

    Make sure you are using the correct driver 

    The ECAP hardware can be used as either a capture input, or as a PWM output. The driver that you are using is a PWM output driver.

    Members of the Linux community have written an ECAP capture driver that you can use. Please keep in mind that this driver was not written or tested by TI, so I have not used the driver myself, and I do not have any TI documentation about how to use the driver. https://git.ti.com/cgit/ti-linux-kernel/ti-linux-kernel/log/drivers/counter/ti-ecap-capture.c?h=ti-linux-6.12.y-cicd 

    You can find the bindings documentation here:
    Documentation/devicetree/bindings/counter/ti,am62-ecap-capture.yaml

    and the example devicetree overlay is here:
    arch/arm64/boot/dts/ti/k3-am625-sk-ecap-capture.dtso

    Regards,

    Nick

  • Hi Nick,

    Thank you for the reply and apologies for the late response.

    There are two independent signals: one driving PPS and the other signaling time to initiate SPI transfer. The signaling for SPI transfer needs to initiate SPI transfer as quickly as possible since the signal is at 3.6kHz, in addition to read eCAP1 timestamp (hw ticks). PPS signal would trigger reading of eCAP0 timestamp (hw ticks).

    You're right about the driver. I had since removed compatibility by adding the following:

    &ecap0 {
    status = "okay";
    /* Delete compatible to prevent standard PWM driver from binding */
    /delete-property/ compatible;
    };
    
    &ecap1 {
    status = "okay";
    /delete-property/ compatible;
    };

    Since I still couldn't get the eCAP interrupt to trigger, I tried another approach by leveraging the DSIS pad mode and attempt to trigger the interrupts via GPIO.

    &main_spi0 {
        status = "okay";
        pinctrl-names = "default";
        pinctrl-0 = <&spi_imu_pins_default>;
        #address-cells = <1>;
        #size-cells = <0>;
    
        imu_ptp_master: am62ax-ptp-master@0 {
            compatible = "ti,am62ax-ptp-master";
            reg = <0>;
            spi-max-frequency = <10000000>; /* 10MHz for fast word transfer */
            
            power-domains = <&k3_pds 51 TI_SCI_PD_EXCLUSIVE>,
                            <&k3_pds 52 TI_SCI_PD_EXCLUSIVE>;
            clocks = <&k3_clks 51 0>, <&k3_clks 52 0>;
            clock-names = "fck_pps", "fck_imu";
            
            /* Define GPIO handles for B16 (GPIO1_30) and B18 (GPIO1_9) */
            pps-gpios = <&main_gpio1 30 GPIO_ACTIVE_HIGH>; 
            imu-gpios = <&main_gpio1 9 GPIO_ACTIVE_HIGH>;
            
            /* References to eCAP hardware blocks for base addresses */
            ti,ecap-pps = <&ecap0>;
            ti,ecap-imu = <&ecap1>;
    
            /* Pinmuxing for B16 (PPS) and B18 (IMU) */
            pinctrl-names = "default";
            pinctrl-0 = <&pps_ecap_pins_default &imu_ecap_pins_default>;
        };
    };

    If I'm not mistaken, there is not need for me to use any eCAP capture driver, right? Assuming that I setup the registers correctly, I can just read off the eCAP counter/capture in my composite driver. The issue is that I can't seem to get the interrupts to fire.

  • Hello Danny,

    Custom driver development

    Custom driver development is outside the scope of support that I can offer. But let's talk a bit more about usecase to make sure that you are using the right tools for your design.

    Using the Linux ECAP driver 

    I was actually able to find some internal conversations around the ECAP capture driver, so it does look like the developer was at least talking with some TI folks while they were doing development. Here are the notes that I was able to find, maybe they will help:

    Userspace commands :
    	### CLOCK SIGNAL ###
    	cd /sys/bus/counter/devices/counter0/signal0
    
    	# Get frequency
    	cat frequency
    
    	### INPUT SIGNAL ###
    	cd /sys/bus/counter/devices/counter0/signal1
    
    	# Get available polarities for each capture event
    	cat polarity0_available
    	cat polarity1_available
    	cat polarity2_available
    	cat polarity3_available
    
    	# Get polarity for each capture event
    	cat polarity0
    	cat polarity1
    	cat polarity2
    	cat polarity3
    
    	# Set polarity for each capture event
    	echo positive > polarity0
    	echo negative > polarity1
    	echo positive > polarity2
    	echo negative > polarity3
    
    	### COUNT ###
    	cd /sys/bus/counter/devices/counter0/count0
    
    	# Get ceiling (counter max value)
    	cat ceiling
    
    	# Reset number of overflows & current timebase counter value
    	echo 0 > num_overflows
    	echo 0 > count
    
    	# Run ECAP
    	echo 1 > enable
    
    	# Get number of overflows & current timebase counter value
    	cat num_overflows
    	cat count
    
    	# Get captured timestamps
    	cat capture0
    	cat capture1
    	cat capture2
    	cat capture3
    
    	# Note that counter watches can also be used to get
    	# data from userspace application
    	# -> see tools/counter/counter_example.c
    
    	# Pause ECAP
    	echo 0 > enable
    
    How to test this driver ?
    	1) Device tree modifications
    
    	1.a) k3-am62-main.dtsi
    
    	ecap2: capture@23120000 {
    		compatible = "ti,am62-ecap-capture";
    		reg = <0x00 0x23120000 0x00 0x100>;
    		interrupts = <GIC_SPI 115 IRQ_TYPE_EDGE_RISING>;
    		power-domains = <&k3_pds 53 TI_SCI_PD_EXCLUSIVE>;
    		clocks = <&k3_clks 53 0>;
    		clock-names = "fck";
    	};
    
    	Warning : ecap2 node already used as pwm by default
    	-> must be replaced (as above) or overlayed for capture testing.
    
    	1.b) k3-am625-sk.dts
    
    	&main_pmx0 {
    		main_ecap2_pins_default: main-ecap2-pins-default {
    			pinctrl-single,pins = <
    				AM62X_IOPAD(0x01a4, PIN_INPUT, 2) /* (B20) MCASP0_ACLKX.ECAP2_IN_APWM_OUT */
    			>;
    		};
    	}
    
    	&ecap2 {
    		pinctrl-names = "default";
    		pinctrl-0 = <&main_ecap2_pins_default>;
    		status = "okay";
    	};
    
    	2) Kernel configuration
    
    	Device Drivers  --->
    		<*> Counter support  --->
    			<*>   TI eCAP capture driver
    
    	3) Once ECAP2 is probed, userspace commands can be used as explained above.
    
    	ECAP2 input pin is available on connector J3 pin 11 for square waveform signal.
    
    	For finest testing, this following userspace application code can be run :
    	(stdout MUST be redirected to log file)
    
    	// SPDX-License-Identifier: GPL-2.0-only
    	/* Counter - example userspace application
    	 *
    	 * The userspace application opens /dev/counter0, configures the
    	 * COUNTER_EVENT_CAPTURE event channels to gather count 0 capture
    	 * events, and prints out the data as it becomes available on the
    	 * character device node.
    	 *
    	 * Copyright (C) 2022 Julien Panis
    	 */
    	#include <errno.h>
    	#include <fcntl.h>
    	#include <linux/counter.h>
    	#include <stdio.h>
    	#include <string.h>
    	#include <sys/ioctl.h>
    	#include <unistd.h>
    
    	/* assuming capture attributes are under the count0 directory */
    	#define CAPTURE_WATCH(_id) \
    	{ \
    		.component.type = COUNTER_COMPONENT_EXTENSION, \
    		.component.scope = COUNTER_SCOPE_COUNT, \
    		.component.parent = 0, \
    		.component.id = _id, \
    		.event = COUNTER_EVENT_CAPTURE, \
    		.channel = _id, \
    	}
    
    	/* get id from respective "captureX_component_id" attributes */
    	#define NB_CAP 4
    	static struct counter_watch watches[NB_CAP] = {
    		CAPTURE_WATCH(0),
    		CAPTURE_WATCH(1),
    		CAPTURE_WATCH(2),
    		CAPTURE_WATCH(3),
    	};
    
    	int main(void)
    	{
    		int fd;
    		int ret;
    		int i;
    		struct counter_event event_data[NB_CAP];
    
    		fd = open("/dev/counter0", O_RDWR);
    		if (fd == -1) {
    			perror("Unable to open /dev/counter0");
    			return 1;
    		}
    
    		for (i = 0; i < NB_CAP; 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;
    		}
    
    		for (;;) {
    			ret = read(fd, event_data, sizeof(event_data));
    			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;
    			}
    
    			/* [WARNING] For data consistency analysis, stdout must be redirected to log file !!! */
    			puts("----------");
    			printf("cap0: %llu\n", event_data[0].value);
    			printf("cap1: %llu\n", event_data[1].value);
    			printf("cap2: %llu\n", event_data[2].value);
    			printf("cap3: %llu\n", event_data[3].value);
    		}
    
    		return 0;
    	}

    Let's talk about the usecase

    These are my current assumptions. Please let me know if I got anything wrong:

    1) You are using RT Linux, NOT regular Linux (Starting in Linux kernel 6.12, this is just a kernel configuration instead of a totally separate patchset). You just described a real-time requirement to me. RT Linux will have a much easier time consistently making real-time requirements than regular Linux

    2) The SPI write is triggered by the nondeterministic signal, and needs to happen within a set amount of time (real-time requirement)

    3) The PPS signal triggers an application to read the timestamps for whenever a SPI write was triggered in the past second (non-real time requirement)

    How much time do you have to initiate the SPI write after the signal lands at the processor pin?

    Is this a hard real-time requirement? (i.e., needs to meet the timing requirement EVERY time) Or do you just need to meet the timing requirements most of the time (like 99.8% of the time)

    Regards,

    Nick

  • Okay, I'll double check with the ECAP driver. Perhaps it'll give me some clue as to what I'm doing wrong.

    As for your assumptions,

    1) That's correct, I'm using RT Linux.

    2) Sorry, I should have been more clear. The signal to initiate SPI transfer IS deterministic 3.6kHz signal. Another words, I'm supposed to make a SPI transfer every 278us and must be made before next signal arrives, i.e. have a window of 278us to initiate SPI transfer (preferably done much early, of course). At this same time, I also need to read the latched value in the ECAP1 to capture the time of arrival of the signal.

    3) PPS is a separate signal routed to a separate ECAP (ECAP0). Upon PPS signal arrival, my driver supposed to read the latched value and compute the equivalent system time. The idea is that by comparing PPS ECAP0 ticks and the 3.6kHz ECAP1 ticks, I can get the relative timing of the signals. My PPS source is extremely accurate.

    These are hard real-time requirements.

    My main issue is that I'm not seeing the interrupt counts increasing when I take a look at the /proc/interrupts. I'm suspecting it's my DTS file where either I'm not initializing the ECAPs correctly or using the correct Interrupts or both (being a complete novice, this is highly probable).

    My Usecase

    I have a PPS signal from a GNSS receiver and I have an IMU that I would like to sample ~18 per second. The IMU is communicated through SPI. It is important to capture precisely the time of validity of the IMU sample, anchored by PPS signal.

  • Hello Danny,

    Hmm, ok. I get nervous whenever anyone brings up hard real-time requirements in Linux in the range of microseconds.

    I am not a deep expert on RT Linux optimization, but in my limited testing 200-300 usec is in between the timings that are definitely not possible with RT Linux (e.g., tens of usec or less is NOT compatible with A53 cores running RT Linux), and timings that definitely ARE possible (requirements in the msec range are very reasonable in my testing). This is the window where obscure things like whether the random number generation machine is properly optimized in this exact version of the Linux kernel can have a major impact on whether or not your timing gets messed up.

    Are you already using the MCU R5F core for tasks on your device? Would it make sense to consider using that core for the RT work instead?

    (I am not saying this definitely isn't possible with RT Linux, just considering options)

    Regards,

    Nick

  • The MCU R5F is the fallback plan. We have tasks planned for R5F but if these RT workload is not possible on the A53 running RT Linux, then we will try squeezing it into R5F. One of the factor that we were considering A53 over R5F is that R5F do not have access to DMA, as per SysConfig tool.

    Does this mean it is possible for the R5F can have access to the ECAP peripherals?

  • Hi,

    The thread owner is out of office till until week of Feb 17. Please ping the thread if you do not get an update during that week.

    Thank you for your patience.

    Regards,
    Harshith