一. 简介
前面几篇文章简单了解了 Linux设备树文件中的 GPIO设备节点信息。
本文简单了解一下 Linux内核源码中,有关 GPIO子系统的驱动实现。本文继续以 I.MX6ULL-ALPHA 开发板上的 UART1_RTS_B ( SD 卡的检测引脚)为例,分析GPIO子系统的驱动实现。
注意:本小节会涉及到
Linux
驱动分层与分离、平台设备驱动等还未讲解的知识,所以本小节教
程可以不用看,不会影响后续的实验。
二. IMX6ULL的GPIO子系统驱动
通过前面几篇文章学习,I.MX6ULL-ALPHA 开发板上的 SD 卡的检测引脚(即UART1_RTS_B)所使用的GPIO为 GPIO1_IO19。也就是 GPIO1组。
打开 imx6ull.dtsi,找到 GPIO1的设备节点信息:
gpio1: gpio@0209c000 { compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio"; reg = <0x0209c000 0x4000>; interrupts = <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>, <GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH>; gpio-controller; #gpio-cells = <2>; interrupt-controller; #interrupt-cells = <2>; };
gpio1 节点的 compatible 属性描述了兼容性,在 Linux 内核中搜索这两个字符串:
“fsl,imx6ul-gpio” “fsl,imx35-gpio”
查找 GPIO 驱动代码实现。可以在 drivers/gpio/gpio-mxc.c文件中匹配到, gpio-mxc.c 就是 I.MX6ULL 的 GPIO 驱动文件。
gpio-mxc.c 文件中有如下所示 of_device_id 匹配表:
static const struct of_device_id mxc_gpio_dt_ids[] = { { .compatible = "fsl,imx1-gpio", .data = &mxc_gpio_devtype[IMX1_GPIO], }, { .compatible = "fsl,imx21-gpio", .data = &mxc_gpio_devtype[IMX21_GPIO], }, { .compatible = "fsl,imx31-gpio", .data = &mxc_gpio_devtype[IMX31_GPIO], }, { .compatible = "fsl,imx35-gpio", .data = &mxc_gpio_devtype[IMX35_GPIO], }, { /* sentinel */ } };
第
156
行的
compatible
值为“
fsl,imx35-gpio
”,和
gpio1
的
compatible
属性匹配,因此
gpio-mxc.c
就是
I.MX6ULL
的
GPIO
控制器驱动文件。
gpio-mxc.c
所在的目录为
drivers/gpio
,打开这
个目录可以看到很多芯片的
gpio
驱动文件, “
gpiolib
” 开始的文件是
gpio
驱动的核心文件。
我们重点来看一下
gpio-mxc.c
这个文件,在
gpio-mxc.c
文件中,有如下所示内容:
static struct platform_driver mxc_gpio_driver = { .driver = { .name = "gpio-mxc", .of_match_table = mxc_gpio_dt_ids, }, .probe = mxc_gpio_probe, .id_table = mxc_gpio_devtype, };
可以看出,
GPIO
驱动也是个平台设备驱动,因此当设备树中的设备节点与驱动的
of_device_id
匹配以后
probe
函数就会执行,在这里就是
mxc_gpio_probe
函数,这个函数就是
I.MX6ULL
的
GPIO
驱动入口函数。
我们简单来分析一下
mxc_gpio_probe
这个函数,函数内容如下:
static int mxc_gpio_probe(struct platform_device *pdev) { struct device_node *np = pdev->dev.of_node; struct mxc_gpio_port *port; struct resource *iores; int irq_base; int err; mxc_gpio_get_hw(pdev); port = devm_kzalloc(&pdev->dev, sizeof(*port), GFP_KERNEL); if (!port) return -ENOMEM; iores = platform_get_resource(pdev, IORESOURCE_MEM, 0); port->base = devm_ioremap_resource(&pdev->dev, iores); if (IS_ERR(port->base)) return PTR_ERR(port->base); port->irq_high = platform_get_irq(pdev, 1); port->irq = platform_get_irq(pdev, 0); if (port->irq < 0) return port->irq; /* disable the interrupt and clear the status */ writel(0, port->base + GPIO_IMR); writel(~0, port->base + GPIO_ISR); if (mxc_gpio_hwtype == IMX21_GPIO) { /* * Setup one handler for all GPIO interrupts. Actually setting * the handler is needed only once, but doing it for every port * is more robust and easier. */ irq_set_chained_handler(port->irq, mx2_gpio_irq_handler); } else { /* setup one handler for each entry */ irq_set_chained_handler(port->irq, mx3_gpio_irq_handler); irq_set_handler_data(port->irq, port); if (port->irq_high > 0) { /* setup handler for GPIO 16 to 31 */ irq_set_chained_handler(port->irq_high, mx3_gpio_irq_handler); irq_set_handler_data(port->irq_high, port); } } err = bgpio_init(&port->bgc, &pdev->dev, 4, port->base + GPIO_PSR, port->base + GPIO_DR, NULL, port->base + GPIO_GDIR, NULL, 0); if (err) goto out_bgio; port->bgc.gc.to_irq = mxc_gpio_to_irq; port->bgc.gc.base = (pdev->id < 0) ? of_alias_get_id(np, "gpio") * 32 : pdev->id * 32; err = gpiochip_add(&port->bgc.gc); if (err) goto out_bgpio_remove; irq_base = irq_alloc_descs(-1, 0, 32, numa_node_id()); if (irq_base < 0) { err = irq_base; goto out_gpiochip_remove; } port->domain = irq_domain_add_legacy(np, 32, irq_base, 0, &irq_domain_simple_ops, NULL); if (!port->domain) { err = -ENODEV; goto out_irqdesc_free; } /* gpio-mxc can be a generic irq chip */ mxc_gpio_init_gc(port, irq_base); list_add_tail(&port->node, &mxc_gpio_ports); return 0; out_irqdesc_free: irq_free_descs(irq_base, 32); out_gpiochip_remove: gpiochip_remove(&port->bgc.gc); out_bgpio_remove: bgpio_remove(&port->bgc); out_bgio: dev_info(&pdev->dev, "%s failed with errno %d ", __func__, err); return err; }
第
3
行,设备树节点指针。
第
4
行,定义一个结构体指针
port
,结构体类型为
mxc_gpio_port
。
gpio-mxc.c 的重点工作就是维护 结构体mxc_gpio_port
,结构体
mxc_gpio_port
就是对
I.MX6ULL GPIO
的抽象。
mxc_gpio_port
结
构体定义如下:
struct mxc_gpio_port { struct list_head node; void __iomem *base; int irq; int irq_high; struct irq_domain *domain; struct bgpio_chip bgc; u32 both_edges; };
mxc_gpio_port
的
bgc
成员变量很重要,因为稍后的重点就是初始化
bgc
。
继续回到
mxc_gpio_probe
函数函数。第
9
行调用
mxc_gpio_get_hw()
函数,获取
gpio
的硬件相关数据,其实就是获取
gpio
的寄存器组。
函数
mxc_gpio_get_hw()实现
如下:
static void mxc_gpio_get_hw(struct platform_device *pdev) { const struct of_device_id *of_id = of_match_device(mxc_gpio_dt_ids, &pdev->dev); enum mxc_gpio_hwtype hwtype; ...... if (hwtype == IMX35_GPIO) mxc_gpio_hwdata = &imx35_gpio_hwdata; else if (hwtype == IMX31_GPIO) mxc_gpio_hwdata = &imx31_gpio_hwdata; else mxc_gpio_hwdata = &imx1_imx21_gpio_hwdata; mxc_gpio_hwtype = hwtype; }
第
385
行,
mxc_gpio_hwdata
是个全局变量,如果硬件类型是
IMX35_GPIO
的话设置
mxc_gpio_hwdat
为
imx35_gpio_hwdata
。对于
I.MX6ULL
而言,硬件类型就是
IMX35_GPIO
,
imx35_gpio_hwdata
是个结构体变量,描述了
GPIO
寄存器组,内容如下:
static struct mxc_gpio_hwdata imx35_gpio_hwdata = { .dr_reg = 0x00, .gdir_reg = 0x04, .psr_reg = 0x08, .icr1_reg = 0x0c, .icr2_reg = 0x10, .imr_reg = 0x14, .isr_reg = 0x18, .edge_sel_reg = 0x1c, .low_level = 0x00, .high_level = 0x01, .rise_edge = 0x02, .fall_edge = 0x03, };
大家将
imx35_gpio_hwdata
中的各个成员变量和
《IMX6ULL参考手册》中的
GPIO
寄存器表对比,就会发
现,
imx35_gpio_hwdata
结构体就是
GPIO
寄存器组结构。这样我们后面就可以通过
mxc_gpio_hwdata
这个全局变量,来访问
GPIO
的相应寄存器了。
第
16
行,调用
devm_ioremap_resource()
函数进行内存映射,得到地址
0x0209C000
在
Linux
内核中的虚拟地址。
第 20~21
行,通过
platform_get_irq()
函数获取中断号。
第
20
行获取高
16
位
GPIO
的中
断号,第
21
行获取低
16
位
GPIO
中断号。
第 26
、
27
行,操作
GPIO1
的
IMR
和
ISR
这两个寄存器,关闭
GPIO1
所有
IO
中断,并
且清除状态寄存器。
第
37~46
行,设置对应
GPIO
的中断服务函数,不管是高
16
位还是低
16
位,中断服务
函数都是
mx3_gpio_irq_handler
。
第 49
行,
bgpio_init()
函数第一个参数为
bgc
,是
bgpio_chip
结构体指针。
bgpio_chip
结构体有个
gc
成员变量,
gc
是个
gpio_chip
结构体类型的变量。
gpio_chip
结构体是抽象出来的
GPIO
控制器,
gpio_chip
结构体如下所示
(
有缩减
)
:
struct gpio_chip { const char *label; struct device *dev; struct module *owner; struct list_head list; int (*request)(struct gpio_chip *chip, unsigned offset); void (*free)(struct gpio_chip *chip, unsigned offset); int (*get_direction)(struct gpio_chip *chip, unsigned offset); int (*direction_input)(struct gpio_chip *chip, unsigned offset); int (*direction_output)(struct gpio_chip *chip, unsigned offset, int value); int (*get)(struct gpio_chip *chip, unsigned offset); void (*set)(struct gpio_chip *chip, unsigned offset, int value); ...... };
可以看出,
gpio_chip
大量的成员都是函数,这些函数就是
GPIO
操作函数。
bgpio_init()函数 主 要 任 务 就 是 初 始 化 bgc->gc
。
bgpio_init
里 面 有 三 个
setup
函数:
bgpio_setup_io
、
bgpio_setup_accessors
和
bgpio_setup_direction
。这三个函数就是初始化
bgc->gc
中的各种有关
GPIO
的操作,比如,输出,输入等等。
mxc_gpio_probe()
函数中,第 49
行的
GPIO_PSR
、
GPIO_DR
和
GPIO_GDIR
都
是
I.MX6ULL
的
GPIO
寄存器。这些寄存器地址会赋值给
bgc
参数的
reg_dat
、
reg_set
、
reg_clr
和
reg_dir
这些成员变量。至此,
bgc
既有了对
GPIO
的操作函数,又有了
I.MX6ULL
有关
GPIO
的寄存器,那么只要得到
bgc
就可以对
I.MX6ULL
的
GPIO
进行操作。
第 57
行,调用
gpiochip_add
()函数,向
Linux
内核注册
gpio_chip
,
也就是
port->bgc.gc
。注册完成以后,我们就可以在驱动中使用
gpiolib
提供的各个
API
函数。