Linux gpio子系统

GPIO子系统

1.GPIO控制器的设备树

bash 复制代码
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>;
			};

2.SOC的GPIO控制器驱动

c 复制代码
//D:\mydir\imx-linux4.9.88\drivers\gpio\gpio-mxc.c

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 = 0;
	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;

	/* the controller clock is optional */
	port->clk = devm_clk_get(&pdev->dev, NULL);
	if (IS_ERR(port->clk))
		port->clk = NULL;

	err = clk_prepare_enable(port->clk);
	if (err) {
		dev_err(&pdev->dev, "Unable to enable clock.\n");
		return err;
	}

	pm_runtime_set_active(&pdev->dev);//Power Management电源运行时管理初始化
	pm_runtime_enable(&pdev->dev);
	err = pm_runtime_get_sync(&pdev->dev);
	if (err < 0)
		goto out_pm_dis;

	/* 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_and_data(port->irq,
						 mx3_gpio_irq_handler, port);
		if (port->irq_high > 0)
			/* setup handler for GPIO 16 to 31 */
			irq_set_chained_handler_and_data(port->irq_high,
							 mx3_gpio_irq_handler,
							 port);
	}
//内核通用 GPIO 芯片初始化函数(bit-banged GPIO),封装了 GPIO 基本操作
//在这里设置gpio_chip的direction_output direction_input get_direction等
	err = bgpio_init(&port->gc, &pdev->dev, 4,
			 port->base + GPIO_PSR,
			 port->base + GPIO_DR, NULL,
			 port->base + GPIO_GDIR, NULL,
			 BGPIOF_READ_OUTPUT_REG_SET);
	if (err)
		goto out_bgio;

	if (of_property_read_bool(np, "gpio_ranges"))
		port->gpio_ranges = true;
	else
		port->gpio_ranges = false;
//设置在这里设置gpio_chip的request和free等
	port->gc.request = mxc_gpio_request;
	port->gc.free = mxc_gpio_free;
	port->gc.parent = &pdev->dev;
	port->gc.to_irq = mxc_gpio_to_irq;
	port->gc.base = (pdev->id < 0) ? of_alias_get_id(np, "gpio") * 32 :
					     pdev->id * 32;
//注册GPIO 芯片到内核
	err = devm_gpiochip_add_data(&pdev->dev, &port->gc, port);
	if (err)
		goto out_bgio;
//中断号分配与中断域初始化
	irq_base = irq_alloc_descs(-1, 0, 32, numa_node_id());//分配中断描述符
	if (irq_base < 0) {
		err = irq_base;
		goto out_bgio;
	}

	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 */
	err = mxc_gpio_init_gc(port, irq_base, &pdev->dev);
	if (err < 0)
		goto out_irqdomain_remove;

	list_add_tail(&port->node, &mxc_gpio_ports);// 将端口加入全局链表管理

	platform_set_drvdata(pdev, port);// 保存 port 到设备私有数据
	pm_runtime_put(&pdev->dev);// 释放 PM 使用权(允许休眠)

	return 0;

out_pm_dis:
	pm_runtime_disable(&pdev->dev);
	clk_disable_unprepare(port->clk);
out_irqdomain_remove:
	irq_domain_remove(port->domain);
out_irqdesc_free:
	irq_free_descs(irq_base, 32);
out_bgio:
	dev_info(&pdev->dev, "%s failed with errno %d\n", __func__, err);
	return err;
}

3.SPI外设模拟GPIO 控制器

只需要分配gpio_chip空间,填充gpio_chip结构体设置值并且注册到内核gpiochip_add_data

