IMX6ULL的GPIO子系统驱动代码说明一

一.  简介

前面几篇文章简单了解了 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
函数。