Linux GPIO模块-RK3588 GPIO驱动分析

1.简介

GPIO是可编程的通用I/O外设。如下图所示,RK3588 GPIO控制器包含3个部分;APB接口模块和SoC内部的APB总线连接,负责与SoC交换数据,位宽为32位;I/O port接口模块管理外部的引脚,引脚的输入和输出都要经过该模块;中断探测模块负责GPIO控制器的中断上报与处理。

Linux GPIO模块-RK3588 GPIO驱动分析_第1张图片
RK3588 GPIO控制器的特性如下:

  1. 32bits APB总线位宽
  2. 每个中断控制器32个GPIO引脚
  3. 每个GPIO引脚软件可控制
  4. 中断引脚可配置防抖
  5. 中断模式可配置
  6. 独立的控制寄存器支持两个虚拟的OS
  7. 在两个虚拟的OS模式中,每个OS都有独立的中断
  8. 非虚拟的OS模式中,支持设置两种中断极性

2.GPIO驱动框架

Linux内核GPIO驱动框架如下图所示。最上层是GPIO使用者驱动,如LED Driver、KEY Driver等,GPIO硬件由使用者控制。接下来是GPIO的抽象层-GPIO库,实现位于内核drivers\gpio\gpiolib.c文件中,GPIO库向上给GPIO使用者提供访问GPIO硬件的接口,向下将所有不同的GPIO Controller统一管理起来。GPIO控制器驱动负责驱动具体的GPIO控制器。最下层是GPIO控制器硬件。

Linux GPIO模块-RK3588 GPIO驱动分析_第2张图片

2.1.数据结构

数据结构的关系图如下所示。一个GPIO控制器对应一个gpio_device、rockchip_pin_bank、irq_chip_generic,一个GPIO引脚对应于一个gpio_desc。所有的gpio_device放到gpio_devices链表中,由内核统一管理。

Linux GPIO模块-RK3588 GPIO驱动分析_第3张图片

2.2.GPIO控制器驱动接口

GPIO控制器驱动需要提供一个gpio_chip结构体,并将其初始化。gpio_chip结构体主要的成员如下。内核使用gpio_irq_chip实现gpio控制器的中断功能。

