哈伯,你好!
本文是关于开发Linux内核的GPIO(通用输入/输出)模块的。 与上
一篇文章一样,我们实现了具有中断支持(IRQ:中断请求)的GPIO驱动程序的基本结构。

输入数据类似于上一篇文章:为“连接”到FPGA并运行Linux 3.18.19的新处理器开发的GPIO模块。
为了开发GPIO驱动程序,我们需要执行以下步骤:
- 了解GPIO驱动程序和用户空间接口之间交互的原理;
- 将内核模块添加到程序集中,并在设备树中描述硬件;
- 实施驱动程序的基本框架及其入口和检索点;
- 实现GPIO驱动程序的功能部分;
- 将IRQ支持添加到驱动程序实现中。
GPIO驱动程序示例可在
此处找到。
第一步
首先,让我们熟悉GPIO驱动程序之间通过用户控制台进行交互的原理。
使用一个小的bash脚本,为/ sysfs中的每个GPIO创建控件。 为此,请在命令行中编写以下脚本:
for i in {248..255}; do echo $i > /sys/class/gpio/export; done
接下来,让我们看看/ sysfs提供了哪些功能来配置每个GPIO:
root@zed-slave:/sys/class/gpio# ls -l gpio248/ total 0 -rw-r--r-- 1 root root 4096 Jan 7 20:50 active_low -rw-r--r-- 1 root root 4096 Jan 7 20:50 direction -rw-r--r-- 1 root root 4096 Jan 7 20:50 edge drwxr-xr-x 2 root root 0 Jan 7 20:50 power lrwxrwxrwx 1 root root 0 Jan 7 20:50 subsystem -> ../../../../class/gpio -rw-r--r-- 1 root root 4096 Jan 7 20:10 uevent -rw-r--r-- 1 root root 4096 Jan 7 20:50 value
目前,我们对以下领域感兴趣:
- 方向 -设置线的方向。 可以取值“ in”或“ out”;
- value-允许您在线路上设置高电平或低电平信号(如果方向设置为“ out”),否则(方向设置为“ in”)允许您读取线路的状态;
- edge-允许您配置发生中断的事件。 它可以采用以下值:“无”,“上升”,“下降”或“两者”。
通过sysfs快速了解驱动程序界面后,您可以考虑驱动程序如何处理用户命令。 Linux内核具有gpio_chip结构,该结构描述了gpio控制器的功能。 其中包含以下字段:
- direction_input :将线设置为输入。 调用以下条目:echo“ in”> / sys / class / gpio / gpio248 / direction;
- direction_output :将行设置为退出。 调用以下条目:echo“ out”> / sys / class / gpio / gpio248 / direction;
- get :读取在行上设置的值。 调用以下条目:cat / sys / class / gpio / gpio248 / value;
- set :在行上设置值。 在以下条目上调用:echo 1/0> / sys / class / gpio / gpio248 / value;
为了描述Linux上的IRQ配置,有一个irq_chip结构,其中包含以下字段:
- irq_set_type :设置发生中断的事件类型。 调用以下条目:echo>“ rising” /“ falling” /“ both”> / sys / class / gpio / gpio248 / edge;
- irq_mask :禁止中断。 在以下条目上调用:echo“ none”> / sys / class / gpio / gpio248 / edge;
- irq_unmask :对设置为irq_set_type的事件启用中断。 在执行irq_set_type后立即调用。
第二步
现在,您可以按照标准步骤将驱动程序添加到程序集中并描述硬件。 首先,创建源文件:
cd drivers/gpio/ vim gpio-skel.c :wq
将驱动程序配置添加到
drivers / gpio / Kconfig后 :
config GPIO_SKEL tristate "SKEL GPIO" help Say yes here to support SKEL GPIO.
将驱动程序添加到
drivers / gpio / Makefile中的程序集中:
obj-$(CONFIG_GPIO_SKEL) += gpio-skel.o
最后,将GPIO块的描述添加到devicetree(* .dts):
gpio: gpio@f8f01d00 { compatible = "skel-gpio"; rcm,ngpio = <8>; rcm,interrupt-type = <IRQ_TYPE_EDGE_RISING>; clocks = <&clkc 42>; gpio-controller ; interrupt-parent = <&ps7_scugic_0>; interrupts = <0 29 4>; reg = <0x43c00000 0x100>; } ;
您可以
在此处阅读有关devicetree的更多信息。
第三步
让我们继续进行最有趣的部分!
我们通过连接必要的头文件并描述没有IRQ支持的完整驱动程序框架来开始开发驱动程序。 接下来,我们将依次用代码填充每个函数,并提供必要的说明。
GPIO驱动程序框架 #include <linux/of.h> #include <linux/irq.h> #include <linux/io.h> #include <linux/irqdomain.h> #include <linux/bitops.h> #include <linux/irqchip/chained_irq.h> #include <linux/interrupt.h> #include <linux/slab.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/gpio/driver.h> #include <linux/platform_device.h> #define SKEL_GPIO_VER 0x04 #define SKEL_GPIO_PAD_DIR 0x08 #define SKEL_GPIO_WR_DATA 0x0C #define SKEL_GPIO_RD_DATA 0x10 #define SKEL_GPIO_WR_DATA1 0x1C #define SKEL_GPIO_WR_DATA0 0x20 #define SKEL_GPIO_SRC 0x24 #define SKEL_GPIO_MAX_NGPIO 8 #define GPIO_OFFSET 4 struct skel_gpio_chip { struct gpio_chip gchip; spinlock_t lock; void __iomem *regs; u32 type; }; static inline void gpio_write(uint32_t value, void *base, uint32_t addr) { writel(value, base + addr); #if defined DEBUG dev_dbg(rdev->dev, "iowrite32(0x%x, base + 0x%x);\n", value, addr); #endif } static inline uint32_t gpio_read(void *base, uint32_t addr) { uint32_t reg = readl(base + addr); #if defined DEBUG dev_dbg(rdev->dev, "/* ioread32(base + 0x%x) == 0x%x */\n", addr, reg); #endif return reg; } static inline struct skel_gpio_chip *to_skel_gpio(struct gpio_chip *chip) { } /* * echo "in" > /sys/class/gpio/gpioN/direction */ static int skel_gpio_direction_input(struct gpio_chip *chip, unsigned offset) { } /* * echo "out" > /sys/class/gpio/gpioN/direction */ static int skel_gpio_direction_output(struct gpio_chip *chip, unsigned offset, int value) { } static int skel_gpio_get(struct gpio_chip *chip, unsigned offset) { } static void skel_gpio_set(struct gpio_chip *chip, unsigned offset, int value) { } static int skel_gpio_probe(struct platform_device *pdev) { } static int skel_gpio_remove(struct platform_device *pdev) { } static const struct of_device_id skel_gpio_of_match[] = { { .compatible = "skel-gpio" }, { }, }; MODULE_DEVICE_TABLE(of, skel_gpio_of_match); static struct platform_driver skel_gpio_driver = { .probe = skel_gpio_probe, .remove = skel_gpio_remove, .driver = { .name = "skel-gpio", .of_match_table = of_match_ptr(skel_gpio_of_match), }, }; module_platform_driver(skel_gpio_driver); MODULE_DESCRIPTION("GPIO driver"); MODULE_AUTHOR("Name Surname"); MODULE_LICENSE("GPL");
从实现中可以看到,驱动程序的框架看起来非常简单,并且不包含许多必要的功能和结构。
为了描述将来的驱动程序,我们需要以下元素:
- platform_driver skel_gpio_driver-描述加载驱动程序时的入口点skel_gpio_probe和从内核中提取时的入口点skel_gpio_remove;
- struct of_device_id skel_gpio_of_match-包含描述GPIO块硬件的表;
- struct skel_gpio_chip-包含用于控制GPIO块驱动程序的必要字段。
接下来,要将驱动程序加载到Linux或从Linux提取驱动程序,您需要实现
skel_gpio_driver结构中指定的
.probe和
.remove方法。
Skel_gpio_probe实现 static int skel_gpio_probe(struct platform_device *pdev) { struct device_node *node = pdev->dev.of_node; struct skel_gpio_chip *skel_gc; struct resource *res; int ngpio, ret; skel_gc = devm_kzalloc(&pdev->dev, sizeof(*skel_gc), GFP_KERNEL); if (!skel_gc) return -ENOMEM; spin_lock_init(&skel_gc->lock); res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { dev_err(&pdev->dev, "Failed to get MMIO resource for GPIO.\n"); return -EINVAL; } skel_gc->regs = devm_ioremap_resource(&pdev->dev, res); if (!skel_gc->regs) goto free; if (!of_property_read_u32(node, "skel,ngpio", &ngpio)) skel_gc->gchip.ngpio = ngpio; else skel_gc->gchip.ngpio = SKEL_GPIO_MAX_NGPIO; if (skel_gc->gchip.ngpio > SKEL_GPIO_MAX_NGPIO) { dev_warn(&pdev->dev, "Number of gpio is greater than MAX!\n"); skel_gc->gchip.ngpio = SKEL_GPIO_MAX_NGPIO; } skel_gc->gchip.direction_input = skel_gpio_direction_input; skel_gc->gchip.direction_output = skel_gpio_direction_output; skel_gc->gchip.get = skel_gpio_get; skel_gc->gchip.set = skel_gpio_set; skel_gc->gchip.owner = THIS_MODULE; skel_gc->gchip.base = -1; platform_set_drvdata(pdev, skel_gc); ret = gpiochip_add(&skel_gc->gchip); if (ret) { dev_err(&pdev->dev, "Failed adding memory mapped gpiochip\n"); return ret; } dev_info(&pdev->dev, "SKEL GPIO probe complete: (%d .. %d)\n", skel_gc->gchip.base, skel_gc->gchip.base + skel_gc->gchip.ngpio); return 0; }
Skel_gpio_remove实现 static int skel_gpio_remove(struct platform_device *pdev) { struct skel_gpio_chip *skel_gc = platform_get_drvdata(pdev); gpiochip_remove(&skel_gc->gchip); return 0; }
skel_gpio_remove函数仅从内核中删除已注册的GPIO驱动程序,因此请考虑skel_gpio_probe中的要点:
- devm_kzalloc-为skel_gpio_chip结构分配内存;
- platform_get_resource-从devicetree读取GPIO寄存器卡的开始地址;
- devm_ioremap_resource-执行物理地址到虚拟地址的映射;
- of_property_read_u32-从设备树中读取可用GPIO的数量;
- skel_gc-> gchip。* -填写所需的结构字段;
- gpiochip_add-向驱动程序添加GPIO控制器;
到目前为止,还没有描述为什么使用诸如248 ... 255之类的魔术数,条目skel_gc-> gchip.base = -1; 要求内核动态分配GPIO使用的数字。 为了在驱动程序的末尾找到这些数字,添加了输出:
dev_info(&pdev->dev, "SKEL GPIO probe complete: (%d .. %d)\n", skel_gc->gchip.base, skel_gc->gchip.base + skel_gc->gchip.ngpio);
当然,Linux提供了手动设置数字的功能,但是让我们看一下源
代码中的注释:
@base: identifies the first GPIO number handled by this chip; * or, if negative during registration, requests dynamic ID allocation. * DEPRECATION: providing anything non-negative and nailing the base * offset of GPIO chips is deprecated. Please pass -1 as base to * let gpiolib select the chip base in all possible cases. We want to * get rid of the static GPIO number space in the long run.
第四步
考虑驱动程序的功能部分,即,我们实现以下方法:
.direction_output ,
.direction_input ,
.get和
.set 。 接下来,将显示与硬件相关的代码,在大多数情况下,它们将有所不同。
Skel_gpio_direction_input实现 static int skel_gpio_direction_input(struct gpio_chip *chip, unsigned offset) { struct skel_gpio_chip *gc = to_skel_gpio(chip); unsigned long flag; u32 data_dir; spin_lock_irqsave(&gc->lock, flag); data_dir = gpio_read(gc->regs, SKEL_GPIO_PAD_DIR); data_dir &= ~BIT(offset); gpio_write(data_dir, gc->regs, SKEL_GPIO_PAD_DIR); spin_unlock_irqrestore(&gc->lock, flag); return 0; }
skel_gpio_direction_input方法执行以下操作:
- 用以下命令调用echo“ in”> / sys / class / gpio / gpioN / direction;
- 读取寄存器SKEL_GPIO_PAD_DIR,该寄存器负责设置GPIO引脚的方向;
- 设置所需的遮罩;
- 将接收到的值写回SKEL_GPIO_PAD_DIR。
Skel_gpio_direction_output实现 static int skel_gpio_direction_output(struct gpio_chip *chip, unsigned offset, int value) { struct skel_gpio_chip *gc = to_skel_gpio(chip); unsigned long flag; u32 data_reg, data_dir; spin_lock_irqsave(&gc->lock, flag); data_reg = gpio_read(gc->regs, SKEL_GPIO_WR_DATA); if (value) data_reg |= BIT(offset); else data_reg &= ~BIT(offset); gpio_write(data_reg, gc->regs, SKEL_GPIO_WR_DATA); data_dir = gpio_read(gc->regs, SKEL_GPIO_PAD_DIR); data_dir |= BIT(offset); gpio_write(data_dir, gc->regs, SKEL_GPIO_PAD_DIR); spin_unlock_irqrestore(&gc->lock, flag); return 0; }
skel_gpio_direction_output方法执行与skel_gpio_direction_inut类似的操作,不同之处在于它是通过以下命令调用的:
- echo“ out”> / sys / class / gpio / gpioN /方向;
- echo“ high”> / sys / class / gpio / gpioN /方向,将值设置为1;
- echo“ low”> / sys / class / gpio / gpioN /方向,将该值设置为0。
value-一个确定输出线上信号电平的值。
Skel_gpio_set实现 static void skel_gpio_set(struct gpio_chip *chip, unsigned offset, int value) { struct skel_gpio_chip *gc = to_skel_gpio(chip); unsigned long flag; unsigned int data_reg; spin_lock_irqsave(&gc->lock, flag); data_reg = gpio_read(gc->regs, SKEL_GPIO_WR_DATA); if (value) data_reg |= BIT(offset); else data_reg &= ~BIT(offset); gpio_write(data_reg, gc->regs, SKEL_GPIO_WR_DATA); spin_unlock_irqrestore(&gc->lock, flag); }
skel_gpio_set方法执行以下操作:
- 用以下命令调用echo 1/0> / sys / class / gpio / gpioN / value;
- 读取寄存器SKEL_GPIO_WR_DATA,该寄存器显示该行上当前信号的值;
- 按偏移量设置或重置所需的位;
- 将接收到的值写回SKEL_GPIO_WR_DATA。
Skel_gpio_get实现 static int skel_gpio_get(struct gpio_chip *chip, unsigned offset) { struct skel_gpio_chip *gc = to_skel_gpio(chip); return !!(gpio_read(gc->regs, SKEL_GPIO_RD_DATA) & BIT(offset)); }
skel_gpio_get方法通过读取寄存器SKEL_GPIO_RD_DATA来读取线上的信号值。
在描述了所有必要的方法和结构之后,您可以将所有内容放在一起,并看一看最终版本。
GPIO驱动程序实现 #include <linux/of.h> #include <linux/irq.h> #include <linux/io.h> #include <linux/irqdomain.h> #include <linux/bitops.h> #include <linux/irqchip/chained_irq.h> #include <linux/interrupt.h> #include <linux/slab.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/gpio/driver.h> #include <linux/platform_device.h> #define SKEL_GPIO_VER 0x04 #define SKEL_GPIO_PAD_DIR 0x08 #define SKEL_GPIO_WR_DATA 0x0C #define SKEL_GPIO_RD_DATA 0x10 #define SKEL_GPIO_WR_DATA1 0x1C #define SKEL_GPIO_WR_DATA0 0x20 #define SKEL_GPIO_SRC 0x24 #define SKEL_GPIO_MAX_NGPIO 8 #define GPIO_OFFSET 4 struct skel_gpio_chip { struct gpio_chip gchip; spinlock_t lock; void __iomem *regs; u32 type; }; static inline void gpio_write(uint32_t value, void *base, uint32_t addr) { writel(value, base + addr); #if defined DEBUG dev_dbg(rdev->dev, "iowrite32(0x%x, base + 0x%x);\n", value, addr); #endif } static inline uint32_t gpio_read(void *base, uint32_t addr) { uint32_t reg = readl(base + addr); #if defined DEBUG dev_dbg(rdev->dev, "/* ioread32(base + 0x%x) == 0x%x */\n", addr, reg); #endif return reg; } static inline struct skel_gpio_chip *to_skel_gpio(struct gpio_chip *chip) { return container_of(chip, struct skel_gpio_chip, gchip); } /* * echo > "out" > /sys/class/gpio/gpioN/direction */ static int skel_gpio_direction_output(struct gpio_chip *chip, unsigned offset, int value) { struct skel_gpio_chip *gc = to_skel_gpio(chip); unsigned long flag; u32 data_reg, data_dir; spin_lock_irqsave(&gc->lock, flag); data_reg = gpio_read(gc->regs, SKEL_GPIO_WR_DATA); if (value) data_reg |= BIT(offset); else data_reg &= ~BIT(offset); gpio_write(data_reg, gc->regs, SKEL_GPIO_WR_DATA); data_dir = gpio_read(gc->regs, SKEL_GPIO_PAD_DIR); data_dir |= BIT(offset); gpio_write(data_dir, gc->regs, SKEL_GPIO_PAD_DIR); spin_unlock_irqrestore(&gc->lock, flag); return 0; } /* * echo > "in" > /sys/class/gpio/gpioN/direction */ static int skel_gpio_direction_input(struct gpio_chip *chip, unsigned offset) { struct skel_gpio_chip *gc = to_skel_gpio(chip); unsigned long flag; u32 data_dir; spin_lock_irqsave(&gc->lock, flag); data_dir = gpio_read(gc->regs, SKEL_GPIO_PAD_DIR); data_dir &= ~BIT(offset); gpio_write(data_dir, gc->regs, SKEL_GPIO_PAD_DIR); spin_unlock_irqrestore(&gc->lock, flag); return 0; } static int skel_gpio_get(struct gpio_chip *chip, unsigned offset) { struct skel_gpio_chip *gc = to_skel_gpio(chip); return !!(gpio_read(gc->regs, SKEL_GPIO_RD_DATA) & BIT(offset)); } static void skel_gpio_set(struct gpio_chip *chip, unsigned offset, int value) { struct skel_gpio_chip *gc = to_skel_gpio(chip); unsigned long flag; unsigned int data_reg; spin_lock_irqsave(&gc->lock, flag); data_reg = gpio_read(gc->regs, SKEL_GPIO_WR_DATA); if (value) data_reg |= BIT(offset); else data_reg &= ~BIT(offset); gpio_write(data_reg, gc->regs, SKEL_GPIO_WR_DATA); spin_unlock_irqrestore(&gc->lock, flag); } static int skel_gpio_remove(struct platform_device *pdev) { struct skel_gpio_chip *skel_gc = platform_get_drvdata(pdev); gpiochip_remove(&skel_gc->gchip); return 0; } static int skel_gpio_probe(struct platform_device *pdev) { struct device_node *node = pdev->dev.of_node; struct skel_gpio_chip *skel_gc; struct resource *res; int ngpio, ret; skel_gc = devm_kzalloc(&pdev->dev, sizeof(*skel_gc), GFP_KERNEL); if (!skel_gc) return -ENOMEM; spin_lock_init(&skel_gc->lock); res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { dev_err(&pdev->dev, "Failed to get MMIO resource for GPIO.\n"); return -EINVAL; } skel_gc->regs = devm_ioremap_resource(&pdev->dev, res); if (!skel_gc->regs) return -ENXIO; if (!of_property_read_u32(node, "skel,ngpio", &ngpio)) skel_gc->gchip.ngpio = ngpio; else skel_gc->gchip.ngpio = SKEL_GPIO_MAX_NGPIO; if (skel_gc->gchip.ngpio > SKEL_GPIO_MAX_NGPIO) { dev_warn(&pdev->dev, "Number of gpio is greater than MAX!\n"); skel_gc->gchip.ngpio = SKEL_GPIO_MAX_NGPIO; } skel_gc->gchip.direction_input = skel_gpio_direction_input; skel_gc->gchip.direction_output = skel_gpio_direction_output; skel_gc->gchip.get = skel_gpio_get; skel_gc->gchip.set = skel_gpio_set; skel_gc->gchip.owner = THIS_MODULE; skel_gc->gchip.base = -1; platform_set_drvdata(pdev, skel_gc); ret = gpiochip_add(&skel_gc->gchip); if (ret) { dev_err(&pdev->dev, "Failed adding memory mapped gpiochip\n"); return ret; } dev_info(&pdev->dev, "SKEL GPIO probe complete: (%d .. %d)\n", skel_gc->gchip.base, skel_gc->gchip.base + skel_gc->gchip.ngpio); return 0; } static const struct of_device_id skel_gpio_of_match[] = { { .compatible = "skel-gpio" }, { }, }; MODULE_DEVICE_TABLE(of, skel_gpio_of_match); static struct platform_driver skel_gpio_driver = { .probe = skel_gpio_probe, .remove = skel_gpio_remove, .driver = { .name = "skel-gpio", .of_match_table = of_match_ptr(skel_gpio_of_match), }, }; module_platform_driver(skel_gpio_driver); MODULE_DESCRIPTION("GPIO driver"); MODULE_AUTHOR("Name Surname"); MODULE_LICENSE("GPL");
已实现的驱动程序包含用于控制GPIO的必要功能,但是此驱动程序目前不支持中断处理,因此可以继续执行下一步。
第五步
将IRQ添加到GPIO驱动程序可以分为三个步骤:
- 内核数据结构中支持的方法的描述;
- 在驱动程序加载到系统时启用IRQ支持;
- 支持的方法的实现。
首先,我们描述必要的操作集:
static struct irq_chip skel_irq_chip = { .name = "skel-gpio", .irq_mask = skel_gpio_irq_mask, .irq_unmask = skel_gpio_irq_unmask, .irq_set_type = skel_gpio_irq_set_type, };
因此,驱动程序可以启用(skel_gpio_irq_unmask)/禁用(skel_gpio_irq_mask)中断,并指示生成事件的类型(skel_gpio_irq_set_type)。
接下来,我们描述唯一负责将虚拟irq号码映射到硬件的方法。
static struct irq_domain_ops skel_gpio_irq_domain_ops = { .map = skel_gpio_irq_domain_map, };
然后,我们将告诉内核加载的驱动程序支持使用IRQ。 为此,将以下代码添加到探测功能:
添加IRQ支持 skel_gc->gchip.to_irq = skel_gpio_to_irq; skel_gc->domain = irq_domain_add_linear(pdev->dev.of_node, rcm_gc->gchip.ngpio, &skel_gpio_irq_domain_ops, skel_gc); if (!skel_gc->domain) return -ENODEV; skel_gc->irq = platform_get_irq(pdev, 0); if (skel_gc->irq < 0) goto free; for (i = 0; i < skel_gc->gchip.ngpio; i++) { int irq = rcm_gpio_to_irq(&skel_gc->gchip, i); irq_set_chip_and_handler(irq, &skel_irq_chip, handle_simple_irq); #ifdef CONFIG_ARM set_irq_flags(irq, IRQF_VALID); #else irq_set_noprobe(irq); #endif } irq_set_chained_handler(skel_gc->irq, skel_irq_handler); irq_set_handler_data(skel_gc->irq, skel_gc);
在上面的代码中发生了:
- irq_domain下区域的分配和初始化;
- 用devicetree读取中断号;
- 虚拟和硬件中断之间的映射;
- 注册中断处理程序并设置数据以传输到该处理程序;
我们继续实施上述某些方法。
skel_gpio_to_irq-在硬件和虚拟中断之间创建映射。 如果已经创建了此映射,则它返回创建的虚拟中断的编号。
Skel_gpio_to_irq实现 static int skel_gpio_to_irq(struct gpio_chip *chip, unsigned gpio) { struct skel_gpio_chip *rcm = to_skel_gpio(chip); return irq_create_mapping(rcm->domain, gpio); }
skel_irq_handler是一个中断处理程序,它:
- 读取中断状态寄存器;
- 读取中断屏蔽寄存器;
- 引发中断以等待处理;
- 对于每个发生中断的GPIO,调用generic_handle_irq。
中断处理程序的实现 static void skel_irq_handler(unsigned int irq, struct irq_desc *desc) { struct skel_gpio_chip *skel_gc = irq_get_handler_data(irq); struct irq_chip *chip = irq_desc_get_chip(desc); void __iomem *base; u32 status, mask, gpio, pending; chained_irq_enter(chip, desc); base = skel_gc->regs; status = gpio_read(base, SKEL_GPIO_STATUS); mask = gpio_read(base, SKEL_GPIO_IRQ_MASK); pending = status & mask; while (pending) { gpio = __ffs(pending); pending &= ~BIT(gpio); generic_handle_irq( irq_find_mapping(skel_gc->domain, gpio)); } chained_irq_exit(chip, desc); }
就是这样,在本文中,我们学习了GPIO驱动程序如何与sysfs虚拟文件系统交互,实现了GPIO驱动程序的基本结构,还研究了支持IRQ所需的方法。
本文没有描述skel_gpio_irq_unmask,skel_gpio_irq_mask和skel_gpio_irq_set_type方法的实现,这有两个原因。 首先,这些方法易于实现。 其次,取决于硬件。 它们负责允许或禁止GPIO控制器支持的某些事件的中断。
请,如果您发现错误/不正确,或者您有什么要补充的内容,请写在PM或注释中。
感谢您的关注!