External interrupts
This is not meant to be comprehensive guide about the external interrupts (EINT0..EINT31), just some notes in getting them to work with the 3.19 stock kernel.
Contents |
Pin support
Not all pins are available for handling of edges and interrupts. For example, on Cubieboard with the 3.4.x kernel there are only 6 pins: ph14, ph15, pi11, pi13, pi10, pi12. And it can't be changed via settings in fex file .
The Setup
I have three interrupt inputs that were used on the linux-sunxi-3.4.102 kernel that needed to be ported onto the recent 3.19 kernel. As you may know, the 3.4.102 uses the FEX files, and direct bit-banging to get things configured. The 3.19 kernel uses the device tree (dts) files to describe the hardware. Yes, you can still bang hardware, but I was having issues with interrupt lockup with the 3.4.102 kernel.
Device Tree Entries
As my system, the AW-SOM A20 DIMM, is similar to the cubieboard2, I started with that file and copied it to a new name (cd arch/arm/boot/dts ; cp sun7i-a20-cubieboard2.dts sun7i-a20-prismlx.dts). My board uses EINT16, EINT18 and EINT20 for external interrupts. So, here are the additions made to the dts file:
[email protected] { ... ... init_pins_cubieboard2: [email protected] { allwinner,pins = "PH20"; allwinner,function = "irq"; allwinner,drive = <0>; allwinner,pull = <1>; }; uart_pins_cubieboard2: [email protected] { allwinner,pins = "PH18"; allwinner,function = "irq"; allwinner,drive = <0>; allwinner,pull = <1>; }; aic_pins_cubieboard2: [email protected] { allwinner,pins = "PH16"; allwinner,function = "irq"; allwinner,drive = <0>; allwinner,pull = <1>; }; }; rebooter { compatible = "prismlx-rebooter"; pinctrl-names = "default"; pinctrl-0 = <&init_pins_cubieboard2>; interrupts-extended = <&pio 20 0>; interrupt-names = "restart"; }; senseb { compatible = "prismlx-senseb"; pinctrl-names = "default"; pinctrl-0 = <&uart_pins_cubieboard2>; interrupts-extended = <&pio 18 0>; interrupt-names = "uarts"; }; sensea { compatible = "prismlx-sensea"; pinctrl-names = "default"; pinctrl-0 = <&aic_pins_cubieboard2>; interrupts-extended = <&pio 16 0>; interrupt-names = "alarm-monitor"; };
The above describes my board configuration for the interrupts. Each interrupt input pin is listed in the pinctrl list and is linked to the platform driver code via the compatible entry. Thus, if your platform driver simply asks for 'platform_get_irq(pdev, 0)', the irq returned would be that which matched the driver .compatible of_device_id with that of the device tree. It appears that you could use platform_get_irq_byname() to get several interrupts into a single platform driver?
Note, the entry for interrupts-extended in the rebooter description has <&pio 20 0>? The 20 is the external interrupt number (EINT20) and the 0 is irrelevant, this value has no affect on the interrupt line sensing. The PIO_INT_CFG register trigger value is set by the IRQF_<flag> when you use request_irq() in your driver (e.g. IRQF_TRIGGER_HIGH sets a value of 0x02 in the PIO_INT_CFGx register for that external pin).
This doesn't seem to be correct anymore since the new 4.x kernel hp197 (talk)
A Simple Platform Driver
This is a simple driver which requests the 'rebooter' IRQ (EINT20).
#include <linux/interrupt.h> #include <linux/of.h> #include <linux/of_platform.h> #include <linux/of_gpio.h> #include <linux/sched.h> #include <linux/fs.h> #include <linux/cdev.h> static int irq = -1; /* respond to the INIT button pressed. */ static irqreturn_t irqHandlerInit (int irq, void *dev_id) { printk (KERN_NOTICE "EINT20 interrupt fired\n"); return IRQ_HANDLED; } static int __init rebooter_probe(struct platform_device *pdev) { irq = platform_get_irq_byname (pdev, "restart"); if (request_irq(irq, &irqHandlerInit, IRQF_TRIGGER_FALLING, "rebooter_irq", NULL)) { printk(KERN_ERR "rebooter-irq: cannot register IRQ %d\n", irq); return -EIO; } return 0; } static int rebooter_remove(struct platform_device *pdev) { free_irq (irq, NULL); return 0; } #if defined(CONFIG_OF) static const struct of_device_id rebooter_dt_ids[] = { { .compatible = "prismlx-rebooter" }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, rebooter_dt_ids); #endif static struct platform_driver rebooter_driver = { .remove = rebooter_remove, .driver = { .name = "prismlx-rebooter", .of_match_table = of_match_ptr(rebooter_dt_ids), }, }; module_platform_driver_probe(rebooter_driver, rebooter_probe); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Tom Walsh"); MODULE_DESCRIPTION("PrismLX Init Switch Monitor");
What you should note is the IRQF_TRIGGER_FALLING of request_irq. It is in the call to request_irq() that the active level / edge of the interrupt request (input pin) is specified.
Doing an ismod of the driver should give you something like this in /proc/interrupts:
[email protected]:~# cat /proc/interrupts CPU0 CPU1 17: 0 0 GIC 29 arch_timer 18: 32144 16029 GIC 30 arch_timer 21: 0 0 GIC 54 sun4i_timer0 22: 0 0 GIC 113 sun5i_timer0 26: 6549 0 GIC 64 sunxi-mmc 27: 0 0 GIC 71 ehci_hcd:usb1 28: 0 0 GIC 96 ohci_hcd:usb3 29: 0 0 GIC 88 1c18000.sata 30: 2 0 GIC 72 ehci_hcd:usb2 31: 110 0 GIC 97 ohci_hcd:usb4 34: 0 0 GIC 56 1c20d00.rtc 40: 102 0 GIC 33 serial 41: 96 0 GIC 39 mv64xxx_i2c 42: 0 0 GIC 40 mv64xxx_i2c 43: 349 0 GIC 117 eth0 68: 0 0 - 20 rebooter_irq 80: 0 0 interrupt-controller 0 axp20x_irq_chip IPI0: 0 0 CPU wakeup interrupts IPI1: 0 0 Timer broadcast interrupts IPI2: 1548 1619 Rescheduling interrupts IPI3: 0 0 Function call interrupts IPI4: 2 6 Single function call interrupts IPI5: 0 0 CPU stop interrupts IPI6: 0 0 IRQ work interrupts IPI7: 0 0 completion interrupts Err: 0
Of course, if you tap on the EINT20 pin with a wire to GROUND, you should see the 'rebooter_irq' count change.
Using the right clock
On the 3.19 kernel, the PIO interrupt sampling clock is set to the default value of 32KHz. This is probably "good enough" if you were to only have some push buttons tied to those external interrupts, but, not good enought if you hang a serial UART on one of those interrupt lines. The sampling rate of 32KHz would have you missing some of the interrupts as data comes in, so, you better have a pretty deep fifo in that UART...
This patch made by Maxime Ripard will allow you to change the PIO Interrupt Debounce clock from 32KHz to 24MHz (tested on kernel 4.1rc1, Hans' branch sunxi-wip):
diff --git a/drivers/pinctrl/sunxi/pinctrl-sunxi.c b/drivers/pinctrl/sunxi/pinctrl-sunxi.c index f09573e13203..a02e0a457b11 100644 --- a/drivers/pinctrl/sunxi/pinctrl-sunxi.c +++ b/drivers/pinctrl/sunxi/pinctrl-sunxi.c @@ -1005,6 +1005,9 @@ int sunxi_pinctrl_init(struct platform_device *pdev, writel(0xffffffff, pctl->membase + sunxi_irq_status_reg_from_bank(i)); + /* Set Interrupt clock to 24MHz */ + writel(1, pctl->membase + sunxi_irq_debounce_reg_from_bank(i)); + irq_set_chained_handler_and_data(pctl->irq[i], sunxi_pinctrl_irq_handler, pctl); diff --git a/drivers/pinctrl/sunxi/pinctrl-sunxi.h b/drivers/pinctrl/sunxi/pinctrl-sunxi.h index e248e81a0f9e..502a5c678380 100644 --- a/drivers/pinctrl/sunxi/pinctrl-sunxi.h +++ b/drivers/pinctrl/sunxi/pinctrl-sunxi.h @@ -68,6 +68,7 @@ #define IRQ_STATUS_IRQ_PER_REG 32 #define IRQ_STATUS_IRQ_BITS 1 #define IRQ_STATUS_IRQ_MASK ((1 << IRQ_STATUS_IRQ_BITS) - 1) +#define IRQ_DEBOUNCE_REG 0x218 #define IRQ_MEM_SIZE 0x20 @@ -283,6 +284,11 @@ static inline u32 sunxi_irq_status_offset(u16 irq) return irq_num * IRQ_STATUS_IRQ_BITS; } +static inline u32 sunxi_irq_debounce_reg_from_bank(u8 bank) +{ + return IRQ_DEBOUNCE_REG + bank * IRQ_MEM_SIZE; +} + int sunxi_pinctrl_init(struct platform_device *pdev, const struct sunxi_pinctrl_desc *desc);