[include/linux/gpio/driver.h]
struct gpio_chip {
	const char		*label;
	// 每个GPIO控制器都有一个gpio_device
	struct gpio_device	*gpiodev;
	// 请求GPIO,gpio_request调用该函数
	int	(*request)(struct gpio_chip *gc, unsigned int offset);
	// 释放GPIO,gpio_free调用该函数
	void (*free)(struct gpio_chip *gc, unsigned int offset);
	// 获取GPIO方向,gpiod_get_direction调用该函数
	int	(*get_direction)(struct gpio_chip *gc, unsigned int offset);
	// 设置GPIO为输入,gpio_direction_input或gpiod_direction_input调用该函数
	int	(*direction_input)(struct gpio_chip *gc, unsigned int offset);
	// 设置GPIO为输出,gpio_direction_output或gpiod_direction_output调用该函数
	int	(*direction_output)(struct gpio_chip *gc,
			unsigned int offset, int value);
	// 获取GPIO值,gpio_get_value或gpiod_get_value调用该函数
	int	(*get)(struct gpio_chip *gc, unsigned int offset);
	// 获取多个GPIO值
	int	(*get_multiple)(struct gpio_chip *gc,
			unsigned long *mask, unsigned long *bits);
	// 设置GPIO值,gpio_set_value或gpiod_set_value调用该函数
	void (*set)(struct gpio_chip *gc, unsigned int offset, int value);
	// 设置多个GPIO值
	void (*set_multiple)(struct gpio_chip *gc, unsigned long *mask, unsigned long *bits);
	// 设置GPIO防抖功能,gpio_set_debounce或gpiod_set_debounce调用该函数
	int	(*set_config)(struct gpio_chip *gc, unsigned int offset,
				unsigned long config);
	// 获取gpio引脚虚拟中断号,gpio_to_irq或gpiod_to_irq调用该函数
	int	(*to_irq)(struct gpio_chip *gc, unsigned int offset);
	......
	// gpio range功能
	int	(*add_pin_ranges)(struct gpio_chip *gc);
	int	base;  // 该gpio控制器的基础编号
	u16	ngpio; // gpio引脚数量
#if IS_ENABLED(CONFIG_GPIO_GENERIC)
	// 读gpio寄存器
	unsigned long (*read_reg)(void __iomem *reg);
	// 写gpio寄存器
	void (*write_reg)(void __iomem *reg, unsigned long data);;
	void __iomem *reg_dat;  // data (in) register for generic GPIO
	void __iomem *reg_set;  // output set register (out=high) for generic GPIO
	void __iomem *reg_clr;  // output clear register (out=low) for generic GPIO
	// direction out setting register for generic GPIO
	void __iomem *reg_dir_out;
	// direction in setting register for generic GPIO
	void __iomem *reg_dir_in;
#endif /* CONFIG_GPIO_GENERIC */

#ifdef CONFIG_GPIOLIB_IRQCHIP
	/**
	 * Integrates interrupt chip functionality with the GPIO chip. Can be
	 * used to handle IRQs for most practical cases.
	 */
	struct gpio_irq_chip irq;
#endif /* CONFIG_GPIOLIB_IRQCHIP */
	......
};
// gpio中断功能
struct gpio_irq_chip {
	/* GPIO IRQ chip implementation, provided by GPIO driver. */
	struct irq_chip *chip;
	/**
	 * Interrupt translation domain; responsible for mapping between GPIO
	 * hwirq number and Linux IRQ number.
	 */
	struct irq_domain *domain;  // 用于gpio中断号映射
	/* Table of interrupt domain operations for this IRQ chip. */
	const struct irq_domain_ops *domain_ops;
	/**
	 * The IRQ handler to use (often a predefined IRQ core function) for
	 * GPIO IRQs, provided by GPIO driver.
	 */
	// gpio控制器中断处理函数,该函数会调用使用者注册的具体pin的中断处理函数
	irq_flow_handler_t handler;
	/**
	 * Default IRQ triggering type applied during GPIO driver
	 * initialization, provided by GPIO driver.
	 */
	unsigned int default_type;
	/**
	 * The interrupt handler for the GPIO chip's parent interrupts, may be
	 * NULL if the parent interrupts are nested rather than cascaded.
	 */
	irq_flow_handler_t parent_handler;
	/**
	 * @init_hw: optional routine to initialize hardware before
	 * an IRQ chip will be added. This is quite useful when
	 * a particular driver wants to clear IRQ related registers
	 * in order to avoid undesired events.
	 */
	int (*init_hw)(struct gpio_chip *gc);
	/**
	 * Required for static IRQ allocation. If set, irq_domain_add_simple()
	 * will allocate and map all IRQs during initialization.
	 */
	unsigned int first;
	/* Store old irq_chip irq_enable callback */
	void		(*irq_enable)(struct irq_data *data);
	/* Store old irq_chip irq_disable callback */
	void		(*irq_disable)(struct irq_data *data);
	/* Store old irq_chip irq_unmask callback */
	void		(*irq_unmask)(struct irq_data *data);
	/* Store old irq_chip irq_mask callback */
	void		(*irq_mask)(struct irq_data *data);
};

gpio_chip结构体初始化完成之后,调用gpiochip_add_data或devm_gpiochip_add_data函数进行注册,注册成功之后,GPIO使用者就可以通过GPIO驱动访问GPIO硬件。调用gpiochip_add_data注册的GPIO控制器需要调用gpiochip_remove释放,使用devm_gpiochip_add_data无需手动释放。

[include/linux/gpio/driver.h]
gpiochip_add_data(gc, data);
devm_gpiochip_add_data(dev, gc, data);
void gpiochip_remove(struct gpio_chip *gc);

2.3.GPIO消费者驱动接口

内核中有两套接口可供GPIO使用者调用。第一套是内核推荐的接口,其基于描述符gpio_desc,以gpiod开头。第二套是传统的接口,基于gpio number,以gpio开头。两种接口的对比如下表所示。

