一. 简介
前面几篇文章简单了解了 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\n", __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
函数。