C 复制代码
//D:\mydir\imx-linux4.9.88\drivers\gpio\gpio-74x164.c
static int gen_74x164_probe(struct spi_device *spi)
{
	struct gen_74x164_chip *chip;
	u32 nregs;
	int ret;

	/*
	 * bits_per_word cannot be configured in platform data
	 */
	spi->bits_per_word = 8;

	ret = spi_setup(spi);
	if (ret < 0)
		return ret;

	if (of_property_read_u32(spi->dev.of_node, "registers-number",
				 &nregs)) {
		dev_err(&spi->dev,
			"Missing registers-number property in the DT.\n");
		return -EINVAL;
	}

	chip = devm_kzalloc(&spi->dev, sizeof(*chip) + nregs, GFP_KERNEL);
	if (!chip)
		return -ENOMEM;

	spi_set_drvdata(spi, chip);

	chip->gpio_chip.label = spi->modalias;
	chip->gpio_chip.direction_output = gen_74x164_direction_output;
	chip->gpio_chip.get = gen_74x164_get_value;
	chip->gpio_chip.set = gen_74x164_set_value;
	chip->gpio_chip.set_multiple = gen_74x164_set_multiple;
	chip->gpio_chip.base = -1;

	chip->registers = nregs;
	chip->gpio_chip.ngpio = GEN_74X164_NUMBER_GPIOS * chip->registers;

	of_property_read_u8_array(spi->dev.of_node, "registers-default",
				 chip->buffer, chip->registers);

	chip->gpio_chip.can_sleep = true;
	chip->gpio_chip.parent = &spi->dev;
	chip->gpio_chip.owner = THIS_MODULE;

	mutex_init(&chip->lock);

	ret = __gen_74x164_write_config(chip);
	if (ret) {
		dev_err(&spi->dev, "Failed writing: %d\n", ret);
		goto exit_destroy;
	}

	ret = gpiochip_add_data(&chip->gpio_chip, chip);
	if (!ret)
		return 0;

exit_destroy:
	mutex_destroy(&chip->lock);

	return ret;
}

4.数据结构

层次

GPIOLIB向下提供的接口 在 drivers\gpio\gpiolib.c 中的 int gpiochip_add_data(struct gpio_chip *chip, void *data)

在 Linux 内核的 GPIO 子系统中,gpio_device、gpio_chip 和 gpio_desc 是三个核心数据结构,它们共同管理 GPIO 设备的硬件和软件资源。以下是它们的关系和作用:

gpio_device

关键字段

字段 说明
chip 指向 gpio_chip 结构体的指针,表示 GPIO 控制器。
base GPIO 的起始编号(全局编号)。
ngpio GPIO 的数量。
label GPIO 设备的名称(如 "gpiochip0")。

每个GPIO Controller用一个gpio_device来表示:

gpio_chip

字段 说明
label GPIO 控制器的名称(如 "gpiochip0")。
ngpio GPIO 的数量。
base GPIO 的起始编号(全局编号)。
direction_input 函数指针,用于将 GPIO 设置为输入模式。
direction_output 函数指针,用于将 GPIO 设置为输出模式。
get 函数指针,用于读取 GPIO 的值。
set 函数指针,用于设置 GPIO 的值。

我们并不需要自己创建gpio_device,编写驱动时要创建的是gpio_chip,里面提供了

gpio_desc

关键字段

字段 说明
chip 指向 gpio_chip 结构体的指针,表示 GPIO 控制器。
flags GPIO 的标志(如方向、状态等)。
label GPIO 引脚的名称(如 "led")。

我们去使用GPIO子系统时,首先是获得某个引脚对应的gpio_desc。 gpio_device表示一个GPIO Controller,里面支持多个GPIO。 在gpio_device中有一个gpio_desc数组,每一引脚有一项gpio_desc。

以下是 gpio_device、gpio_chip 和 gpio_desc 的关系:

lua 复制代码
gpio_device
    |
    |-- gpio_chip
    |       |
    |       |-- direction_input
    |       |-- direction_output
    |       |-- get
    |       |-- set
    |
    |-- gpio_desc
            |
            |-- chip
            |-- flags
            |-- label

三者共同构成了 Linux GPIO 子系统的核心,用于管理 GPIO 设备和引脚。

5.GPIO 控制器驱动程序

分配、设置、注册gpioc_chip结构体,示例:drivers\gpio\gpio-74x164.c

c 复制代码
struct gen_74x164_chip {
	struct gpio_chip	gpio_chip;
	struct mutex		lock;
	u32			registers;
	/*
	 * Since the registers are chained, every byte sent will make
	 * the previous byte shift to the next register in the
	 * chain. Thus, the first byte sent will end up in the last
	 * register at the end of the transfer. So, to have a logical
	 * numbering, store the bytes in reverse order.
	 */
	u8			buffer[0];
};

这里的gen_74x164_chip里面定义了gpio_chip

在控制器驱动中只需要设置gpio_chip的值,并且通过gpiochip_add_data注册到gpio子系统核心去。

6.写外设驱动流程

1.注意资源的active low或者active high。

2.旧API gpio_request和新API devm_gpiod_get 新API会自动申请并且在dev注销时自动释放资源。

3.GPIO_ACTIVE_LOW/HIGH是逻辑值,当HIGH时,逻辑为1,此时active有效,如果是active low输出低电平/如果是active high输出高电平。