Linux GPIO模块-RK3588 GPIO驱动分析_第4张图片

基于描述符的GPIO接口和基于整数的GPIO接口主要的区别在于获取GPIO。基于描述符获取GPIO接口如下所示。

[include/linux/gpio/consumer.h]
#define GPIOD_FLAGS_BIT_DIR_SET           BIT(0)  // 设置方向,默认输入
#define GPIOD_FLAGS_BIT_DIR_OUT           BIT(1)  // 输出方向
#define GPIOD_FLAGS_BIT_DIR_VAL           BIT(2)  // 设置值,默认低电平
#define GPIOD_FLAGS_BIT_OPEN_DRAIN        BIT(3)  // 开漏
#define GPIOD_FLAGS_BIT_NONEXCLUSIVE      BIT(4)
/**
 * Optional flags that can be passed to one of gpiod_* to configure direction
 * and output value. These values cannot be OR'd.
 */
enum gpiod_flags {
    GPIOD_ASIS = 0,
    GPIOD_IN = GPIOD_FLAGS_BIT_DIR_SET,  // 输入
    // 输出-低电平
    GPIOD_OUT_LOW = GPIOD_FLAGS_BIT_DIR_SET | GPIOD_FLAGS_BIT_DIR_OUT,
    // 输出-高电平
    GPIOD_OUT_HIGH = GPIOD_FLAGS_BIT_DIR_SET | GPIOD_FLAGS_BIT_DIR_OUT | 
                     GPIOD_FLAGS_BIT_DIR_VAL,
    // 输出-低电平-开漏
    GPIOD_OUT_LOW_OPEN_DRAIN = GPIOD_OUT_LOW | GPIOD_FLAGS_BIT_OPEN_DRAIN,
    // 输出-高电平-开漏
    GPIOD_OUT_HIGH_OPEN_DRAIN = GPIOD_OUT_HIGH | GPIOD_FLAGS_BIT_OPEN_DRAIN,
};
/**
 * gpiod_get - 获取gpio描述符
 * @dev: GPIO consumer设备结构体指针
 * @con_id: GPIO consumer引用gpio的属性前缀,如uart_rts_gpios,前缀为uart_rts
 * @flags: 可选的GPIO初始化标志
 */
struct gpio_desc *gpiod_get(struct device *dev, const char *con_id, enum gpiod_flags flags);
/**
 * gpiod_get - 获取指定的gpio描述符
 * @dev: GPIO consumer设备结构体指针
 * @con_id: GPIO consumer引用gpio的属性前缀,如uart_rts_gpios,前缀为uart_rts
 * @idx: GPIO consumer引用gpio的属性索引,uart_rts_gpios只有一个属性
 * @flags: 可选的GPIO初始化标志
 */
struct gpio_desc *gpiod_get_index(struct device *dev, const char *con_id, unsigned int idx, enum gpiod_flags flags);
// 获取指定的gpio所有描述符,得到的是一个gpio数组
static inline struct gpio_descs *gpiod_get_array(struct device *dev, const char *con_id, enum gpiod_flags flags)

3.GPIO消费者驱动示例

GPIO使用者驱动wireless_bluetooth的设备树如下所示,其引用了GPIO3控制器的的编号为4的引脚(GPIO引脚编号定义在include/dt-bindings/pinctrl/rockchip.h头文件中),低电平有效,即gpiod_set_value/gpio_set_value设置0时有效。wireless_bluetooth引用了uart8_gpios的pinctrl设备节点,uart8_gpios节点将gpio3控制器的编号为4的引脚复用为GPIO功能。GPIO使用者驱动中GPIO属性名称的命名规则为"[-]gpios",name表明该GPIO的用途,新的内核推荐使用该命名规则。当然也可以使用之前的规则,即使用gpios和[-]gpio"作为属性名称。

