Enabling GPIO interrupts in kernel driver?

Hi

I have a kernel driver that I need to be able to respond to an interrupt generated by a positive edge on a GPIO input (GPIO7_TOUCH_RST). From my research so far I think this is TX1 module pin B23 (TOUCH_RST).

From the Jetson TX1 Customer Config spreadsheet this is GPIO3_PV.06 which I have translated to GPIO number 174 using gpio-names.h

My driver code looks like this

...
#define DATA_READY_PIN 174
...
irqreturn_t Data_Ready_IRQHandler(int irq, void *dev_id)
{
    printk(KERN_ERR "Data Ready IRQ\n");
    return IRQ_HANDLED;
}

...

static int XPCIe_init(void)
{
  // Grab gpio pins
  int data_ready_irq_number;

  ...

  if(0 == gpio_request(DATA_READY_PIN,"ZYNQ Read data available"))
  {
    gpio_direction_input(DATA_READY_PIN);
    gpio_export(DATA_READY_PIN,false);
    data_ready_irq_number = gpio_to_irq(DATA_READY_PIN);
    if (0 > request_irq(data_ready_irq_number,
                        &Data_Ready_IRQHandler,
                        IRQF_SHARED | IRQF_TRIGGER_RISING,
                        gDrvrName, gDev))
    {
      printk(KERN_WARNING"%s: Init: Unable to allocate Data Ready IRQ",gDrvrName);
      return (CRIT_ERR);
    }
    else
    {
      printk(KERN_ERR "GPIO: %d IRQ: %d\n",DATA_READY_PIN, data_ready_irq_number);
    }
  }
  else
  {
      printk(KERN_ERR "Couldnt get DATA READY GPIO\n");
  }

  ...
}

...

// Driver Entry Point
module_init(XPCIe_init);

So when the code runs all the GPIO related calls succeed and the following is output to the dmesg log

[   41.719347] GPIO: 174 IRQ: <b>447</b>

