上一篇博客中,已经实现了设备树的添加
【IMX6ULL驱动开发学习】12.Linux驱动之设备树
这篇博客介绍Pinctrl子系统与GPIO子系统的使用
Pinctrl子系统参考文档:
内核文档链接:https://www.kernel.org/doc/Documentation/
内核源码doc:Documentation/devicetree/bindings/pinctrl/pinctrl-bindings.txt
GPIO子系统参考文档:
内核文档链接:https://www.kernel.org/doc/Documentation/
内核源码doc:Documentation/devicetree/bindings/gpio/各个芯片厂商文件
代码自取【13.led_button_drv_tree_gpio_pinctrl】:
https://gitee.com/chenshao777/imx6-ull_-drivers
为什么要用Pinctrl子系统与GPIO子系统?
驱动开发中,我们可以使用寄存器对外设进行操作,但是那样太麻烦了
所以引入了 Pinctrl子系统与GPIO子系统
Pinctrl子系统: 可以用它来复用引脚、配置引脚的电气属性等等。
GPIO子系统: 控制引脚,设置输入输出。
之前在设备树中定义硬件资源的方法
(1)设备树节点中自定义 pin 属性
(2)在驱动代码中通过 of_property_read_u32 函数读取属性值,得到具体引脚
(3)根据GPIO组和pin对寄存器地址进行映射 (ioremap)
弊端: 还是需要在驱动代码中进行寄存器的操作,进行地址映射操作 ( ioremap )
引入Pinctrl子系统和GPIO子系统
总结图片,一览无遗
1、Pinctrl子系统
Pinctrl子系统是负责引脚的复用,和属性定义
可以认为它对应IOMUX──用来复用引脚,还可以配置引脚(比如上下拉电阻等)
//client端:
@节点名字 {
pinctrl-names = "default, sleep"; // 定义有几个状态
pinctrl-0 = <&pinctrl_自定义名字A>; // 第一个状态对应的引脚属性
pinctrl-1 = <&pinctrl_自定义名字B>; // 第二个状态对应的引脚属性
xxx-gpio = <&gpiox n flag>; //
status = "okay";
};
//pincontroller服务端
pinctrl_自定义名字A: 自定义名字 {
fsl,pins = <
引脚复用宏定义 PAD(引脚)属性, // 引脚 A
>;
};
pinctrl_自定义名字B: 自定义名字 {
fsl,pins = <
引脚复用宏定义 PAD(引脚)属性; // 引脚 B
>;
};
PS:
一个引脚可以有多种状态 ,例如可以配置成串口模式,睡眠的时候为了省点配置成GPIO模式
2、添加gpio属性,指定引脚和有效状态
@节点名字 {
pinctrl-names = "default, sleep"; // 定义有几个状态
pinctrl-0 = <&pinctrl_自定义名字A>; // 第一个状态对应的引脚属性
led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>; //指定GPIO1_3引脚,模式低电平有效
status = "okay";
};
使用实例:
设备节点
pinctrl端
提问:
为什么设备节点中 led-gpio 后面是<&gpio1 3 flag>
&gpio1 是 组
为什么后面要加上两个位呢?这是因为在 imx6ull.dtsi 文件中定义了 gpio1节点,且定义了 pinctrl ,其中 #gpio-cells = <2>; ,这个属性就表明了,GPIO组后面要跟两个参数
提问:
pinctrl端 “MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0” 是怎么得来的?
想要获得IMX6ULL 的pinctrl 代码,可以使用官方提供的工具Pins_Tool_for_i.MX_Processors_v6_x64 来获取,下载链接,使用方式自行百度吧嘿嘿
这个 0x10B0 其实就是 参考手册中 IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03 寄存器设置的值
MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 是一个宏,在 imx6ul-pinfunc.h 文件中定义
3、GPIO子系统API使用
推荐使用新的一套GPIO子系统
(1)获得GPIO (gpiod_get)
struct gpio_desc *my_dev_gpio;
........
//从设备树中获取GPIO引脚,并设置成默认输出模式,无效(LOW表示无效逻辑)
my_dev_gpio = gpiod_get(dev, "led", GPIOD_OUT_LOW);
第二个参数对应设备树中 xxx-gpio 中的 xxx
led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
(2)【可选】获取自定义设备名称属性(of_property_read_string)
char a[20];
const char *str = a;
of_property_read_string(np, "my_name", &str);
(3)创建设备节点(device_create)
//创建设备节点 /dev/xxx
device_create(my_dev_class, NULL, MKDEV(major, 0), NULL, str);
(4)写GPIO(gpiod_set_value)
gpiod_set_value(my_dev_gpio, status);
(5)释放GPIO,销毁设备节点
//释放GPIO
gpiod_put(my_dev_gpio);
//销毁设备
device_destroy(my_dev_class, MKDEV(major, 0));
全部代码:
我将led和beep都加进来了,所以打开设备节点、销毁节点时要区分是哪一个,所以将下面这两个该成了数组,通过设备树中自定义的 my_name 属性区分设备
char dev_names[10][20]={}; //保存设备树中的名字
struct gpio_desc *my_dev_gpio[10];
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/gpio.h>
#include <linux/uaccess.h>
#include <linux/string.h>
int major; //设备号
static struct class *my_dev_class;
int dev_cnt;
char dev_names[10][20]={}; //保存设备树中的名字
struct gpio_desc *my_dev_gpio[10];
/*=============================file_operations ==============================*/
static ssize_t my_drv_read (struct file *filp, char __user *buf, size_t size, loff_t *offset)
{
printk("drv_read function run....\n");
return 1;
}
static ssize_t my_drv_write (struct file *filp, const char __user *buf, size_t size, loff_t *offset)
{
struct inode *inode = file_inode(filp);
int minor = iminor(inode);
char status;
int err;
err = copy_from_user(&status, buf, 1);
gpiod_set_value(my_dev_gpio[minor], status);
printk("drv_write function run....\n");
return 0;
}
static int my_drv_open (struct inode *node, struct file *filp)
{
struct inode *inode = file_inode(filp);
int minor = iminor(inode);
printk("drv_open function run....\n");
gpiod_set_value(my_dev_gpio[minor], 0);
return 0;
}
static int my_drv_release (struct inode *node, struct file *filp)
{
printk("drv_release function run....\n");
return 0;
}
/* operations结构体:为应用层提供驱动接口 */
static struct file_operations my_dev_ops = {
.owner = THIS_MODULE,
.read = my_drv_read,
.write = my_drv_write,
.open = my_drv_open,
.release = my_drv_release,
};
/*=============================platform_driver==============================*/
/* 如果匹配到了内核根据设备树生成的platform_device,
该函数会被调用,如果有多个匹配的设备节点,该函数
会被多次调用
*/
static int my_probe(struct platform_device *pdev)
{
/* 从内核根据设备树生成的 platform_device
结构体中获取到设备节点
*/
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
int gpio_pin;
char a[20];
const char *str = a;
of_property_read_string(np, "my_name", &str);
//保存设备的名字
strcpy(dev_names[dev_cnt], str);
//从设备树中获取GPIO引脚,并设置成默认输出模式,无效(LOW表示无效逻辑)
my_dev_gpio[dev_cnt] = gpiod_get(dev, str, GPIOD_OUT_LOW);
//从struct desc结构体转成GPIO子系统标号
gpio_pin = desc_to_gpio(my_dev_gpio[dev_cnt]);
//创建设备节点 /dev/xxx
device_create(my_dev_class, NULL, MKDEV(major, dev_cnt), NULL, str);
dev_cnt++;
printk("my_probe run, my_name = %s\n", str);
return 0;
}
static int my_remove(struct platform_device *pdev)
{
/* 从内核根据设备树生成的 platform_device
结构体中获取到设备节点
*/
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
int gpio_pin, i;
char a[20];
const char *str = a;
int flag = 0;
of_property_read_string(np, "my_name", &str);
for(i = 0; i < dev_cnt; i++){
if(strcmp(dev_names[i],str) == 0){
strcpy(dev_names[i], "");
gpio_pin = desc_to_gpio(my_dev_gpio[i]);
//释放GPIO
gpiod_put(my_dev_gpio[i]);
//销毁设备
device_destroy(my_dev_class, MKDEV(major, i));
printk("my_remove run, device_destroy %s, my_gpio = %d\n", str, gpio_pin);
}
if(dev_names[i])
flag = 1;
}
if(flag == 0){
dev_cnt = 0;
printk("all removed\n");
}
return 0;
}
static struct of_device_id my_dev_match[] = {
{.compatible = "hc-led-beep"},
{.compatible = "hc-led-beep"},
// {.compatible = "hc-key"},
{},
};
static struct platform_driver dev_driver = {
.probe = my_probe,
.remove = my_remove,
.driver = {
.name = "my_platform_driver",
.of_match_table = my_dev_match,
},
};
/*=============================驱动出入口函数==============================*/
/* 驱动入口函数:insmod xx.ko 时会被调用 */
static int dev_init(void)
{
major = register_chrdev(0, "hc_dev_drv", &my_dev_ops);
if(major < 0){
printk("register_chrdev famy\n");
return major;
}
my_dev_class = class_create(THIS_MODULE, "my_dev_class");
if(IS_ERR(my_dev_class)){
printk("class_create failed\n");
return 1;
}
platform_driver_register(&dev_driver);
return 0;
}
/* 驱动出口函数: rmmod xx.ko 时会被调用 */
static void dev_exit(void)
{
platform_driver_unregister(&dev_driver);
class_destroy(my_dev_class);
unregister_chrdev(major, "hc_dev_drv");
printk("my_dev driver exit\n");
}
module_init(dev_init);
module_exit(dev_exit);
MODULE_LICENSE("GPL");