[arch/arm64/boot/dts/rockchip/rk3588s-evb4-lp4x.dtsi]
wireless_bluetooth: wireless-bluetooth {
	compatible = "bluetooth-platdata";
	clocks = <&hym8563>;
	clock-names = "ext_clock";
	// gpio3控制器,4号引脚,低电平有效
	uart_rts_gpios = <&gpio3 RK_PA4 GPIO_ACTIVE_LOW>;
	pinctrl-names = "default", "rts_gpio";
	pinctrl-0 = <&uart8m1_rtsn>, <&bt_reset_gpio>, <&bt_wake_gpio>, <&bt_wake_host_irq>;
	pinctrl-1 = <&uart8_gpios>;  // 引用uart_rts_gpios引脚的pinctrl结点
	BT,reset_gpio    = <&gpio3 RK_PB7 GPIO_ACTIVE_HIGH>;
	BT,wake_gpio     = <&gpio3 RK_PC1 GPIO_ACTIVE_HIGH>;
	BT,wake_host_irq = <&gpio3 RK_PC0 GPIO_ACTIVE_HIGH>;
	status = "okay";
};

[arch/arm64/boot/dts/rockchip/rk3588s-evb4-lp4x.dtsi]
&pinctrl {
	......
	wireless-bluetooth {
		uart8_gpios: uart8-gpios {
			// gpio3控制器,4号引脚,引脚复用为GPIO功能,不配置上下拉
			rockchip,pins = <3 RK_PA4 RK_FUNC_GPIO &pcfg_pull_none>;
		};
		......
		bt_wake_gpio: bt-wake-gpio {
			rockchip,pins = <3 RK_PC1 RK_FUNC_GPIO &pcfg_pull_none>;
		};
	};
	......
};

[arch/arm64/boot/dts/rockchip/rockchip-pinconf.dtsi]
&pinctrl {
	......
	/omit-if-no-ref/
	pcfg_pull_none: pcfg-pull-none {
		// 该属性定义在drivers/pinctrl/pinconf-generic.c文件中
		bias-disable;
	};
	......
};

[arch/arm64/boot/dts/rockchip/rk3588s.dtsi]
pinctrl: pinctrl {
	......
	// gpio3控制器设备树节点
	gpio3: gpio@fec40000 {
		compatible = "rockchip,gpio-bank";
		reg = <0x0 0xfec40000 0x0 0x100>;
		// 整个gpio3控制器使用gic的280+32=312号中断,高电平触发
		interrupts = ;
		clocks = <&cru PCLK_GPIO3>, <&cru DBCLK_GPIO3>;
		gpio-controller;  // gpio控制器
		#gpio-cells = <2>;
		gpio-ranges = <&pinctrl 0 96 32>;
		interrupt-controller; // gpio控制器也是一个中断控制器
		#interrupt-cells = <2>;
	};
	......
};

wireless_bluetooth驱动如下所示,在probe函数中先使用of_get_named_gpio_flags函数获取GPIO(整数),然后调用devm_gpio_request请求GPIO,在remove函数中调用gpio_free释放GPIO。wireless_bluetooth驱动使用的是基于整数的GPIO接口,可以使用基于描述符的GPIO接口,如of_get_named_gpiod_flagsdevm_gpiod_put

[net/rfkill/rfkill-bt.c]
static int rfkill_rk_probe(struct platform_device *pdev)
{
	......
	ret = bluetooth_platdata_parse_dt(&pdev->dev, pdata);
	......
	ret = rfkill_rk_setup_gpio(pdev, &pdata->rts_gpio, rfkill->pdata->name,
				   "rts");
	......
	ret = rfkill_rk_setup_gpio(pdev, &pdata->wake_gpio, pdata->name,
				   "wake");
	......
}

static int bluetooth_platdata_parse_dt(struct device *dev,
	struct rfkill_rk_platform_data *data)
{
	......
	// 获取GPIO
	gpio = of_get_named_gpio_flags(node, "uart_rts_gpios", 0, &flags);
	......
	// 获取GPIO
	gpio = of_get_named_gpio_flags(node, "BT,wake_gpio", 0, &flags);;
	......
}

