一、Linux的GPIO驱动框架介绍
- 功能包含:控制引脚的方向(输入/输出)、读取输入值、设置输出值、中断等
- 屏蔽了物理硬件层面的真实有效电平,在内核中使用逻辑电平,如输出1在高电平有效时输出高电平,在低电平有效时输出低电平
- 管理芯片自带的GPIO和扩展的GPIO,扩展的GPIO速度较慢,使用时不建议获取spinlock
二、GPIO子系统的三层
- 用户层,包括led、key等GPIO设备,作为consumer
- 核心层,主要是gpiolib.c,向用户层提供了gpiod_xx的API,给控制器层提供了gpiochip_add_data的API
- 控制器层,GPIO控制器的驱动代码,作为provider
三、典型的设备树文件
/{
gpio_virt: virtual_gpio_controller{
compatible = "user,virtual_gpio";
gpio-controller;
#gpio-cells = <2>;
ngpios = <4>;
};
my_led {
compatible = "user,led";
led-gpios = <&gpio_virt 0 GPIO_ACTIVE_LOW>;
};
};
四、三个比较重要的结构体
struct gpio_device{ // 描述GPIO控制器
int id; // 第几个GPIO控制器
struct gpio_chip *chip; // 该GPIO组的操作函数
struct gpio_desc *descs; // 描述GPIO控制器下的每个GPIO
int base; // 一组GPIOxx的基号码
u16 ngpio; // 一组GPIO有多少个
struct list_head list; // 管理gpio控制器的链表
...
};
struct gpio_chip{
struct gpio_device *gpiodev; // 属于哪一个GPIO控制器
... // 一堆函数指针
int base; // 一组GPIOxx的基号码
u16 ngpio; // 一组GPIO有多少个
...
};
struct gpio_desc{ // 描述GPIO
struct gpio_device *gdev; // 属于哪一个GPIO控制器
...
};
五、驱动程序流程
// 控制器的驱动
1. 分配一个gpio_chip结构体
2. 设置gpio_chip结构体成员、label、parent、owner、get、set、direction_input、direction_output、base、ngpio
3. 使用devm_gpiochip_add_data的API使用gpio_chip构建一个gpio_device,并添加到链表中
4. 需要实现get、set、direction_input、direction_output等函数
// GPIO设备的驱动
1. 使用gpiod_get获得一个gpio_desc结构体
2. 通过gpiod_xxx函数操作对应的GPIO
六、加入pinctrl的gpio
// 设备树
gpio控制器节点中加入属性,当前gpio控制器的0引脚对应pinctrl的64引脚,共4组对应关系
gpio-ranges = <&pinctrl0 0 64 4>;
// 驱动程序
gpio控制器的驱动程序中,给gpio_chip结构体提供request函数,可以使用gpiolib.c中提供的gpiochip_generic_request
pinctrl控制器的驱动程序中,给pmx_ops结构体提供gpio_request_enable或request函数
// 对应ZYNQ,不存在pinctrl的概念,因此也没有这项功能,以上说法来自网上
七、用户编程
// sysfs文件系统
cd /sys/class/gpio
ls
// 显示gpiochipxxx表示gpio控制器、显示gpioxx表示gpio引脚
// export和unexport用作根据引脚号创建和销毁gpio的引脚
// gpioxx目录下direction、value、edge表示引脚的方向和值以及触发沿
// IO命令
io -r reg
io -w reg data
// /dev/mem+mmap将物理地址和该设备节点映射
mmap_addr = (char *)mmap(NULL, MMAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, MMAP_ADDR);
八、调试gpio
cat /sys/kernel/debug/gpio
/sys/kernel/debug/pinctrl/*/pinmux-pins
/sys/kernel/debug/pinctrl/*/pins
/sys/kernel/debug/pinctrl/*/gpio-ranges
/sys/kernel/debug/pinctrl/*/pinmux-functions
/sys/kernel/debug/pinctrl/*/pingroups
/sys/kernel/debug/pinctrl/*/pinconf-pins
九、GPIO子系统的API
// 消费者
struct gpio_desc *__must_check gpiod_get(struct device *dev,const char *con_id,enum gpiod_flags flags); // 获得GPIO的描述符,参数二是设备树中xx-gpios的xx
struct gpio_desc *gpiod_get_index(struct device *dev, const char *con_id, unsigned int idx, enum gpiod_flags flags); // 获得GPIO的描述符,参数三是xx-gpios中的第几个gpio
void gpiod_put(struct gpio_desc *desc); // 释放GPIO描述符
int gpiod_get_direction(struct gpio_desc *desc); // 获得方向
int gpiod_direction_input(struct gpio_desc *desc); // 设置GPIO为输入
int gpiod_direction_output(struct gpio_desc *desc, int value); // 设置GPIO为输出并设置初始值
int gpiod_get_value(const struct gpio_desc *desc); // 获得GPIO的电平
void gpiod_set_value(struct gpio_desc *desc, int value); // 设置GPIO的电平
int gpiod_to_irq(const struct gpio_desc *desc); // 将GPIO转为中断号
// 三级节点,/下的节点下的节点
unsigned int device_get_child_node_count(struct device *dev); // 获得device对应的节点下的子节点的数量
struct fwnode_handle *device_get_next_child_node(struct device *dev, struct fwnode_handle *child); // 获得下一个子节点的地址
struct gpio_desc *fwnode_get_named_gpiod(struct fwnode_handle *fwnode, const char *propname, int index, enum gpiod_flags dflags, const char *label); // 根据节点获得GPIO
// pinctrl非default属性设置
struct pinctrl* pinctrl_get(struct device *dev); // 获得一个pinctrl结构体
struct pinctrl_state *pinctrl_lookup_state(struct pinctrl *p, const char *name); // 根据名字查找状态
int pinctrl_select_state(struct pinctrl *p, struct pinctrl_state *s); // 设置pinctrl状态到硬件
void pinctrl_put(struct pinctrl *p); // 释放pinctrl
十、动态切换引脚复用
- 实现原理是sysfs的device的属性文件
- 对属性文件的写操作,调用store函数,由此调用pinctrl_select_state实现pinctrl的切换