1287 字
6 分钟
Linux中断机制
设备树中断信息节点
IMX6ULL和IMX6UL的中断控制器呈兼容关系,即IMX6ULL继承IMX6UL的GIC节点信息。在imx6ul.dtsi中有:
intc: interrupt-controller@a01000 { compatible = "arm,gic-400", "arm,cortex-a7-gic"; /* GIC_PPI:私有外设中断,非共享 9:中断号为9 GIC_CPU_MASK_SIMPLE(1):PPI只发送给CPU0处理 IRQ_TYPE_LEVEL_HIGH:电平触发,高电平有效 */ interrupts = <GIC_PPI 9 (GIC_CPU_MASK_SIMPLE(1) | IRQ_TYPE_LEVEL_HIGH)>; /* 中断描述需3个单元 */ #interrupt-cells = <3>; /* 声明节点为中断控制器 */ interrupt-controller; /* 父类为自身,说明是顶级GIC */ interrupt-parent = <&intc>; /* GIC寄存器基地址和大小 */ reg = <0x00a01000 0x1000>, /* GIC分发器基地址 */ <0x00a02000 0x2000>, /* GIC CPU接口基地址 */ <0x00a04000 0x2000>, /* GIC虚拟CPU接口 */ <0x00a06000 0x2000>; /* GIC虚拟分发器 */ };这里的interrupt-cells通常格式为<中断类型 中断号 标志位>或<中断号 标志位>,其中
- 中断类型:
0:SPI,共享外设中断1:PPI,私有外设中断
- 中断号:设备中断ID,SPI中断号范围为0
987,PPI中断号范围为015 - 标志位:
IRQ_TYPE_NONE:Kernel不参与设置,跟随ubootIRQ_TYPE_EDGE_RISING:上升沿触发IRQ_TYPE_EDGE_FALLING:下降沿触发IRQ_TYPE_EDGE_BOTH:双边沿触发IRQ_TYPE_LEVEL_HIGH:高电平触发IRQ_TYPE_LEVEL_LOW:低电平触发
GPIO也可以作为GIC:
gpio5: gpio@20ac000 { compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio"; reg = <0x020ac000 0x4000>; /* 共享中断,中断号74,高电平触发 共享中断,中断号75,高电平触发 */ interrupts = <GIC_SPI 74 IRQ_TYPE_LEVEL_HIGH>, <GIC_SPI 75 IRQ_TYPE_LEVEL_HIGH>; clocks = <&clks IMX6UL_CLK_GPIO5>; gpio-controller; #gpio-cells = <2>; interrupt-controller; /* 声明为中断控制器 */ gpio-ranges = <&iomuxc 0 7 10>, <&iomuxc 10 5 2>;}中断需要在节点中配置,如imx6ul-14x14-evk.dtsi中i2c1节点下的:
fxls8471@1e { compatible = "fsl,fxls8471"; reg = <0x1e>; position = <0>; interrupt-parent = <&gpio5>; /* GIC为GPIO5 */ interrupts = <0 8>; /* 中断号为0,低电平触发 */ };简单来说,设备树节点中描述中断的信息有:
#interrupt-cells:中断源cell个数interrupt-controller:声明当前节点为中断控制器interrupts:指定中断号和中断触发模式interrupt-parent:父中断(GIC)
用法
- 修改设备树
- 设备结构体中声明
irqreturn_t (*handler)(int, void *)(也可以不声明)和int irqnum(存放中断号) - 通过
irq_of_parse_and_map向设备树申请中断信息
unsigned int irq_of_parse_and_map(struct device_node *node, int index);该函数返回申请到的中断号。 4. 初始化时向kernel申请中断:
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev)irq:中断号handler:中断服务函数flags:标志位,可选:
#define IRQF_TRIGGER_NONE 0x00000000#define IRQF_TRIGGER_RISING 0x00000001#define IRQF_TRIGGER_FALLING 0x00000002#define IRQF_TRIGGER_HIGH 0x00000004#define IRQF_TRIGGER_LOW 0x00000008#define IRQF_TRIGGER_MASK (IRQF_TRIGGER_HIGH | IRQF_TRIGGER_LOW | \ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING)#define IRQF_TRIGGER_PROBE 0x00000010*name:生成中断的设备名*dev:Cookie,传递设备结构体地址
- 设备注销时释放中断:
void *free_irq(unsigned int irq, void *dev);irq:中断号*dev:Cookie,传递设备结构体地址
例程
#include <linux/types.h>#include <linux/kernel.h>#include <linux/delay.h>#include <linux/libata.h> /* 新版kernel不再支持ide.h */#include <linux/init.h>#include <linux/module.h>#include <linux/errno.h>#include <linux/gpio.h>#include <linux/cdev.h>#include <linux/device.h>#include <linux/of.h>#include <linux/of_address.h>#include <linux/of_gpio.h>#include <asm/mach/map.h>#include <asm/uaccess.h>#include <asm/io.h>#include <linux/semaphore.h>#include <linux/timer.h>#include <linux/of_irq.h>#include <linux/irq.h>
#define IMX6UIRQ_CNT 1#define IMX6UIRQ_NAME "imx6uirq"#define KEY0VALUE 0x01#define INVAKEY 0xFF#define KEY_NUM 1
struct irq_keydesc { int gpio; int irqnum; /* 中断号 */ unsigned char value; /* 按键对应的键值 */ char name[10]; irqreturn_t (*handler)(int, void *); /* 中断服务函数 */};
struct irq_led { int gpio;};
struct imx6uirq_dev { dev_t devid; struct cdev cdev; struct class *class; struct device *device; int major; int minor; struct device_node *nd; atomic_t keyvalue; /* 有效按键键值 */ atomic_t releasekey; /* 标记是否完成一次按键 */ struct timer_list timer; struct irq_keydesc irqkeydesc; /* 按键描述结构体 */ struct irq_led irqled;};
struct imx6uirq_dev imx6uirq;
/* IRQ服务函数,延时10ms,消抖 */static irqreturn_t key0_handler(int irq, void *dev_id){ struct imx6uirq_dev *dev = (struct imx6uirq_dev *)dev_id;
mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10)); /* 延时10ms,消抖 */ return IRQ_RETVAL(IRQ_HANDLED);}
/* 定时器服务函数,到期后再次读取按键值,如果还是按下表示按键有效 */void timer_function(struct timer_list *t){ unsigned char value; struct irq_keydesc *keydesc; struct imx6uirq_dev *dev = from_timer(dev, t, timer);
keydesc = &dev->irqkeydesc;
value = gpio_get_value(keydesc->gpio); /* 读取IO电平 */ if (value == 0){ /* 如果按下 */ int sta = gpio_get_value(dev->irqled.gpio); gpio_set_value(dev->irqled.gpio, !sta); atomic_set(&dev->keyvalue, keydesc->value); }else{ /* 如果松开 */ atomic_set(&dev->keyvalue, 0x80 | keydesc->value); atomic_set(&dev->releasekey, 1); /* 标记松开按键 */ }}
static int keyio_init(void){ int ret = 0;
imx6uirq.nd = of_find_node_by_path("/key"); if (imx6uirq.nd == NULL){ printk("find node failed\r\n"); return -EINVAL; }
imx6uirq.irqkeydesc.gpio = of_get_named_gpio(imx6uirq.nd, "key-gpio", 0); if (imx6uirq.irqkeydesc.gpio < 0){ printk("unable get key gpio\r\n"); return -EINVAL; }
imx6uirq.irqled.gpio = of_get_named_gpio(imx6uirq.nd, "led-gpio", 0); if (imx6uirq.irqled.gpio < 0){ printk("unable get led gpio\r\n"); return -EINVAL; }
sprintf(imx6uirq.irqkeydesc.name, "KEY0"); gpio_request(imx6uirq.irqkeydesc.gpio, imx6uirq.irqkeydesc.name); ret = gpio_direction_input(imx6uirq.irqkeydesc.gpio); if (ret < 0){ printk("unable set key pin level\r\n"); return ret; } imx6uirq.irqkeydesc.irqnum = irq_of_parse_and_map(imx6uirq.nd, 0); printk("key0: gpio = %d, irqnum = %d\r\n", imx6uirq.irqkeydesc.gpio, imx6uirq.irqkeydesc.irqnum);
gpio_request(imx6uirq.irqled.gpio, "LED"); ret = gpio_direction_output(imx6uirq.irqled.gpio, 1); if (ret < 0){ printk("unable set led pin level\r\n"); return ret; }
imx6uirq.irqkeydesc.handler = key0_handler; imx6uirq.irqkeydesc.value = KEY0VALUE;
ret = request_irq(imx6uirq.irqkeydesc.irqnum, imx6uirq.irqkeydesc.handler, IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, imx6uirq.irqkeydesc.name, &imx6uirq);
if (ret < 0){ printk("IRQ request failed, error code: %d\r\n", ret); }
timer_setup(&imx6uirq.timer, timer_function, 0);
return 0;}
static int imx6uirq_open(struct inode *inode, struct file *filp){ filp->private_data = &imx6uirq; return 0;}
static ssize_t imx6uirq_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt){ int ret = 0; unsigned char keyvalue = 0; unsigned char releasekey = 0; struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;
keyvalue = atomic_read(&dev->keyvalue); releasekey = atomic_read(&dev->releasekey);
if (releasekey){ if (keyvalue & 0x80){ keyvalue &= ~(0x80); ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue)); }else{ goto data_error; } atomic_set(&dev->releasekey, 0); }else{ goto data_error; } return 0;
data_error: return -EINVAL;}
static struct file_operations imx6uirq_fops = { .owner = THIS_MODULE, .open = imx6uirq_open, .read = imx6uirq_read,};
static int __init imx6uirq_init(void){ if (imx6uirq.major){ imx6uirq.devid = MKDEV(imx6uirq.major, 0); register_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT, IMX6UIRQ_NAME); }else{ alloc_chrdev_region(&imx6uirq.devid, 0, IMX6UIRQ_CNT, IMX6UIRQ_NAME); imx6uirq.major = MAJOR(imx6uirq.devid); imx6uirq.minor = MINOR(imx6uirq.devid); } printk("KEY reg ok, major = %d, minor = %d\r\n", imx6uirq.major, imx6uirq.minor);
cdev_init(&imx6uirq.cdev, &imx6uirq_fops); cdev_add(&imx6uirq.cdev, imx6uirq.devid, IMX6UIRQ_CNT);
imx6uirq.class = class_create(IMX6UIRQ_NAME); if (IS_ERR(imx6uirq.class)){ return PTR_ERR(imx6uirq.class); }
imx6uirq.device = device_create(imx6uirq.class, NULL, imx6uirq.devid, NULL, IMX6UIRQ_NAME); if (IS_ERR(imx6uirq.device)){ return PTR_ERR(imx6uirq.device); }
atomic_set(&imx6uirq.keyvalue, INVAKEY); atomic_set(&imx6uirq.releasekey, 0); keyio_init();
return 0;}
static void __exit imx6uirq_exit(void){ del_timer_sync(&imx6uirq.timer);
free_irq(imx6uirq.irqkeydesc.irqnum, &imx6uirq); gpio_free(imx6uirq.irqkeydesc.gpio);
cdev_del(&imx6uirq.cdev); unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT); device_destroy(imx6uirq.class, imx6uirq.devid); class_destroy(imx6uirq.class);}
module_init(imx6uirq_init);module_exit(imx6uirq_exit);MODULE_LICENSE("GPL");