static int rfkill_rk_setup_gpio(struct platform_device *pdev,
	struct rfkill_rk_gpio *gpio, const char *prefix,
	const char *name)
{
	......
	// 请求GPIO
	ret = devm_gpio_request(&pdev->dev, gpio->io, gpio->name);
	......
}

static int rfkill_rk_remove(struct platform_device *pdev)
{
	......
	// 判断GPIO是否有效
	if (gpio_is_valid(rfkill->pdata->rts_gpio.io))
		gpio_free(rfkill->pdata->rts_gpio.io);  // 释放GPIO
	......
	if (gpio_is_valid(rfkill->pdata->wake_gpio.io))
		gpio_free(rfkill->pdata->wake_gpio.io);
	......
}

可以利用sys文件系统在用户空间操作gpio,具体如下所示。

echo 400 >  /sys/class/gpio/export    // 导出gpio
echo 400 >  /sys/class/gpio/unexport  // 不导出gpio

cat /sys/class/gpio/gpio400/direction // 查看gpio方向
# gpio方向默认输出低电平
echo in/out > /sys/class/gpio/gpio400/direction  // 设置gpio方向

cat /sys/class/gpio/gpio400/value
# 非0值-高电平,0-低电平,若配置为中断引脚,则可在此文件上调用poll轮询
echo 非0值/0 > /sys/class/gpio/gpio400/value

cat /sys/class/gpio/gpio400/edge
# 中断输入引脚有效
echo none/falling/rising/both > /sys/class/gpio/gpio400/edge

cat /sys/class/gpio/gpio400/active_low
# 写入非零值,真假反转
echo 1 > /sys/class/gpio/gpio400/active_low

cat /sys/kernel/debug/gpio # 查看GPIO的编号和使用情况

4.驱动分析

RK3588 GPIO驱动由pinctrl驱动调用of_platform_populate匹配。匹配成功后,rockchip_gpio_probe函数就会被调用。

[drivers/gpio/gpio-rockchip.c]
static const struct of_device_id rockchip_gpio_match[] = {
	{ .compatible = "rockchip,gpio-bank", },
	{ .compatible = "rockchip,rk3188-gpio-bank0" },
	{ },
};
static struct platform_driver rockchip_gpio_driver = {
	.probe		= rockchip_gpio_probe,
	.remove		= rockchip_gpio_remove,
	.driver		= {
		.name	= "rockchip-gpio",
		.of_match_table = rockchip_gpio_match,
	},
};

RK3588驱动的执行流程如下,主要的工作有:

  1. 获取设备树中的GPIO信息并进行处理,gpio的bank信息定义在pinctrl驱动中
  2. 注册GPIO控制器,即将rockchip_gpiolib_chip注册到内核中,每个gpio_chip都对应一个gpio_device,每个gpio bank中的gpio pin都有一个gpio_desc,pin的编号由gpio_desc距数组开头的偏移值决定,最后设置GPIO的Linux编号,初始化gpio sys,具体路劲为/sys/class/gpio。
  3. 注册GPIO中断。GPIO也是一个中断控制器,串联到GIC中断控制器下面,当GPIO中断发生后,GPIO控制器会将中断上报到GIC,GIC在将中断报告给CPU。GPIO中断的处理过程则正常相反,CPU首先调用GIC提供的中断函数,再调用GPIO驱动的中断处理函数,最后调用GPIO消费者驱动注册中断处理函数。