However I get no interrupts when I toggle this pin :-(

Dumping the contents of /proc/interrupts

# cat /proc/interrupts
           CPU0       CPU1       CPU2       CPU3
 29:          0          0          0          0       GIC  arch_timer
 30:          0          0          0          0       GIC  arch_timer
 34:          0          0          0          0       GIC  tegra_rtc
 36:          0          0          0          0       GIC  bpmp
 37:          0          0          0          0       GIC  bpmp
 39:          0          0          0          0       GIC  bpmp
 46:          0          0          0          0       GIC  mmc2
 47:        750          0          0          0       GIC  mmc1
 49:          0          0          0          0       GIC  546c0000.i2c
 50:          0          0          0          0       GIC  bpmp
 52:          0          0          0          0       GIC  tegra-otg, tegra-udc
 53:          0          0          0          0       GIC  pmc_usb_phy_wake_isr
 63:       3925          0          0          0       GIC  mmc0
 68:       1781          0          0          0       GIC  serial

...

256:          0          0          0          0       GIC  adma.8
257:          0          0          0          0       GIC  adma.9
271:          0          0          0          0       GIC  adsp watchdog
274:          0          0          0          0       GIC  AMC error int
276:          0          0          0          0       GIC  adsp wfi
279:          0          0          0          0       GIC  adsp_cpu
334:          1          0          0          0      GPIO  <b>bluetooth hostwake</b>
461:          0          0          0          0      GPIO  <b>nct72</b>
462:          0          0          0          0      GPIO  <b>Power</b>
463:          0          0          0          0      GPIO  <b>Volume Up</b>
465:          0          0          0          0      GPIO  <b>Volume Down</b>
473:          0          0          0          0      GPIO  <b>1.extcon</b>
474:          0          0          0          0      GPIO  <b>mmc2</b>
532:          0          0          0          0  max77620-top  max77620-gpio
533:          0          0          0          0  max77620-top  max77620-rtc
536:          0          0          0          0  max77620-top  4-003c
537:          0          0          0          0  max77620-top  max77620-thermal.6
538:          0          0          0          0  max77620-top  max77620-thermal.6
539:          0          0          0          0  max77620-gpio  1.extcon
547:          0          0          0          0  soc_therm_oc  voltmon_oc1
548:          0          0          0          0  soc_therm_oc  batmon_oc3
IPI0:      1080       1647        739        773       Rescheduling interrupts
IPI1:        23        117        137        138       Function call interrupts
IPI2:         1          5          0          4       Single function call interrupts
IPI3:         0          0          0          0       CPU stop interrupts
IPI4:         0          0          0          0       CPU wakeup interrupts
IPI5:         0          0          0          0       Timer broadcast interrupts

Firstly I see that there is no entry for interrupt 447 but I do see entries for other gpios :-(

Secondly if I cat /sys/kernel/debug/tegra_gpio (I’ve reformatted this to make it more decipherable)

Name:Bank:Port  CNF	OE 	OUT     IN      INT_STA	INT_ENB         INT_LVL
	A: 0:0  24 	00 	00 	04      00 	00 		000000
	B: 0:1  0f 	00 	00 	00      00 	00 		000000
	C: 0:2  00 	00 	00 	00      00 	00 		000000
	D: 0:3  10 	10 	00 	00      00 	00 		000000
	E: 1:0  70 	00 	00 	00      00 	00 		000000
	F: 1:1  00 	00 	00 	00      00 	00 		000000
	G: 1:2  00 	00 	00 	00      00 	00 		000000
	H: 1:3  ff 	1b 	0a 	64      00 	20 		002024
	I: 2:0  0f 	0d 	01 	02      00  	00 		000000
	J: 2:1  00 	00 	00 	00      00 	00 		000000
	K: 2:2  f0 	20 	00 	d0      00 	00 		000000
	L: 2:3  02 	00 	00 	02      00 	00 		000000
	M: 3:0  00 	00 	00 	00      00 	00 		000000
	N: 3:1  00 	00 	00 	00      00 	00 		000000
	O: 3:2  00 	00 	00 	00      00 	00 		000000
	P: 3:3  00 	00 	00 	00      00 	00 		000000
	Q: 4:0  00 	00 	00 	00      00 	00 		000000
	R: 4:1  00 	00 	00 	00      00 	00 		000000
	S: 4:2  f0 	f0 	00 	00      00 	00 		000000
	T: 4:3  03 	03 	00 	00      00 	00 		000000
	U: 5:0  0c 	00 	00 	00      00 	00 		000000
	<b><u>V: 5:1  6e 	66 	00 	00      00 	00 		000000</u></b>
	W: 5:2  00 	00 	00 	00      00 	00 		000000
	X: 5:3  ff 	00 	00 	f4      00 	70 		606000
	Y: 6:0  03 	00 	00 	02      00 	01 		010100
	Z: 6:1  1f 	08 	00 	17      00 	03 		030300
       AA: 6:2  00 	00 	00 	00      00 	00 		000000
       BB: 6:3  0d 	04 	00 	09      00 	00 		000000
       CC: 7:0  32 	30 	20 	20      00 	00 		000000
       DD: 7:1  00 	00 	00 	00      00 	00 		000000
       EE: 7:2  00 	00 	00 	00      00 	00 		000000
       FF: 7:3  00 	00 	00 	00      00 	00 	        000000

So looking at port V (underlined above) using section 9.13 from TRM

--------------------7<u>6</u>543210  Bit 6 
CNF      0x6e       0<u>1</u>101110  1 - GPIO
OE       0x66       0<u>1</u>100110  1 - DRIVEN
OUT      0x00       0<u>0</u>000000  0 - LOW
IN       0x00       0<u>0</u>000000  0 - LOW
INT_STA  0x00       0<u>0</u>000000  0 - IN_ACTIVE
INT_ENB  0x00       0<u>0</u>000000  0 - DISABLE
INT_LVL  0x000000                      - NO_DELTA LEVEL LOW

Given the status of this I guess it is no wonder I’m not getting any interrupts on the PIN as the interrupts are not enabled :-(

So, what is the correct way to go about this? Is it in the device tree or in code? Looking at gpio-tegra.c there doesn’t seem to be an obvious way to enable the interrupt. Only the resume method seems to write the enable register and only writes it from a previous save?

Help!

Robert

1 Like

I’ve had a little look in the device tree for the GPIO and found this

pinmux: pinmux@700008d4 {
                compatible = "nvidia,tegra210-pinmux";
                reg = <0x0 0x700008d4 0x0 0x2a5    /* Pad control registers */
                       0x0 0x70003000 0x0 0x290>; /* Mux registers */
		#gpio-range-cells = <3>;
		status = "disabled";
        };

	gpio: gpio@6000d000 {
                compatible = "nvidia,tegra210-gpio", "nvidia,tegra124-gpio", "nvidia,tegra30-gpio";
                reg = <0x0 0x6000d000 0x0 0x1000>;
                interrupts = <0 32 0x04
                                0 33 0x04
                                0 34 0x04
                                0 35 0x04
                                0 55 0x04
                                0 87 0x04
                                0 89 0x04
                                0 125 0x04>;
                #gpio-cells = <2>;
                gpio-controller;
                #interrupt-cells = <2>;
                interrupt-controller;
		gpio-ranges = <&pinmux 0 0 246>;
		status = "disabled";
        };

The interrupts entry has three byte values per entry. Any idea what these mean?

Robert

There is backwards compatibilty in GPIO architecture going back into older Tegra devices. You will find the following documentation in the kernel source userful even on the Tegra21x:

Documentation/devicetree/bindings/gpio/nvidia,tegra20-gpio.txt

Hi linuxdev

Thanks for this but I looked at it and it’s of no real help me to be honest :-(

Can anybody tell me how I set a particular GPIO (in my case GPIO 174) to be enabled in the Secondary interrupt controller, for the interrupt to be enabled in the GPIO controller and be low to high edge enabled?

I’ve been looking at this for over a day and it shouldn’t be this hard or maybe I shouldn’t be this dumb?

Robert

NVIDIA Tegra GPIO controller

Required properties:
- compatible : "nvidia,tegra<chip>-gpio"
- reg : Physical base address and length of the controller's registers.
- interrupts : The interrupt outputs from the controller. For Tegra20,
  there should be 7 interrupts specified, and for Tegra30, there should
  be 8 interrupts specified.
- #gpio-cells : Should be two. The first cell is the pin number and the
  second cell is used to specify optional parameters:
  - bit 0 specifies polarity (0 for normal, 1 for inverted)
- gpio-controller : Marks the device node as a GPIO controller.
- #interrupt-cells : Should be 2.
  The first cell is the GPIO number.
  The second cell is used to specify flags:
    bits[3:0] trigger type and level flags:
      1 = low-to-high edge triggered.
      2 = high-to-low edge triggered.
      4 = active high level-sensitive.
      8 = active low level-sensitive.
      Valid combinations are 1, 2, 3, 4, 8.
- interrupt-controller : Marks the device node as an interrupt controller.

Example:

gpio: gpio@6000d000 {
	compatible = "nvidia,tegra20-gpio";
	reg = < 0x6000d000 0x1000 >;
	interrupts = < 0 32 0x04
		       0 33 0x04
		       0 34 0x04
		       0 35 0x04
		       0 55 0x04
		       0 87 0x04
		       0 89 0x04 >;
	#gpio-cells = <2>;
	gpio-controller;
	#interrupt-cells = <2>;
	interrupt-controller;
};

174 implies bank V, port 6 (TEGRA_GPIO_PV6). In “/sys” you can manually test changes via echoes or cat related to these files:

echo 174 > /sys/class/gpio/export
# see all gpio settings: 
cat /sys/kernel/debug/gpio
# Check your particular gpio:
grep 174 /sys/kernel/debug/gpio
# disable 174: echo 174 > /sys/class/gpio/unexport

Note in particular that “grep 174 /sys/kernel/debug/gpio” gives two columns at the right side of a table row for whether the gpio is set to input or output, and what level it is set for. Once gpio 174 exists this file will also show up (6000d000.gpio validates the particular controller base address to use in the dtb):

/sys/devices/platform/<b>6000d000.gpio</b>/gpio/gpio174

…within that directory will be these files to see or set properties:

cat active_low
cat direction
cat edge
cat value

Does your setup indicate “rising” for “edge”?

As a test, change the actual value at the pin (make it is set as input first)…does “cat value” follow voltages actually used at the pin? This should tell you if you have the right pin.

Hi linuxdev

Thanks for the answer but I’m inside a kernel driver, I’m not exporting this in user space.

I can use gpio interrupts in userland without issue, but I need to get the interrupts in order to process the next block of PCIe data inside the kernel driver.

I’ve made some progress on this but I don’t really understand why it works one way but not another.

if (0 > request_irq(data_ready_irq_number,
                        &Data_Ready_IRQHandler,
                        IRQF_SHARED | IRQF_TRIGGER_RISING,
                        gDrvrName, gDev))

Does not work, but the following does

if (0 > request_threaded_irq(data_ready_irq_number,
                                   NULL,
                                   &Data_Ready_IRQHandler,
                                   IRQF_SHARED | IRQF_TRIGGER_RISING | IRQF_ONESHOT,
                                   gDrvrName, gDev))

Not sure what the difference is but I lifted this from the sdhci-tegra.c driver

Robert

You’ll probably need to define closer what you mean by doesn’t work…is request_irq returning non-zero? If so, what is the return value? Perhaps it was EBUSY?

Hi

All the calls succeed in both cases but no entry in /proc/interrupts is created for the interrupt and the interrupt never fires even when the pin is toggled.

Using the threaded version the entries in /proc/interrupts are created and my IRQ handler gets called when the pin is toggled

Robert

You may need to add printk statements to log to dmesg which stages are actually reached…this is tedious, but will show you exactly which part of your code is not executing…or that it is executing and not doing what is expected. Map out progression through the driver this way.

The basic difference between threaded and non-threaded is that threaded uses the kernel “kthread” mechanism to service that portion of the driver…this can migrate to different cores like any other thread and becomes managed by the scheduler (thread priorities can be manipulated or given CPU affinity to a particular core). This would typically be desirable for all portions of the driver which do not mandate disabling interrupts…only CPU0 can be used for direct hardware servicing, it’s wise to migrate other parts of the driver to other cores…this is a “good thing” with threaded (you need hard wires to CPU0 for I/O with the device, but processing data from the I/O already completed can go to any CPU…all hardware competes for CPU0). There is the basis of why you will see far more CPU0 interrupts in “/proc/interrupts” versus other cores.

Note that if you are trying to run driver code when interrupts are disabled by another section of code you won’t get what you want (threaded does not disable interrupts, non-threaded does).

Hi

All of my code is executing, except the interrupt is not fired unless I use the threaded call. If I use the threaded version not only do I get the interrupt but all the corresponding configuration registers described a few posts back get setup correctly.

I understand about the interrupt disabled/enabled stuff but I don’t follow why nothing appears in /proc/interrupts just because I use the non-threaded version.

Anyway, like I mentioned before I’m making progress on it now so thanks for the help. I’ll mark your last answer as the correct one. :-)

Robert

Hi Robert,

I have gone through your discussion for using GPIO as interrupt source. I want to register one irq in my kernel driver, it should be called when the gpio exhibits particular edge.

I want to use GPIO_EXP0_INT ( A23) as Interrupt pin (input mode). As per the TX2 pin mux GPIO calculation, respective GPIO number is 480.

I am able to export the gpio in user space and able to get the status. But i want to use it in kernel driver.

Do i need to modify any dts file for adding entry for my gpio ? Could you please share your information?

It would be helpful to me.

Thanks,
Jana

Hi Jana

There is nothing more to it than the code I have posted in my original driver and to use the request_threaded_irq() call instead of the request_irq() one.

I didn’t change the dtb at all.

Robert

Hi Robert,

In your case gpio(B23 - TOUCH_RST) is normal GPIO, so you have used GPIO subsystem devices. But in my case it is GPIO_EXP0_INT (A23 ) in TX2.

I am not sure, that is also normal GPIO, or interrupt line.

can you please help me whether i can follow your method or not?

Thanks,
Jana

Hi

I have no idea.

Why don’t you try it and see?

You may have to add something into the DTB for your pin but I have no idea what or where

Robert