Linux学习_Pinctrl子系统与GPIO子系统(基础版)
为啥要学这俩子系统呢,就是方便,用的时候谁一个个配寄存器啊跟怨种似的,就用pinctrl或者gpio这俩子系统带的函数,直接便捷使用了
Pinctrl子系统
原本我们写代码时,不论是直接写也好,总线模式也好,都是手写调用IOMUX对引脚进行配置,将PIN配置为GPIO、I2C等等,一个一个搞这些引脚其实还是比较麻烦的,所以BSP工程师们把其改成用软件来操作,制作成Pinctrl子系统。
在设备树中,想使用上pinctrl很简单,在设备属性中加入pinctrl-names,再用pinctrl-n来代表第n个状态由尖括号中的子节点(们)来设置,并在pincontroller中添加对应的子节点就OK了,节点的具体配置方法如下图:
右边device这边配置是非常标准的,就是这样的规范化书写,没啥可说的,然而。。。左边的pincontroller部分,写法五花八门,各家都不一样,不总结了就。
注意:图中的pincontroller和device都是代词,表示这个意思而已,不是实际命名,实际应用中根据芯片和设备的不同可能命名为各种具体的名字。
用imx6ull举个例子吧,pincontroller写成这德行了:
ABC__xxx 0xnnnnn,这个格式表示的意思是,ABC引脚,复用为xxx功能,配置参数为0xnnnnn,大概率是有个宏吧能这样定义的。
GPIO子系统
还是搬出来本文第一个图:
是不是有点怪了,哈哈。都有了 pinctrl 子系统了,还整GPIO子系统干啥,因为很多辣鸡芯片没有IOMUX,引脚的复用和配置就是在GPIO模块内部实现的,所以这玩意也得学。
总的来说要操作一个 GPIO 引脚,先用 Pinctrl 把引脚配置为 GPIO ,然后用 GPIO 带的函数具体设置,方便的一b。
在 GPIO 子系统下,我们可以:
- 在设备树里指定 GPIO 引脚
- 在驱动代码中:使用 GPIO 子系统的标准函数获得 GPIO、设置 GPIO 方向、读取/设置 GPIO 值
那么,一个一个说。
在设备树里指定 GPIO 引脚
还记得之前“点亮一个LED时”用的啥 GPIO 吗,是不是 GPIO5_3 ,这种写法就是ARM芯片中 GPIO 的典型写法,代表了第五组第三个。
在设备树中,组体现为gpio子节点,如imx6ull.dtsi中:
这里面的gpio1就表示第一组gpio,gpio2就表示第二组,(感觉在说废话。。。)只需要关注其中两个属性:
gpio-controller;
:代表表示这个节点是一个 GPIO Controller,也就是代表是一组GPIO,它下面有很多引脚#gpio-cells=<2>;
:表示这个控制器下每一个引脚要用 2 个 32 位的数(cell)来描述
一般来说,第一个cell指定具体引脚,第二个cell表示有效电平,在自己写的设备树.dts文件中,可以使用gpios
或name-gpios
来引用引脚,赋值时遵循name-gpios = <gpio组名 cell cell ....>
的格式,如下所示:
在驱动代码中获得、设置、读写GPIO引脚
首先是包含头文件,其中legacy系列比较老了,推荐使用d-b系列
#include <linux/gpio/consumer.h> // descriptor-based
或者
#include <linux/gpio.h> // legacy
常用的函数有这么一大坨,推荐使用devm系列的,因为它具有自动释放机制,在设备不存在时资源就会自动释放,比较方便
举个栗子:
设备树中有这么个玩意:
foo_device {
compatible = "acme,foo";
...
led-gpios = <&gpio 15 GPIO_ACTIVE_HIGH>, /* red */
<&gpio 16 GPIO_ACTIVE_HIGH>, /* green */
<&gpio 17 GPIO_ACTIVE_HIGH>; /* blue */
power-gpios = <&gpio 1 GPIO_ACTIVE_LOW>;
};
那么驱动里面可以这样获得引脚:gpiod_get_index(dev,“name”-gpios,组内第几个,状态设置)
struct gpio_desc *red, *green, *blue, *power;
red = gpiod_get_index(dev, "led", 0, GPIOD_OUT_HIGH); //led-gpios中的第0个,设置为输出、高有效
green = gpiod_get_index(dev, "led", 1, GPIOD_OUT_HIGH); //led-gpios中的第1个,设置为输出、高有效
blue = gpiod_get_index(dev, "led", 2, GPIOD_OUT_HIGH); //led-gpios中的第2个,设置为输出、高有效
power = gpiod_get(dev, "power", GPIOD_OUT_HIGH); //power-gpios就这一个直接get,设置为输出、高有效
注意:这里面的高有效低有效是逻辑高低,不是实际电压高低,有的设备树很烦就指定低电压为high,高电压为low,可能是叛逆吧。
GPIO子系统LED驱动
大致思路:
- 设备树节点会被内核转换为 platform_device。
- 对应的,驱动代码中要注册一个 platform_driver,
- 在 probe 函数中:获得引脚、注册 file_operations。
- 在 file_operations 中:设置方向、读值/写值。
其中,设备树节点这些有NXP官方给的设备树工具,设置生成挺好用,要改的就pincontroller和根节点下新加个子节点。
所以从2开始是我们要写在驱动里的,都写在驱动代码里:
与设备树配对,注册platform_driver
加一个id表:
static const struct of_device_id ask100_leds[] = {
{ .compatible = "100ask,leddrv" },
{ },
};
塞到platform_driver里,跟设备树配对用:
static struct platform_driver chip_demo_gpio_driver = {
.probe = chip_demo_gpio_probe,
.remove = chip_demo_gpio_remove,
.driver = {
.name = "100ask_led",
.of_match_table = ask100_leds,
},
};
注册platform_driver:
static int __init led_init(void){
int err;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
err = platform_driver_register(&chip_demo_gpio_driver);//主要是这句register
return err;
}
在probe里get GPIO
在probe里面 1. 用gpiod_get获得GPIO,2. 注册file_operations结构体:
static struct gpio_desc *led_gpio;
static int chip_demo_gpio_probe(struct platform_device *pdev){
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
/* 4.1 设备树中定义有: led-gpios=<...>; */
led_gpio = gpiod_get(&pdev->dev, "led", 0);
/* 4.2 注册file_operations */
major = register_chrdev(0, "100ask_led", &led_drv); /* /dev/led */
led_class = class_create(THIS_MODULE, "100ask_led_class");
device_create(led_class, NULL, MKDEV(major, 0), NULL, "100ask_led%d", 0); /* /dev/100ask_led0 */
return 0;
}
在open里设置GPIO为output,gpiod_direction_output( )
static int led_drv_open (struct inode *node, struct file *file){
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
/* 根据次设备号初始化LED */
gpiod_direction_output(led_gpio, 0);
return 0;
}
在write里设置具体引脚值,gpiod_set_value( )
static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
int err;
char status;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
/* 根据次设备号和status控制LED */
err = copy_from_user(&status, buf, 1);
gpiod_set_value(led_gpio, status);
return 1;
}
实现解除注册和占用的remove函数
static int chip_demo_gpio_remove(struct platform_device *pdev){
device_destroy(led_class, MKDEV(major, 0));
class_destroy(led_class);
unregister_chrdev(major, "100ask_led");
gpiod_put(led_gpio);
return 0;
}