rockchip_gpio_probe
	of_pinctrl_get          // 获取节点的别名
	// 获取rockchip_pin_bank,该数据定义在pinctrl驱动中
	rockchip_gpio_find_bank
	// 获取该gpio控制器的信息
	rockchip_get_bank_data
		devm_ioremap_resource  // 映射寄存器地址
		irq_of_parse_and_map   // 获取gpio控制器的虚拟中断号
		clk_prepare_enable     // 使能时钟
		bank->gpio_regs = &gpio_regs_v2  // 获取gpio寄存器偏移地址
	rockchip_gpiolib_register
		bank->gpio_chip = rockchip_gpiolib_chip  // 设置gpio_chip
		gc->base = bank->pin_base;  // gpio控制器编号
		gc->ngpio = bank->nr_pins;  // gpio控制器中pin的数量
		gc->label = bank->name;     // gpio控制器名称
		gpiochip_add_data
			// 分配gpio_device
			gdev = kzalloc(sizeof(*gdev), GFP_KERNEL)
			// 分配ngpio个gpio_desc,每个gpio对应一个
			gdev->descs = kcalloc(gc->ngpio, sizeof(gdev->descs[0]...))
			// 若驱动未设置Linux gpio编号,则内核会查找设置
			// bank0 = 512 - 32,bank1 = 512 - 2 * 32...
			gpiochip_find_base(gc->ngpio)
			// 若设备树定义了gpio-ranges属性,还需要建立
			// gpio linux编号和pinctrl的映射关系
			gpiochip_add_pin_range
			// 初始化gpio sys,这样用户空间可以利用sys文件系统操作gpio
			gpiochip_setup_dev(struct gpio_device *gdev)
		rockchip_interrupts_register  // 注册gpio控制器中断
			// 创建一个线性映射中断的irq_domain,最大映射32个中断
			// 映射方法由irq_generic_chip_ops决定
			irq_domain_add_linear(..., 32, &irq_generic_chip_ops, NULL)
			// 分配irq_domain_chip_generic,有32个中断,一个irq_chip_generic
			// 该chip上的所有中断的默认处理函数为handle_level_irq
			irq_alloc_domain_generic_chips(bank->domain, 32, 1,
				"rockchip_gpio_irq", handle_level_irq, clr, 0, 0);
			irq_get_domain_generic_chip // 获取上面分配的irq_chip_generic
			// 设置gpio中断相关寄存器读写函数
			gc->reg_writel = gpio_writel_v2;
			gc->reg_readl = gpio_readl_v2;
			// gpio作为中断控制器,也应该有中断响应、屏蔽、使能、设置中断类型等函数
			gc->chip_types[0].chip.irq_ack = irq_gc_ack_set_bit;
			gc->chip_types[0].chip.irq_mask = irq_gc_mask_set_bit;
			gc->chip_types[0].chip.irq_unmask = irq_gc_mask_clr_bit;
			gc->chip_types[0].chip.irq_enable = rockchip_irq_enable;
			gc->chip_types[0].chip.irq_disable = rockchip_irq_disable;
			gc->chip_types[0].chip.irq_set_wake = irq_gc_set_wake;
			gc->chip_types[0].chip.irq_suspend = rockchip_irq_suspend;
			gc->chip_types[0].chip.irq_resume = rockchip_irq_resume;
			gc->chip_types[0].chip.irq_set_type = rockchip_irq_set_type;
			// 设置gpio bank的链式中断处理函数,当该gpio bank中的gpio发生中断时,gic
			// 的中断处理函数会调用该函数
			irq_set_chained_handler_and_data(...rockchip_irq_demux...);

GPIO初始化的过程中用到的数据结构如下所示。

[drivers/pinctrl/pinctrl-rockchip.c]
// 3588 gpio信息,主要有gpio bank编号、pin数量、iomux配置宽度、输出类型
static struct rockchip_pin_bank rk3588_pin_banks[] = {
	RK3588_PIN_BANK_FLAGS(0, 32, "gpio0",
			      IOMUX_WIDTH_4BIT, PULL_TYPE_IO_1V8_ONLY),
	RK3588_PIN_BANK_FLAGS(1, 32, "gpio1",
			      IOMUX_WIDTH_4BIT, PULL_TYPE_IO_1V8_ONLY),
	RK3588_PIN_BANK_FLAGS(2, 32, "gpio2",
			      IOMUX_WIDTH_4BIT, PULL_TYPE_IO_1V8_ONLY),
	RK3588_PIN_BANK_FLAGS(3, 32, "gpio3",
			      IOMUX_WIDTH_4BIT, PULL_TYPE_IO_1V8_ONLY),
	RK3588_PIN_BANK_FLAGS(4, 32, "gpio4",
			      IOMUX_WIDTH_4BIT, PULL_TYPE_IO_1V8_ONLY),
};

