RK3399平台开发系列讲解(内核子系统篇)2.1、GPIO子系统框架图解

    科技2022-07-10  268

    文章目录

    1、Linux GPIO子系统框架2、结构体的抽象2.1、GPIO描述符 gpio_desc2.2、GPIO操作集抽象:gpio_chip2.3、GPIO设备:gpio_device 3、向 Gpiolib 注册你的 GPIO:gpiochip_add

    平台内核版本安卓版本RK3399Linux4.4Android7.1

    1、Linux GPIO子系统框架

    在Linux中,GPIO 子系统大致分为 3 层:

    GPIO 硬件GPIO 硬件驱动层抽象驱动框架 从下到上为: 分层作用GPIO 硬件由多个 GPIO Controller 组成,每一个 Controller 管理一组 GPIO 引脚,由控制电路和对应寄存器组成。GPIO 硬件驱动层硬件驱动层需要实现与抽象框架进行交互的接口,为抽象硬件无关的驱动框架提供具体硬件驱动支持,在系统初始化时,GPIO 硬件驱动需要向驱动框架进行注册,然后驱动框架可以通过下层驱动接口进行具体硬件设备的操作。抽象驱动框架这一层是与硬件平台无关,抽象了所有 GPIO 的硬件特性,为上层用户(内核开发者所编写的驱动程序)提供统一的驱动接口。

    2、结构体的抽象

    2.1、GPIO描述符 gpio_desc

    在 Linux 中,GPIO 驱动框架是在gpiolib.c 中实现的。对于每一个 GPIO引脚,抽象了一个描述符 gpio_desc:

    目录:kernel-4.4\drivers\gpio\gpiolib.h

    struct gpio_desc { struct gpio_device *gdev; unsigned long flags; /* flag symbols are bit numbers */ #define FLAG_REQUESTED 0 #define FLAG_IS_OUT 1 #define FLAG_EXPORT 2 /* protected by sysfs_lock */ #define FLAG_SYSFS 3 /* exported via /sys/class/gpio/control */ #define FLAG_ACTIVE_LOW 6 /* value has active low */ #define FLAG_OPEN_DRAIN 7 /* Gpio is open drain type */ #define FLAG_OPEN_SOURCE 8 /* Gpio is open source type */ #define FLAG_USED_AS_IRQ 9 /* GPIO is connected to an IRQ */ #define FLAG_IS_HOGGED 11 /* GPIO is hogged */ #define FLAG_SLEEP_MAY_LOOSE_VALUE 12 /* GPIO may loose value in sleep */ /* Connection label */ const char *label; /* Name of the GPIO */ const char *name; };

    2.2、GPIO操作集抽象:gpio_chip

    GPIO 硬件是由若干个 GPIO Controller 组成,每一个Controller 负责管理一组 GPIO 引脚。结构体 gpio_chip 表示一组 GPIO 引脚的控制器,其中定义了对于具体硬件驱动的下层接口:

    目录:kernel-4.4/include/linux/gpio/driver.h

    struct gpio_chip { const char *label; //标号为了给系统提供诊断保留用的 struct gpio_device *gpiodev; //提供给 GPIO 可选择的设备 struct device *parent; struct module *owner; //标定拥有者防止被移除的模块污染正在使用的 GPIO int (*request)(struct gpio_chip *chip, //请求 GPIO unsigned offset); ...... }

    其中抽象了所有GPIO 的硬件特性,为上层用户(内核开发者所编写的驱动程序)提供统一的驱动接口。框架中主要功能分为三部分:

    GPIO 参数的设置,包括配置引脚为输入或输出,设置引脚上的电平高低等;GPIO 引脚状态查询,即查询 GPIO 引脚上当前的电平状态;中断管理,中断的使能与关闭,中断方式的选择,中断号管理等。

    gpio_chip中驱动接口如下:

    接口描述Request请求 GPIOFree释放 GPIOGet_direction获取当前 GPIO 引脚输入输出状态Direction_input配置 GPIO 为输入,返回当前 GPIO 状态Get获取 GPIO 引脚电平状态Direction_output配置 GPIO 为输出,并将电平状态设置为valueSet_debounce设置消抖动时间,尤其是 GPIO 按键时有用Set设置 GPIO 为 value 值To_irq把 GPIO 号转换为中断号Dbg_showDebug 信息

    具体硬件驱动需要根据不同硬件平台的特性实现上述接口,在系统初始化时,将每个 gpio_chip 注册进驱动框架,在驱动框架中会维护一个 GPIO 描述符数组,用于存储注册进来的 GPIO 引脚的具体硬件驱动。

    通过 AMBA 总线连接进CPU的 GPIO 控制器一般通过寄存器来控制,所以在gpio_chip中抽象了一个数据结构表示控制器的寄存器信息。

    2.3、GPIO设备:gpio_device

    gpio_device结构中,包含了gpio_chip(对接芯片的操作集),gpio_desc(一些 GPIO 的描述);这个结构贯穿了整个 Gpiolib,因为 gpio_device 代表的是一个 Bank,一般的 GPIO 有多个 Bank,所以Kernel中,对这 gpio_device的组织是由一个 gpio_devices 的链表构成。

    struct gpio_device { int id; struct device dev; struct cdev chrdev; struct device *mockdev; struct module *owner; struct gpio_chip *chip; struct gpio_desc *descs; int base; u16 ngpio; char *label; void *data; struct list_head list; #ifdef CONFIG_PINCTRL /* * If CONFIG_PINCTRL is enabled, then gpio controllers can optionally * describe the actual pin range which they serve in an SoC. This * information would be used by pinctrl subsystem to configure * corresponding pins for gpio usage. */ struct list_head pin_ranges; #endif };

    3、向 Gpiolib 注册你的 GPIO:gpiochip_add

    /* add/remove chips */ extern int gpiochip_add_data(struct gpio_chip *chip, void *data); static inline int gpiochip_add(struct gpio_chip *chip) { return gpiochip_add_data(chip, NULL); }

    在gpio_desc[]中分配空间,并链接chip结构;注册一个gpio_chip对应的gpio_desc到全局数组gpio描述符中: 目录:kernel-4.4\drivers\gpio\gpiolib.c

    int gpiochip_add_data(struct gpio_chip *chip, void *data) { unsigned long flags; int status = 0; unsigned i; int base = chip->base; struct gpio_device *gdev; /* * First: allocate and populate the internal stat container, and * set up the struct device. */ /* (1) 分配gpio chip对应的gdev结构 */ gdev = kzalloc(sizeof(*gdev), GFP_KERNEL); if (!gdev) return -ENOMEM; gdev->dev.bus = &gpio_bus_type; gdev->chip = chip; chip->gpiodev = gdev; if (chip->parent) { gdev->dev.parent = chip->parent; gdev->dev.of_node = chip->parent->of_node; } #ifdef CONFIG_OF_GPIO /* If the gpiochip has an assigned OF node this takes precedence */ if (chip->of_node) gdev->dev.of_node = chip->of_node; #endif gdev->id = ida_simple_get(&gpio_ida, 0, 0, GFP_KERNEL); if (gdev->id < 0) { status = gdev->id; goto err_free_gdev; } dev_set_name(&gdev->dev, "gpiochip%d", gdev->id); device_initialize(&gdev->dev); dev_set_drvdata(&gdev->dev, gdev); if (chip->parent && chip->parent->driver) gdev->owner = chip->parent->driver->owner; else if (chip->owner) /* TODO: remove chip->owner */ gdev->owner = chip->owner; else gdev->owner = THIS_MODULE; /* (2) 根据gpio chip中gpio的数量,分配gpio描述符空间 每个gpio pin对应一个独立的描述符gdev->descs[offset] */ gdev->descs = kcalloc(chip->ngpio, sizeof(gdev->descs[0]), GFP_KERNEL); if (!gdev->descs) { status = -ENOMEM; goto err_free_ida; } if (chip->ngpio == 0) { chip_err(chip, "tried to insert a GPIO chip with zero lines\n"); status = -EINVAL; goto err_free_descs; } if (chip->label) gdev->label = kstrdup(chip->label, GFP_KERNEL); else gdev->label = kstrdup("unknown", GFP_KERNEL); if (!gdev->label) { status = -ENOMEM; goto err_free_descs; } gdev->ngpio = chip->ngpio; gdev->data = data; spin_lock_irqsave(&gpio_lock, flags); /* * TODO: this allocates a Linux GPIO number base in the global * GPIO numberspace for this chip. In the long run we want to * get *rid* of this numberspace and use only descriptors, but * it may be a pipe dream. It will not happen before we get rid * of the sysfs interface anyways. */ /* (3) 根据chip->base给gdev->base赋值 */ if (base < 0) { base = gpiochip_find_base(chip->ngpio); //在gpio_desc[]中分配chip->ngpio个空间(从最后往前分配),返回第一个index if (base < 0) { status = base; spin_unlock_irqrestore(&gpio_lock, flags); goto err_free_label; } /* * TODO: it should not be necessary to reflect the assigned * base outside of the GPIO subsystem. Go over drivers and * see if anyone makes use of this, else drop this and assign * a poison instead. */ chip->base = base; } gdev->base = base; /* (4) 将gdev加入到全局链表中 */ status = gpiodev_add_to_list(gdev); if (status) { spin_unlock_irqrestore(&gpio_lock, flags); goto err_free_label; } spin_unlock_irqrestore(&gpio_lock, flags); /* (5) 初始化gpio描述符gdev->descs[i] */ for (i = 0; i < chip->ngpio; i++) { struct gpio_desc *desc = &gdev->descs[i]; desc->gdev = gdev; /* * REVISIT: most hardware initializes GPIOs as inputs * (often with pullups enabled) so power usage is * minimized. Linux code should set the gpio direction * first thing; but until it does, and in case * chip->get_direction is not set, we may expose the * wrong direction in sysfs. */ if (chip->get_direction) { /* * If we have .get_direction, set up the initial * direction flag from the hardware. */ int dir = chip->get_direction(chip, i); if (!dir) set_bit(FLAG_IS_OUT, &desc->flags); } else if (!chip->direction_input) { /* * If the chip lacks the .direction_input callback * we logically assume all lines are outputs. */ set_bit(FLAG_IS_OUT, &desc->flags); } } #ifdef CONFIG_PINCTRL INIT_LIST_HEAD(&gdev->pin_ranges); #endif status = gpiochip_set_desc_names(chip); if (status) goto err_remove_from_list; status = gpiochip_irqchip_init_valid_mask(chip); if (status) goto err_remove_from_list; /* (6.1) chip注册 */ status = of_gpiochip_add(chip); if (status) goto err_remove_chip; /* (6.2) chip注册 */ acpi_gpiochip_add(chip); /* * By first adding the chardev, and then adding the device, * we get a device node entry in sysfs under * /sys/bus/gpio/devices/gpiochipN/dev that can be used for * coldplug of device nodes and other udev business. * We can do this only if gpiolib has been initialized. * Otherwise, defer until later. */ if (gpiolib_initialized) { status = gpiochip_setup_dev(gdev); if (status) goto err_remove_chip; } return 0; err_remove_chip: acpi_gpiochip_remove(chip); gpiochip_free_hogs(chip); of_gpiochip_remove(chip); gpiochip_irqchip_free_valid_mask(chip); err_remove_from_list: spin_lock_irqsave(&gpio_lock, flags); list_del(&gdev->list); spin_unlock_irqrestore(&gpio_lock, flags); err_free_label: kfree(gdev->label); err_free_descs: kfree(gdev->descs); err_free_ida: ida_simple_remove(&gpio_ida, gdev->id); err_free_gdev: /* failures here can mean systems won't boot... */ pr_err("%s: GPIOs %d..%d (%s) failed to register\n", __func__, gdev->base, gdev->base + gdev->ngpio - 1, chip->label ? : "generic"); kfree(gdev); return status; } 内核研究所 认证博客专家 Linux Kernel Android 一直从业于半导体行业,曾为Linux内核开源文档社区提交过若干文档。主要从事Linux、Android相关系统软件开发工作,负责Soc芯片BringUp及系统软件开发,喜欢阅读内核源代码,在不断的学习和工作中深入理解内存管理,进程调度,文件系统,设备驱动等内核子系统。
    Processed: 0.027, SQL: 8