pinctrl 子系统主要工作内容如下:
①、获取设备树中 pin 信息。
②、根据获取到的 pin 信息来设置 pin 的复用功能
③、根据获取到的 pin 信息来设置 pin 的电气特性,比如上/下拉、速度、驱动能力等。
对于我们使用者来讲,只需要在设备树里面设置好某个 pin 的相关属性即可,其他的初始
化工作均由 pinctrl 子系统来完成,pinctrl 子系统源码目录为 drivers/pinctrl。
首先在100ask_imx6ull-14x14.dts设备树文件中要添加PIN配置信息,毕竟 pinctrl 子系统要根据你提供的信息来配置 PIN 功能,一般会在设备树里面创建一个iomuxc 节点来描述 PIN 的配置信息。
iomuxc 节点就是 IMX6ULL 的 IOMUXC 外设对应的节点
在iomuxc下的imx6ull-evk中插入pinctrl的配置
pinctrl_led: ledgrp {
fsl,pins = <
MX6ULL_PAD_SNVS_TAMPER3__GPIO5_IO03 0x000110A0
>;
}
主要格式就是以上的代码fsl,pins =<>;的两个参数是 PIN 的配置信息
一个参数是宏定义保存的寄存器地址
图中宏定义对应的5个参数为
0x0014:mux_reg 寄存器偏移地址
0x0058:conf_reg 寄存器偏移地址
0x0000:input_reg 寄存器偏移地址
5:mux_reg 寄存器值
0:input_reg 寄存器值,在这里无效。
回到配置的另一个参数0x000110A0,就是conf_reg寄存器的值,用来设置IO 的上/下拉、驱动能力和速度等,IOMUXC_SNVS_SW_PAD_CTL_PAD_SNVS_TAMPER3的PAD寄存器,其地址为0X2290058H。这也是个32位寄存器,但是只用到了其中的低17位
如果 pinctrl 子系统将一个 PIN 复用为 GPIO 的话,那么接下来就要用到 gpio 子系统了。gpio 子系统顾名思义,就是用于初始化 GPIO 并且提供相应的API 函数,比如设置 GPIO为输入输出,读取 GPIO 的值等。gpio 子系统的主要目的就是方便驱动开发者使用 gpio,驱动开发者在设备树中添加 gpio 相关信息,然后就可以在驱动程序中使用 gpio 子系统提供的 API函数来操作 GPIO,Linux 内核向驱动开发者屏蔽掉了 GPIO 的设置过程,极大的方便了驱动开发者使用 GPIO。
在根节点“/”下创建 LED 灯节点,节点名为“gpioled”,节点内容如下:
gpioled {
#address-cells = <1>;
#size-cells = <1>;
compatible = "louis-gpioled";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_led>;
led-gpio = <&gpio5 3 GPIO_ACTIVE_LOW>;
};
pinctrl-names 属性,此属性描述 pinctrl 名字为“default”。
pinctrl-0 节点,此节点引用 45.1.3 中创建的 pinctrl_led节点,表示 led 设备的所使用的 PIN 信息保存在 pinctrl_led 节点中
led-gpio表示最后需要在 test 节点中添加 GPIO 属性信息,表明 test 所使用的 GPIO 是哪个引脚和电平有效性
为什么要进行PIN的查重呢?
比如 A 这个引脚在官方开发板接的是 I2C 的 SDA,而我们所使用的硬件可能将 A 这个引脚接到了其他的外设,比如 LED 灯上,接不同的外设,A 这个引脚的配置就不同。一个引脚一次只能实现一个功能,如果 A 引脚在设备树中配置为了 I2C 的 SDA 信号,那么 A 引脚就不能再配置为 GPIO,否则的话驱动程序在申请 GPIO 的时候就会失败。
检查 PIN 有没有被其他外设使用包括两个方面:
①、检查 pinctrl 设置。
②、如果这个 PIN 配置为 GPIO 的话,检查这个 GPIO 有没有被别的外设使用。
在设备树里搜索MX6ULL_PAD_SNVS_TAMPER3__GPIO5_IO03发现在iomuxc_snvs中有一个pinctrl_leds节点这时候要把<>中的内容用/**/注释掉
驱动的编写和上一篇文章的编写相比略有改动,改动如下
1.取消了iomap映射寄存器虚拟地址
2.取消了led_switch函数直接对寄存器进行控制读写
3.取消了初始化中从设备树获取compatible、status、reg属性内容
4.新增gpioled_dev结构体中GPIO编号属性
struct gpioled_dev{
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
struct device_node *nd; /* 设备节点 */
int led_gpio; /* led所使用的GPIO编号 */
};
struct gpioled_dev gpioled; /* led设备 */
5.新增初始化中of_get_named_gpio获取GPIO编号
gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0);
6.新增设备操作函数write中通过读取 filp 的 private_data 成员变量来得到设备结构体变量,也就是 gpioled。这种将设备结构体设置为 filp 私有数据的方法在 Linux 内核驱动里面非常常见
struct gpioled_dev *dev = filp->private_data;
retvalue = copy_from_user(databuf, buf, cnt);
if(retvalue < 0) {
printk("kernel write failed!\r\n");
return -EFAULT;
}
ledstat = databuf[0]; /* 获取状态值 */
if(ledstat == LEDON) {
gpio_set_value(dev->led_gpio, 0); /* 打开LED灯 */
} else if(ledstat == LEDOFF) {
gpio_set_value(dev->led_gpio, 1); /* 关闭LED灯 */
}
7.新增gpio_direction_output设置gpio的输出电平
ret = gpio_direction_output(gpioled.led_gpio, 1);