[drivers/gpio/gpio-rockchip.c]
// gpio驱动定义的gpio_chip数据结构
static const struct gpio_chip rockchip_gpiolib_chip = {
	.request = gpiochip_generic_request,
	.free = gpiochip_generic_free,
	.set = rockchip_gpio_set,
	.get = rockchip_gpio_get,
	// 获取方向
	.get_direction	= rockchip_gpio_get_direction,
	// 输入
	.direction_input = rockchip_gpio_direction_input,
	// 输出
	.direction_output = rockchip_gpio_direction_output,
	.set_config = rockchip_gpio_set_config,
	.to_irq = rockchip_gpio_to_irq,  // 获取gpio的中断号
	.owner = THIS_MODULE,
};

[kernel/irq/generic-chip.c]
// 获取gpio中断号并进行映射
struct irq_domain_ops irq_generic_chip_ops = {
	.map	= irq_map_generic_chip,
	.unmap  = irq_unmap_generic_chip,
	.xlate	= irq_domain_xlate_onetwocell,
};

5.GPIO中断号

GPIO设备树中定义的是GPIO bank的中断号,而每个GPIO bank中的pin都有中断号(虚拟)。GPIO消费者驱动使用gpiod_to_irq获取GPIO pin的中断号(虚拟)。gpiod_to_irq内部会调用rockchip_gpio_to_irq,将GPIO pin的引脚编号映射为虚拟中断号。

gpiod_to_irq
    offset = gpio_chip_hwgpio(desc);
        // 返回descs的偏移,表示gpio引脚的编号
        return desc - &desc->gdev->descs[0];
    gc->to_irq(gc, offset)
    rockchip_gpio_to_irq
        // gpio中断控制器irq_domain采用chained形式
        irq_create_mapping(domain, offset);
            // gpio引脚的编号offset就是hwirq,将hwirq映射为虚拟中断号
            irq_create_mapping_affinity(host, hwirq, NULL)
                // 检查该gpio硬件中断号是否被映射
                irq_find_mapping(domain, hwirq);
                    domain->linear_revmap[hwirq]  // 线性映射
                    radix_tree_lookup(&domain->revmap_tree, hwirq) // 非线性映射
                // 获取虚拟中断号,并分配irq_desc
                virq = irq_domain_alloc_descs(......);
                    __irq_alloc_descs
                        bitmap_find_next_zero_area // 从位图中找一个空闲的bit
                        alloc_descs
                            alloc_desc
                                desc = kzalloc_node(sizeof(*desc), ...)
                                irq_insert_desc(start + i, desc)
                                    radix_tree_insert(&irq_desc_tree, irq, desc);
                                // 在位图中设置获取的虚拟中断号
                                bitmap_set(allocated_irqs, start, cnt);
                irq_domain_associate
                    irq_data->hwirq = hwirq;    // 保存gpio硬件中断号
                    irq_data->domain = domain;  // 保存gpio中断控制器irq_domain
                    domain->ops->map(domain, virq, hwirq)
                    gpiochip_irq_map
                        irq_set_chip_data(irq, gc);
                            // 保存私有数据
                            desc->irq_data.chip_data = data;
                        irq_set_chip_and_handler
                            // 将irq_chip设置到irq_data中
                            irq_set_chip(irq, chip)
                            // 设置中断处理函数,这里设置的handle_bad_irq
                            __irq_set_handler(irq, handle, 0, name)
                    irq_domain_set_mapping(domain, hwirq, irq_data);
                        // 线性映射
                        irq_domain_set_mapping(domain, hwirq, irq_data);
                        // 非线性映射
                        radix_tree_insert(&domain->revmap_tree, hwirq, irq_data);

6.GPIO中断处理流程

GPIO的中断处理流程如下所示,主要的工作流程如下:

Linux GPIO模块-RK3588 GPIO驱动分析_第5张图片

你可能感兴趣的:(Linux驱动,linux,GPIO,中断,RK3588)