嵌入式Linux系统使用I/O与外界器件交互,简单的如GPIO,SPI,I2C,复杂的如CMOS,LCD-RGB,ETH,USB接口等,都是由I/O引脚来作为信号的输出或者输入接口。因为功能的复杂性,以及引脚的数目限制,每个引脚都可以复用成不同的功能,这就需要一套机制来处理引脚的复用功能,pinctrl框架正是用来完成此工作的。另外当引脚作为普通I/O时,又需要在驱动中控制引脚的电平机制,gpio子系统正是用来实现对引脚的控制。pinctrl和gpio子系统在Linux内核中相辅相成,共同完成对I/O引脚的管理工作
pinctrl框架主要通过在设备树中配置引脚的复用,内核中加载解析设备树后,即可直接完成引脚的复用功能和驱动能力的配置。gpio子系统则通过封装的接口,管理引脚资源,控制引脚状态,进行对寄存器操作的封装。这样就简化了驱动的开发,也避免了在代码中直接对寄存器的操作,更容易复用代码,降低了开发难度,是目前主流的引脚开发方式。
本节目录如下。
pinctrl主要控制引脚的复用,和驱动中相关的pinctrl的接口如下所示。
功能步骤 | 功能接口 | 接口说明 |
---|---|---|
获取pinctrl资源 | devm_pinctrl_get | 用于获取I/O引脚的pinctrl-x对应的所有资源列表 |
devm_pinctrl_get_select | 用于获取I/O引脚的pinctrl-x对应的资源列表,并同时选择name指定的资源 | |
获取pinctrl-name对应状态 | pinctrl_lookup_state | 从pinctrl资源中根据名称获取pinctrl-name对应的资源信息 |
设置pinctrl状态 | pinctrl_select_state | 设置pinctrl配置信息 |
释放pinctrl资源 | devm_pinctrl_put | 释放获取I/O引脚的pinctrl资源 |
具体功能接口如下所示。
// 获取I/O引脚的pinctrl资源列表
struct pinctrl *devm_pinctrl_get(struct device *dev)
// 用于获取I/O引脚的pinctrl-x对应的资源列表,并同时选择name指定的资源
// 相当于devm_pinctrl_get和pinctrl_select_state的组合
static inline struct pinctrl * __must_check devm_pinctrl_get_select(struct device *dev, const char *name)
// 从pinctrl资源中根据名称获取pinctrl配置信息
struct pinctrl_state *pinctrl_lookup_state(struct pinctrl *p, const char *name)
// 设置pinctrl配置信息
int pinctrl_select_state(struct pinctrl *p, struct pinctrl_state *state)
// 释放获取I/O引脚的pinctrl资源
void devm_pinctrl_put(struct pinctrl *p)
pinctrl主要工作在内核解析设备树的过程,来控制引脚的复用功能,因此主要修改的部分在设备树中。另外不同厂商在设备树中的引脚pinctrl定义有区别,这里以I.MX6ULL为例进行说明。设备树中以pinctrl的定义以iomux和iomuxc_snvs节点显示,这里实现支持pinctrl切换功能的设备树和应用接口。
iomuxc: pinctrl@20e0000 {
compatible = "fsl,imx6ul-iomuxc"; //属性标签,驱动匹配加载
reg = <0x020e0000 0x4000>; //定义iomuxc管理寄存器范围
};
&iomuxc {
pinctrl_led_improve: led-improve {
fsl,pins = <
MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x40017059 //定义引脚的复用类型和性能,详细见后续说明
>;
};
pinctrl_loopled: gpio-loopleds {
fsl,pins = <
MX6UL_PAD_CSI_DATA00__GPIO4_IO21 0x17059
MX6UL_PAD_CSI_DATA02__GPIO4_IO23 0x17059
MX6UL_PAD_CSI_DATA04__GPIO4_IO25 0x17059
>;
};
}
usr_led {
//.....
pinctrl-names = "default", "improve"; //用于pinctrl查找state的名称,默认是default
pinctrl-0 = <&pinctrl_gpio_led>; //第一组引脚复用功能指定的结构
pinctrl-1 =<&pinctrl_led_improve>; //第二组引脚复用功能指定的结构
};
/*
* The pin function ID is a tuple of
* <mux_reg conf_reg input_reg mux_mode input_val>
*/
#define MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x0068 0x02f4 0x0000 5 0
mux_reg:mux寄存器偏移地址,寄存器用于定义引脚的复用模式,寄存器地址(0x02290000+0x000C)
conf_reg:配置寄存器偏移地址,寄存器用于配置引脚的性能,寄存器地址(0x02290000+0x0050)
input_reg:input寄存器偏移地址,为0表示不存在
mux_mode:配置mux寄存器的值,0x5表示为引脚模式(详细看手册)
input_val:input寄存器不存在,配置0即可
/*
MX6ULL_PAD_SNVS_TAMPER1__GPIO5_IO01 <config_val>
*/
config_val:定义config寄存器的值
bit0: 引脚速度,0表示slow_field,1表示fast_field
bit3~5: 引脚驱动能力
- 000 DSE_0_output_driver_disabled
- 001 DSE_1_R0_260_Ohm___3_3V__150_Ohm_1_8V__240_Ohm_for_DDR
- 010 DSE_2_R0_2
- 011 DSE_2_R0_3
- 100 DSE_2_R0_4
- 101 DSE_2_R0_5
- 110 DSE_2_R0_6
- 111 DSE_2_R0_7
bit6-7:ready-only speed
bit-11: 开漏模式,0关闭开漏,1开漏模式
bit-12: Pull / Keep Enable Field,0表示关闭,1表示开启
bit-13: Pull / Keep Select Field,0表示Keeper,1表示Pull
bit14-15: Pull Up / Down Config. Field
- 00 PUS_0_100K_Ohm_Pull_Down — 100K Ohm Pull Down
- 01 PUS_1_47K_Ohm_Pull_Up — 47K Ohm Pull Up
- 10 PUS_2_100K_Ohm_Pull_Up — 100K Ohm Pull Up
- 11 PUS_3_22K_Ohm_Pull_Up — 22K Ohm Pull Up
bit16: Hyst. Enable Field,0:表示不开启I/O迟滞,1表示开启
bit17~bit31: Reserved
引脚的输入读取功能由SI_ON控制,
//driver/pinctrl/freescale/pinctrl-imx.c
//函数: imx_pinctrl_parse_pin_mmio
//#define IMX_PAD_SION 0x40000000
if (config & IMX_PAD_SION)
pin_mmio->mux_mode |= IOMUXC_CONFIG_SION;
pin_mmio->config = config & ~IMX_PAD_SION;
//根据I/O config的说明即可定义寄存器值,这部分主要影响设备功耗,I/O状态。
关于pinctrl的注册流程详细可看drivers/pinctrl/freescale/目录。这里还是从驱动应用的角度讲解说明。pinctrl的主要应用是在驱动中修改引脚的复用功能,详细接口说明如下。
struct pinctrl_state {
struct list_head node;
const char *name; //pinctrl的name解析,对应pinctrl-names中的单个字符串
struct list_head settings; //pinctrl的具体配置解析,对应pinctrl-0,...的配置
};
struct pinctrl {
struct list_head node; //pinctrl全局列表
struct device *dev; //使用pinctrl资源的设备节点指针
struct list_head states; //设备状态列表
struct pinctrl_state *state; //pinctrl的状态列表(解析设备树获得)
struct list_head dt_maps; //从设备树解析的设备表块
struct kref users; //pinctrl资源被引用的个数
};
以上面的设备树为例,支持pinctrl的例程如下所示。
/* 获取pinctrl结构 */
chip->led_pinctrl = devm_pinctrl_get(&pdev->dev);
/* 获取state结构 */
chip->pinctrl_state[0] = pinctrl_lookup_state(chip->led_pinctrl, "default");
chip->pinctrl_state[1] = pinctrl_lookup_state(chip->led_pinctrl, "improve");
/* 选择default对应的pinctrl */
pinctrl_select_state(chip->led_pinctrl, chip->pinctrl_state[0]);
/* 选择improve对应的pinctrl */
pinctrl_select_state(chip->led_pinctrl, chip->pinctrl_state[1]);
对于大部分驱动来说,pinctrl负责管理I/O的复用,如上下拉,驱动能力等,一般在初始化后基本不需要修改。而gpio则负责控制引脚的输入/输出,高低电平,是驱动主要操作的接口。内核从设备树中获取gpio资源的接口有两类,即gpio接口和gpiod接口。从功能上来说,gpio和gpiod都能实现配置引脚输入输出状态,并控制输入和获取引脚状态的功能,不过gpio属于Linux老版本的接口,gpiod则是引入的新的API,支持设备树中通过GPIO_ACTIVE来控制逻辑电平和实际电平(gpio接口在新版本中虽然仍存在,不过底层使用gpiod_set_raw_value,gpiod_get_raw_value实现,不受该配置影响),关于gpio的主要接口说明如下。
在实际开发中,建议使用gpiod相关接口进行i/o管理,这里列出相关接口,对于接口的详细说明见本章的附件章节。
//设备树格式
usr_led {
//......
led-gpios = <&gpio1 3 GPIO_ACTIVE_LOW>; //gpio资源
status = "okay";
};
//从内核中获取gpio资源,只获取第一个
struct gpio_desc *__must_check devm_gpiod_get(struct device *dev, const char *con_id, enum gpiod_flags flags)
/*
dev: 管理资源的设备,一般为总线匹配的设备
con_id: gpio属性id名称,"led"表示led-gpios属性,如果为NULL则表示gpios属性
flags: 设置GPIO的状态(请求并设置GPIO状态)
GPIOD_ASIS 默认状态
GPIOD_IN 输入状态
GPIOD_OUT_LOW 输出状态,默认输出为0
GPIOD_OUT_HIGH 输出状态,默认输出为1
GPIOD_OUT_LOW_OPEN_DRAIN 输出状态,开漏模式,默认输出0
GPIOD_OUT_HIGH_OPEN_DRAIN 输出状态,开漏模式,默认输出1
*/
//带编号的获取gpio资源描述符
struct gpio_desc *__must_check devm_gpiod_get_index(struct device *dev, const char *con_id, unsigned int idx, enum gpiod_flags flags)
//向内核获取gpiod资源描述符数组
struct gpio_descs *__must_check devm_gpiod_get_array(struct device *dev,
const char *con_id,
enum gpiod_flags flags)
在gpio模块中,以下结构需要重点理解。
//gpio device接口
struct gpio_device {
int id; //此GPIO设备所属gpiochip的编号,用于标识该GPIO设备在gpiochip中的位置
struct device dev; //指向该GPIO设备所属的通用设备结构体,关联了底层的硬件设
struct cdev chrdev; //该GPIO设备对应的字符设备,用于在用户空间和内核空间之间进行数据交互
struct device *mockdev; //指向该GPIO设备对应的模拟设备,可用于模拟GPIO功能,通常在测试或调试时使用。
struct module *owner; //指向拥有该GPIO设备的内核模块,表明哪个模块负责管理和操作这个GPIO设备。
struct gpio_chip *chip; //指向所属的gpiochip,如gpio1
struct gpio_desc *descs; //所属gpiochip管理的所有gpio描述符数组首指针
int base; //gpiochip的起始编号,即该gpiochip下第一个GPIO引脚的编号,用于计算其他引脚的编号。
u16 ngpio; //GPIO描述符数组指针内I/O数量,即该gpiochip所管理的GPIO引脚的总数
const char *label; //GPIO设备的描述名称,用于在日志或调试信息中标识该GPIO设备
void *data; //GPIO设备的私有数据指针,可用于存储与该GPIO设备相关的自定义数据
struct list_head list; //用于将该GPIO设备链接到GPIO设备链表中,方便内核统一管理和遍历所有GPIO设备。
struct blocking_notifier_head notifier; //阻塞通知头,用于注册和通知对该GPIO设备状态变化感兴趣的模块。
struct rw_semaphore sem; //读写信号量,用于保护对该GPIO设备的并发访问,确保数据的一致性
#ifdef CONFIG_PINCTRL
// 由pinctrl管理的引脚范围(仅在配置了PINCTRL时存在)
struct list_head pin_ranges;
#endif
};
//gpio_desc接口
struct gpio_desc {
struct gpio_device *gdev; //指向所属的gpio设备
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_IRQ_IS_ENABLED 10 /* GPIO is connected to an enabled IRQ */
#define FLAG_IS_HOGGED 11 /* GPIO is hogged */
#define FLAG_TRANSITORY 12 /* GPIO may lose value in sleep or reset */
#define FLAG_PULL_UP 13 /* GPIO has pull up enabled */
#define FLAG_PULL_DOWN 14 /* GPIO has pull down enabled */
#define FLAG_BIAS_DISABLE 15 /* GPIO has pull disabled */
#define FLAG_EDGE_RISING 16 /* GPIO CDEV detects rising edge events */
#define FLAG_EDGE_FALLING 17 /* GPIO CDEV detects falling edge events */
#define FLAG_EVENT_CLOCK_REALTIME 18 /* GPIO CDEV reports REALTIME timestamps in events */
#define FLAG_EVENT_CLOCK_HTE 19 /* GPIO CDEV reports hardware timestamps in events */
const char *label; //引脚的标签
const char *name; //gpio名称
#ifdef CONFIG_OF_DYNAMIC
// 所属的父设备节点
struct device_node *hog;
#endif
#ifdef CONFIG_GPIO_CDEV
// 去抖动时间(单位us)
unsigned int debounce_period_us;
#endif
};
控制引脚的输入输出状态。
//设置gpio为输出引脚,并定义初始状态
int gpiod_direction_output(struct gpio_desc *desc, int value)
//设置gpio为输入引脚
int gpiod_direction_input(struct gpio_desc *desc);
//设置gpio状态
void gpiod_set_value(struct gpio_desc *desc, int value)
//获取gpio引脚状态
int gpiod_get_value(const struct gpio_desc *desc)
对于gpio的编号计算方法如下。
对于gpio和gpiod,虽然从原理上一样,不过实际应用中接口有差异,这里展示两类不同的gpio使用例程。
//gpio接口
//1.从设备树中获取gpio编号
//2.从内核中申请gpio编号对应的资源
//3.设置gpio的方向
//4.使用set/get命令操作gpio
chip->gpio = of_get_named_gpio(beep_nd, "beep-gpios", 0);
devm_gpio_request(&pdev->dev, chip->gpio, "beep")
gpio_direction_output(chip->gpio, 1);
gpio_set_value(chip->gpio, 0);
//gpiod接口
//1.申请gpio资源并完成初始化
//2.使用set/get命令操作gpio
chip->led_desc = devm_gpiod_get(&pdev->dev, "led", GPIOD_OUT_LOW) //对应led-gpios属性
gpiod_set_value(chip->led_desc, 0);
在前面讲解了pinctrl框架和gpio子系统,也了解了gpio的知识,那么现在要开始以这些知识来定义基于设备树的LED驱动,可以知晓需要如下步骤。
下面基于这个步骤进行说明。
这里以正点原子的阿尔法开发板,使用引脚为GPIO1_3。设备树定义如下,其中pinctrl是固定格式,参考前面说明即可。对于gpio的设备树,其实定义也有对应思路,我可以说明下。
参考这个思路,LED的设备树的定义如下。
// 定义pinctrl的引脚复用
&iomuxc {
//....
pinctrl_gpio_led: gpio-leds {
fsl,pins = <
MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x17059
>;
};
};
//创建LED节点,在platform总线管理下
gpiosgrp {
compatible = "simple-bus";
//..
usr_led {
compatible = "rmk,usr-led";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_gpio_led>;
led-gpios = <&gpio1 3 GPIO_ACTIVE_LOW>;
status = "okay";
};
//......
};
至于驱动的实现以及应用层的访问,和上章节说明基本一致,这里不重复说明,详细参考: LED驱动文件,不过其中引入了复用功能切换,sysfs文件管理的相关内容。
对于LED来说,内核支持leds模块,可以直接控制输出。其实现代码路径:drivers/leds/led-gpios.c
内核需要支持LED相关的配置。
CONFIG_NEW_LEDS=y
CONFIG_LEDS_CLASS=y
CONFIG_GPIOLIB=y
CONFIG_LEDS_GPIO=y
具体路径为Device Driver > LED Support > LED Support for GPIO connected LEDs.
对于设备树的实现,则如下所示。
//访问路径: /sys/class/leds/led-red, /sys/class/leds/led-green, /sys/class/leds/led-blue
&iomuxc {
//...
pinctrl_loopled: gpio-loopleds { //引脚复用功能
fsl,pins = <
MX6UL_PAD_CSI_DATA00__GPIO4_IO21 0x17059
MX6UL_PAD_CSI_DATA02__GPIO4_IO23 0x17059
MX6UL_PAD_CSI_DATA04__GPIO4_IO25 0x17059
>;
};
};
leds-gpio {
compatible = "leds-gpio"; //标签,匹配驱动名称
pinctrl-names = "default"; //复用别名,默认配置
pinctrl-0 = <&pinctrl_loopled>; //指定引脚的复用功能
status = "okay"; //模块状态,功能开启
led-red { //led-red节点
label = "led-red";
gpios = <&gpio4 21 GPIO_ACTIVE_HIGH>;
default-state = "off";
};
led-green { //led-green节点
label = "led-green";
gpios = <&gpio4 23 GPIO_ACTIVE_HIGH>;
default-state = "off";
};
led-blue { //led-blue节点
label = "led-blue";
gpios = <&gpio4 25 GPIO_ACTIVE_HIGH>;
default-state = "off";
};
};
代码里面查看如下所示。
static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct fwnode_handle *child;
struct gpio_leds_priv *priv;
int count, ret;
count = device_get_child_node_count(dev);
if (!count)
return ERR_PTR(-ENODEV);
priv = devm_kzalloc(dev, struct_size(priv, leds, count), GFP_KERNEL);
if (!priv)
return ERR_PTR(-ENOMEM);
device_for_each_child_node(dev, child) {
struct gpio_led_data *led_dat = &priv->leds[priv->num_leds];
struct gpio_led led = {};
// 获取字节点的gpio数据
led.gpiod = devm_fwnode_get_gpiod_from_child(dev, NULL, child,
GPIOD_ASIS,
NULL);
if (IS_ERR(led.gpiod)) {
fwnode_handle_put(child);
return ERR_CAST(led.gpiod);
}
led_dat->gpiod = led.gpiod;
// 获取引脚的默认状态
led.default_state = led_init_default_state_get(child);
if (fwnode_property_present(child, "retain-state-suspended"))
led.retain_state_suspended = 1;
if (fwnode_property_present(child, "retain-state-shutdown"))
led.retain_state_shutdown = 1;
if (fwnode_property_present(child, "panic-indicator"))
led.panic_indicator = 1;
ret = create_gpio_led(&led, led_dat, dev, child, NULL);
if (ret < 0) {
fwnode_handle_put(child);
return ERR_PTR(ret);
}
/* Set gpiod label to match the corresponding LED name. */
gpiod_set_consumer_name(led_dat->gpiod,
led_dat->cdev.dev->kobj.name);
priv->num_leds++;
}
return priv;
}
// 获取引脚中的default-state默认状态
enum led_default_state led_init_default_state_get(struct fwnode_handle *fwnode)
{
const char *state = NULL;
if (!fwnode_property_read_string(fwnode, "default-state", &state)) {
if (!strcmp(state, "keep"))
return LEDS_DEFSTATE_KEEP;
if (!strcmp(state, "on"))
return LEDS_DEFSTATE_ON;
}
return LEDS_DEFSTATE_OFF;
}
在应用层,可以通过/sys/class/gpio/led-xxx/*进行处理。
# 打开LED-RED
echo 1 > /sys/class/leds/led-red/brightness
# 打开LED-GREEN
echo 1 > /sys/class/leds/led-green/brightness
# 打开LED-BLUE
echo 1 > /sys/class/leds/led-blue/brightness
对于内核来说,提供了一套用于调试gpio的接口,通过/sys/class/gpio访问。
注意:能够被导出的接口不能在设备树中访问,否则会现实busy而无法导出。
在/sys/class/gpio 中有3类入口:
控制接口是只写的:
/sys/class/gpio/
注意: gpio编号的计算方法为 (控制器编号*32) + 线的位置, 例如对于I.MX6ull, GPIO1对应的控制器编号为0,GPIO2为1,依次类推
GPIO 信号的路径类似 /sys/class/gpio/gpio42/ (对于 GPIO #42 来说),并有如下的读/写属性:
/sys/class/gpio/gpioN/
这里以导出GPIO2_10(编号42)为例,导出命令如下。
# 导出引脚
echo 42 > /sys/class/gpio/export
# 设置引脚方向
echo "in" > /sys/class/gpio/gpio42/direction
# 设置引脚外部触发模式
echo "rising" > /sys/class/gpio/gpio42/edge
# 取消引脚导出
echo 42 > /sys/class/gpio/unexport
GPIO 控制器的路径类似 /sys/class/gpio/gpiochip42/ (对于从#42 GPIO开始实现控制的控制器),并有着以下只读属性:
/sys/class/gpio/gpiochipN/
从内核中导出gpio引脚的接口。
/* 导出 GPIO 到用户空间 */
int gpio_export(unsigned gpio, bool direction_may_change);
/* gpio_export()的逆操作 */
void gpio_unexport(unsigned gpio);
/* 创建一个 sysfs 连接到已导出的 GPIO 节点 */
int gpio_export_link(struct device *dev, const char *name,
unsigned gpio)
在内核申请一个gpio驱动后,可以通过 gpio_export()使其在sysfs接口(sys class gpio)中可见,该驱动可以控制信号方向是否可修改。这有助于防止用户空间代码无意间破坏重要的系统状态。
直接开始下一章节说明: input输入子系统