目录
1 引入
2 GPIO子系统的层次
3 gpio子系统驱动程序流程
4 gpio子系统的中药数据结构
5 gpio子系统函数调用的详细细节
6 GPIO子系统的sysfs接口
6.1有哪些gpio控制器
6.2每个gpio控制器的详细信息
6.3查看gpio使用情况
6.4通过SYSFS使用GPIO
6.4.1 确定GPIO编号
6.4.2 导出、设置方向、读写值
7 费曼学习法:于是我录制了一个讲解gpio子系统的学习视频
1 引入
当我们想用某个引脚控制LED灯的亮灭时,一般来说我们需要使能时钟,然后将引脚配置为GPIO功能,然后配置电气属性,然后配置GPIO为输出,最后根据控制GPIO的输出电平,其中配置GPIO的方向以及电平是由GPIO子系统来做的。
2 GPIO子系统的层次
上图就是gpio子系统的层次结构图,在其他的驱动程序里面,我们可以直接用gpiod_set_value这种函数来设置引脚的值,这个函数是在gpio库里面定义的,gpio库起到一个承上启下的作用,然后这个gpiod_set_value函数最终调用的是chip->set(chip, gpio_chip_hwgpio(desc), value)函数,这里的chip就是在gpio驱动程序里面注册的结构体,这个结构体体里面就包含了一些对gpio的操作函数。
3 gpio子系统驱动程序流程
上图是我根据Linux内核源码画的一个GPIO驱动程序流程图,我们在设备树中的gpio控制器节点里面的compatible为fsl,imx35-gpio,然后我们在内核源码中搜索,可以找到匹配的驱动为mxc_gpio_driver,然后当device和driver相匹配之后,会调用驱动程序里面的probe函数,在这里也就是mxc_gpio_probe函数,然后在这个mxc_gpio_probe函数里面其实就是做了下面三个工作
- 分配结构体
- 设置结构体
- 注册结构体
mxc_gpio_probe函数具体做的工作:首先调用了mxc_gpio_get_hw函数,这个函数是获取了gpio寄存器组的偏移地址,然后还一个platform_get_resource函数,这个platform_get_resource函数是得到了gpio的寄存器的基地址,然后调用了devm_kzalloc分配了一个mxc_gpio_port结构体,
struct mxc_gpio_port {
struct list_head node;
struct clk *clk;
void __iomem *base;
int irq;
int irq_high;
struct irq_domain *domain;
struct gpio_chip gc;
u32 both_edges;
int saved_reg[6];
bool gpio_ranges;
};
然后这个mxc_gpio_port结构体里面有一个重要的gpio_chip结构体成员。
struct gpio_chip {
const char *label;
struct gpio_device *gpiodev;
struct device *parent;
struct module *owner;
...省略一些...
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);
...省略一些...
enum single_ended_mode mode);
int (*to_irq)(struct gpio_chip *chip,
unsigned offset);
void (*dbg_show)(struct seq_file *s,
struct gpio_chip *chip);
...省略一些...
};
这个gpio_chip里面就是各种操作函数。
然后probe函数里面又调用了下面这个函数
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);
这里的参数
port->base + GPIO_PSR,
port->base + GPIO_DR,
port->base + GPIO_GDIR,
就是寄存器地址
设置完结构体之后,这个结构体里面有寄存器值也有操作函数。
在这个bgpio_init函数里面主要调用了下面三个函数
bgpio_setup_io(gc, dat, set, clr, flags);
bgpio_setup_accessors(dev, gc, flags & BGPIOF_BIG_ENDIAN,
flags & BGPIOF_BIG_ENDIAN_BYTE_ORDER);
bgpio_setup_direction(gc, dirout, dirin, flags);
然后这三个函数里面就是gpio的各种操作函数。然后调用了err = devm_gpiochip_add_data(&pdev->dev, &port->gc, port);函数,这个函数里面是分配了gpio_device结构体,然后gpio_device结构体里面的chip成员就是前面分配设置的gpio_chip结构体。
4 gpio子系统的中药数据结构
我们前面说过,我们要分配设置注册一个gpio_chip结构体,然后我们用gpiochip_add_data(chip, data);函数注册了一个gpio_device结构体,然后这个gpio_device结构体里面就包含gpio_chip结构体,然后这个gpio_device结构体里面除了chip成员外,还有descs成员,这个是用来表示引脚的,每一个引脚都有一个descs结构体,然后descs结构体里面有一个gdev成员,我们可以根据这个gdev成员找到这个引脚属于哪一个gpio控制器。
5 gpio子系统函数调用的详细细节
上图是gpio子系统的函数调用具体细节,我们一个gpio控制器就对应一个gpio_device结构体,然后这个结构体里面有
- base成员,base成员是这个gpio控制器里面引脚的起始标号,
- ngpio是引脚的个数,
- descs成员是一个结构体数组,里面每一项都是一个gpio_desc结构体,都表示一个引脚。
然后加入我们设备树里面有一个
myled{
compatible = "cumtchw"
led-gpios = <&gpio1 10 GPIO_ACTIVE_LOW>
};
那么当我们调用led_gpio = gpiod_get(&pdev->dev, "led", 0);函数时,那么就是根据led-gpios = <&gpio1 10 GPIO_ACTIVE_LOW> 可以得到是gpio1控制器里面的第10项,那么led_gpio就指向descs数组里面的第10项,然后我们这个desc其实就可找到这个引脚对应的控制器,然后那么当我们用gpiod_set_value(led_gpio, status);这个函数去设置引脚的时候就会找到控制器里面的chip->set(chip, gpio_chip_hwgpio(desc), value);然后这个函数的第二个参数是指设置这个控制器的第几个引脚,这里的第二个参数是这么得到的
static int __maybe_unused gpio_chip_hwgpio(const struct gpio_desc *desc)
{
return desc - &desc->gdev->descs[0];
} 这里就是10
6 GPIO子系统的sysfs接口
驱动程序为`drivers\gpio\gpiolib-sysfs.c`,
6.1有哪些gpio控制器
/sys/bus/gpio/devices`目录下,列出了所有的GPIO控制器:
6.2每个gpio控制器的详细信息
/sys/class/gpio/gpiochipXXX`下,有这些信息:
base // 这个GPIO控制器的GPIO编号
device
label // 名字
ngpio // 引脚个数
power
subsystem
uevent
6.3查看gpio使用情况
cat /sys/kernel/debug/gpio
6.4通过SYSFS使用GPIO
如果只是简单的引脚控制(比如输出、查询输入值),可以不编写驱动程序。
但是涉及中断的话,就需要编写驱动程序了。
6.4.1 确定GPIO编号
查看每个`/sys/class/gpio/gpiochipXXX`目录下的label,确定是你要用的GPIO控制器,也称为GPIO Bank。
根据它名字gpiochipXXX,就可以知道基值是XXX。
基值加上引脚offset,就是这个引脚的编号。
6.4.2 导出、设置方向、读写值
echo 509 > /sys/class/gpio/export
echo out > /sys/class/gpio/gpio509/direction
echo 1 > /sys/class/gpio/gpio509/value
echo 509 > /sys/class/gpio/unexportecho 509 > /sys/class/gpio/export
echo in > /sys/class/gpio/gpio509/direction
cat /sys/class/gpio/gpio509/value
echo 509 > /sys/class/gpio/unexport
7 费曼学习法:于是我录制了一个讲解gpio子系统的学习视频