4.注意platform_set_drvdata将平台设备和私有数据绑定,这样remove或者回调函数可以从pdev->driver_data中拿到资源或者回调函数的函体地址。

举例:假设硬件资源

GPIO1_IO05 → reset(active low)

GPIO1_IO06 → power enable(active high)

c 复制代码
//设备树
mydev: mydev@0 {
    compatible = "nxp,mydev-demo";
    status = "okay";

    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_mydev>;

    reset-gpios = <&gpio1 5 GPIO_ACTIVE_LOW>;
    power-gpios = <&gpio1 6 GPIO_ACTIVE_HIGH>;
};
//pinctrl
pinctrl_mydev: mydevgrp {
    fsl,pins = <
        MX6UL_PAD_GPIO1_IO05__GPIO1_IO05 0x10b0
        MX6UL_PAD_GPIO1_IO06__GPIO1_IO06 0x10b0
    >;
};

私有数据结构:

c 复制代码
struct mydev {
    struct device *dev;

    struct gpio_desc *reset_gpio;  //gpio_desc表示某个gpio口
    struct gpio_desc *power_gpio;

    dev_t devt;
    struct cdev cdev;
    struct class *class;
};

probe函数

c 复制代码
static int mydev_probe(struct platform_device *pdev)
{
    struct mydev *mdev;
    int ret;

    mdev = devm_kzalloc(&pdev->dev, sizeof(*mdev), GFP_KERNEL);
    if (!mdev)
        return -ENOMEM;

    mdev->dev = &pdev->dev;

    /* 获取 GPIO(逻辑语义,不关心物理电平) */
    mdev->reset_gpio =
        devm_gpiod_get(&pdev->dev, "reset", GPIOD_OUT_HIGH);
    if (IS_ERR(mdev->reset_gpio))
        return PTR_ERR(mdev->reset_gpio);

    mdev->power_gpio =
        devm_gpiod_get(&pdev->dev, "power", GPIOD_OUT_LOW);
    if (IS_ERR(mdev->power_gpio))
        return PTR_ERR(mdev->power_gpio);

    platform_set_drvdata(pdev, mdev);//私有数据和平台设备绑定

    /* === 上电 / 复位时序 === */

    gpiod_set_value(mdev->power_gpio, 1);   /* power on */
    msleep(10);

    gpiod_set_value(mdev->reset_gpio, 1);   /* assert reset */
    msleep(20);
    gpiod_set_value(mdev->reset_gpio, 0);   /* deassert reset */

    dev_info(&pdev->dev, "mydev initialized\n");
    return 0;
}

GPIOD_OUT_HIGH = 逻辑 active

GPIO_ACTIVE_LOW 决定 物理电平翻转

probe 失败 → 所有 GPIO 自动释放

remove函数

c 复制代码
static int mydev_remove(struct platform_device *pdev)
{
    struct mydev *mdev = platform_get_drvdata(pdev);

    dev_info(mdev->dev, "mydev removed\n");
    return 0;
}

platform_driver定义

c 复制代码
static const struct of_device_id mydev_of_match[] = {
    { .compatible = "nxp,mydev-demo" },
    { }
};
MODULE_DEVICE_TABLE(of, mydev_of_match);

static struct platform_driver mydev_driver = {
    .probe  = mydev_probe,
    .remove = mydev_remove,
    .driver = {
        .name = "mydev",
        .of_match_table = mydev_of_match,
    },
};

module_platform_driver(mydev_driver);
相关推荐
风中凌乱2 小时前
linux服务器安装部署mayfly-go
linux·服务器·golang
炽天使3282 小时前
龙虾尝鲜记(3)——装ubuntu(续)
linux·运维·ubuntu
敲代码还房贷2 小时前
Ubuntu24安装xcp_d
linux·ubuntu·医学生·afni
小王要努力上岸2 小时前
LVS DR 模式(Direct Routing)
linux·运维·lvs
2 小时前
Docker安装OpenClaw大龙虾详细教程
linux·openai
青桔柠薯片3 小时前
数据库编程:从SQLite基础到C语言集成
linux·数据库·学习·sqlite
MIXLLRED3 小时前
解决:Ubuntu系统引导修复操作步骤
linux·windows·ubuntu
somi73 小时前
Linux-基于网络爬虫技术的天气数据查询
linux·运维·服务器
爱装代码的小瓶子3 小时前
【C++与Linux进阶】详解信号的捕获:内核态和用户态的转换
linux·开发语言·c++