概述
Linux驱动讲究驱动分离和分层,pinctrl和gpio子系统就是驱动分离和分层思想下的产物。驱动分离与分层是按照面向对象编程的设计思想设计的驱动框架。pinctrl和gpio子系统统一管理SOC引脚,避免了直接操作寄存器的驱动开发方式,提高了驱动的可移植性。
1.pinctrl子系统
大多数SOC的pin都支持引脚复用,需要配置复用功能,同时还要配置pin的电气特性等。传统配置pin的方式是直接操作寄存器,过程比较繁琐,而且容易出错。pinctrl子系统为了解决上述问题而引入,pinctrl子系统的主要工作内容如下:
(1)获取设备树中的pin信息
(2)根据获取的pin信息,设置pin的复用功能
(3)根据获取的pin信息,设置pin的电气特性
对于驱动的开发者,只需要在设备树中设置好某个pin的相关属性即可,剩余的初始化工作由pinctrl子系统完成。
1.1. imx6ull的pinctrl配置
要使用pinctrl子系统,需要在设备树里配置pin的信息,imx6ull.dtsi文件中有iomuxc的节点,此节点用于配置pin信息,imx6ull-14x14-evk.dts文件中引用了这个节点。向引用的节点添加子节点,以配置需要的pin信息。
// imx6ull.dtsi文件中pinctrl设置节点
iomuxc: iomuxc@020e0000 {
compatible = "fsl,imx6ul-iomuxc";
reg = <0x020e0000 0x4000>;
};
// imx6ull-14x14-evk.dt文件中引用了iomuxc节点,可以向iomuxc节点添加子节点
&iomuxc {
pinctrl-names = "default";
imx6ul-evk {
pinctrl_flexcan1: flexcan1grp{ // 添加的子节点
fsl,pins = < // 子节点用于配置具体的pin
//宏定义 配置引脚的电气属性
MX6UL_PAD_UART3_RTS_B__FLEXCAN1_RX 0x1b020
MX6UL_PAD_UART3_CTS_B__FLEXCAN1_TX 0x1b020
>;
};
pinctrl_i2c1: i2c1grp {
fsl,pins = <
MX6UL_PAD_UART4_TX_DATA__I2C1_SCL 0x4001b8b0
MX6UL_PAD_UART4_RX_DATA__I2C1_SDA 0x4001b8b0
>;
};
};
fsl,pins
属性中有两列,第一列为宏定义,具体定义在imx6ul-pinfunc.h文件中。
#define MX6UL_PAD_UART3_RTS_B__FLEXCAN1_RX 0x00B0 0x033C 0x0584 0x2 0x0
// <
// mux_reg 0x00B0 配置复用功能寄存器偏移地址
// conf_reg 0x033C 配置电气属性寄存器偏移地址
// input_reg 0x0584 输入选择寄存器的偏移地址,输出时为0
// mux_mode 0x2 复用模式配置位的值
// input_val 0x0 输入选择寄存器的值,输出时为0
// >
1.2. imx6ull的pinctrl驱动
pinctrl驱动程序根据设备树中设置的pinctrl信息初始化pin,驱动文件的路径为drivers/pinctrl/freescale/pinctrl-imx6ul.c
。imx6ul_pinctrl_of_match
存放了此驱动程序的兼容性,用于和设备树中pinctrl的compatible
属性匹配。匹配成功后执行imx6ul_pinctrl_probe
函数。
// 匹配字符串数组
static struct of_device_id imx6ul_pinctrl_of_match[] = {
{ .compatible = "fsl,imx6ul-iomuxc", .data = &imx6ul_pinctrl_info, },
{ .compatible = "fsl,imx6ull-iomuxc-snvs", .data = &imx6ull_snvs_pinctrl_info, },
{ /* sentinel */ }
};
imx6ul_pinctrl_probe()
->imx_pinctrl_probe()
// 向struct pinctrl_desc结构体中注册三个pinctrl操作函数集合
imx_pinctrl_desc->pctlops = &imx_pctrl_ops;
imx_pinctrl_desc->pmxops = &imx_pmx_ops;
imx_pinctrl_desc->confops = &imx_pinconf_ops;
->imx_pinctrl_probe_dt()
->imx_pinctrl_parse_functions()
->imx_pinctrl_parse_groups() // 提取fsl,pins属性并进行解析
->pinctrl_register() // 向内核注册pinctrl子系统
1.3. 使用pinctrl编写rgbled设备树节点
在imx6ull-14x14-evk.dts文件中引用的iomuxc节点下添加rgbled的pinctrl信息:
pinctrl_rgb_led:rgb_led {
fsl,pins = <
MX6UL_PAD_GPIO1_IO04__GPIO1_IO04 0x000010B1 /* read led */
MX6UL_PAD_CSI_HSYNC__GPIO4_IO20 0x000010B1 /* green led */
MX6UL_PAD_CSI_VSYNC__GPIO4_IO19 0x000010B1 /* blue led */
>;
};
在imx6ull-14x14-evk-emmc.dts文件中添加rgbled设备节点:
/{
/* 在根节点添加rgb led节点 */
rgb_led {
#address-cells = <1>;
#size-cells = <1>;
pinctrl-names = "default";
compatible = "my,rgb-led";
status = "okay";
pinctrl-0 = <&pinctrl_rgb_led>;
red = <&gpio1 4 GPIO_ACTIVE_LOW>;
green = <&gpio4 20 GPIO_ACTIVE_LOW>;
blue = <&gpio4 19 GPIO_ACTIVE_LOW>;
};
};
2.gpio子系统
gpio子系统用于初始化gpio,并提供操作gpio的API函数,比如设置gpio为输入或者输出,读取gpio数据寄存器的值等。gpio子系统主要目的是给驱动开发者提供便利,增加驱动的可移植性。开发者只要在设备树中添加gpio相关信息,然后在驱动程序中直接使用gpio子系统提供的API操作gpio即可。Linux内核屏蔽了gpio的操作过程,使驱动开发者专注于功能的实现,而不用关心gpio繁琐的操作过程。
gpio子系统提供的API操作函数都需要注册在struct gpio_chip
结构体中。
include <linux/gpio/driver.h>
struct gpio_chip {
const char *label;
struct device *dev;
struct module *owner;
struct list_head list;
int (*request)(struct gpio_chip *chip, unsigned offset);
void (*free)(struct gpio_chip *chip, unsigned offset);
int (*get_direction)(struct gpio_chip *chip,unsigned offset);
int (*direction_input)(struct gpio_chip *chip,unsigned offset);
int (*direction_output)(struct gpio_chip *chip,unsigned offset, int value);
int (*get)(struct gpio_chip *chip, unsigned offset);
void (*set)(struct gpio_chip *chip, unsigned offset, int value);
};
gpio子系统提供的通过gpio操作函数位于/drivers/gpio/gpiolib-legacy.c中。参数gpio
为要申请的gpio
标号,使用of_get_named_gpio
函数从设备树获取指定gpio
属性信息,此函数会返回这个gpio
标号。label
为给gpio
设置的名字。
include <linux/gpio.h>
// 申请一个GPIO引脚
int gpio_request(unsigned gpio, const char *label)
// 释放一个GPIO引脚
void gpio_free(unsigned gpio)
// 设置某个GPIO引脚为输入
int gpio_direction_input(unsigned gpio)
// 设置某个GPIO引脚为输出,并输出值设置为value
int gpio_direction_output(unsigned gpio, int value)
// 用于获取某个GPIO的值
int gpio_get_value(unsigned int gpio)
// 设置某个GPIO的值
void gpio_set_value(unsigned int gpio, int value)
of_get_named_gpio
函数获取GPIO编号,Linux内核中关于GPIO的API函数都要使用GPIO编号,此函数会将设备树中类似<&gpio5 7 GPIO_ACTIVE_LOW>
的属性信息转换为对应的GPIO编号,此函数在驱动中使用很频繁。np
为设备节点,propname
为要获取GPIO信息的属性名,index
GPIO索引,因为一个属性里面可能包含多个GPIO,此参数指定要获取哪个GPIO的编号,如果只有一个GPIO信息的话此参数为0。返回正值成功,返回负值失败。
int of_get_named_gpio(struct device_node *np, const char *propname, int index)
2.字符设备驱动源码
/*===========================my_led_pinctrl_gpio_subsystem.h================================*/
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/mutex.h>
#include <linux/atomic.h>
#include <asm/io.h>
#include <linux/of.h> // 设备树
// GPIO1_4 红灯
// GPIO4_20 绿灯
// GPIO4_19 蓝灯
#define LED_ON 0
#define LED_OFF 1
#define READ_PIN_NAME "read_pin"
#define GREEN_PIN_NAME "green_pin"
#define BLUE_PIN_NAME "blue_pin"
// 设备结构体
struct my_led_dev {
struct cdev cdev; // 字符设备结构体
struct device_node* node; // rgb_led设备树节点指针
int r_gpio; // 红色led引脚的GPIO编号
int g_gpio; // 绿色led引脚的GPIO编号
int b_gpio; // 蓝色led引脚的GPIO编号
char r_name[12]; // 红色led引脚的名字
char g_name[12]; // 绿色led引脚的名字
char b_name[12]; // 蓝色led引脚的名字
int r_status; // 红色led状态,0亮,1灭
int g_status; // 绿色led状态,0亮,1灭
int b_status; // 蓝色led状态,0亮,1灭
dev_t devno; // 设备号
struct class* my_led_class;
struct device* my_led_device;
struct mutex mutex; // 用于同步的互斥体
unsigned int r_on_cnt; // 红色led亮的次数
unsigned int g_on_cnt; // 红色led亮的次数
unsigned int b_on_cnt; // 红色led亮的次数
};
/*===========================my_led_pinctrl_gpio_subsystem.c================================*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <linux/slab.h>
#include <linux/stat.h>
#include <linux/sysfs.h>
#include <linux/string.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include "my_led_pinctrl_gpio_subsystem.h"
static struct my_led_dev* my_led = NULL;
static int my_led_open(struct inode* inode, struct file* filp)
{
struct my_led_dev* dev = container_of(inode->i_cdev,
struct my_led_dev, cdev);
filp->private_data = dev;
return 0;
}
// 设置led对应的gpio值
static void set_led_gpio(int gpio, int val, int* status,int* cnt)
{
// 非0-灯亮,0-灯灭,GPIO引脚输出低电平灯亮,输出高点平灯灭
if (val == 0) {
gpio_set_value(gpio, LED_OFF);
*status = LED_OFF;
}
else {
gpio_set_value(gpio, LED_ON);
if (*status != LED_ON) {
*cnt = *cnt + 1;
*status = LED_ON;
}
}
}
static ssize_t my_led_write(struct file* filp, const char __user* buf,
size_t size, loff_t* ppos)
{
struct my_led_dev* dev = filp->private_data;
int ret, val[3];
if (size != sizeof(val)) return -EINVAL;
// 成功返回0,失败返回失败的数目
ret = copy_from_user(&val, buf, sizeof(val));
if (0 != ret) return -EFAULT;
set_led_gpio(dev->r_gpio, val[0], &dev->r_status, &dev->r_on_cnt);
set_led_gpio(dev->g_gpio, val[1], &dev->g_status, &dev->g_on_cnt);
set_led_gpio(dev->b_gpio, val[2], &dev->b_status, &dev->b_on_cnt);
return sizeof(val);
}
// 文件操作函数结构体
static const struct file_operations my_led_fops = {
.owner = THIS_MODULE,
.write = my_led_write,
.open = my_led_open,
};
// 初始化和注册cdev结构体
static int set_up_my_led_cdev(struct my_led_dev* dev, int cnt)
{
int err;
cdev_init(&dev->cdev, &my_led_fops);
dev->cdev.owner = THIS_MODULE;
err = cdev_add(&dev->cdev, dev->devno, cnt); // 出错返回负值
if (err < 0)
printk(KERN_ERR "adding my_led cdev %d error, errno %d\n", cnt, err);
return err;
}
// 定义设备的属性
static ssize_t red_show(struct device* dev, struct device_attribute* attr,char* buf)
{
return sprintf(buf, "red led lighting times %u\n", my_led->r_on_cnt);
} // echo 非0,灯亮,echo 0,灯灭
static ssize_t red_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{ int err;
unsigned int val;
// 将echo输入得字符串转换为无符号整数,10表示十进制
err = kstrtouint(buf, 10, &val);
if (err)
return err;
set_led_gpio(my_led->r_gpio, val, &my_led->r_status, &my_led->r_on_cnt);
return count;
}
static ssize_t green_show(struct device *dev, struct device_attribute *attr,char *buf)
{
return sprintf(buf, "green led lighting times %u\n", my_led->g_on_cnt);
}
static ssize_t green_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{ int err;
unsigned int val;
err = kstrtouint(buf, 10, &val);
if (err)
return err;
set_led_gpio(my_led->g_gpio, val, &my_led->g_status, &my_led->g_on_cnt);
return count;
}
static ssize_t blue_show(struct device *dev, struct device_attribute *attr,char *buf)
{
return sprintf(buf, "blue led lighting times %u\n", my_led->b_on_cnt);
}
static ssize_t blue_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{ int err;
unsigned int val;
err = kstrtouint(buf, 10, &val);
if (err)
return err;
set_led_gpio(my_led->b_gpio, val, &my_led->b_status, &my_led->b_on_cnt);
return count;
}
// 红色led设备属性,生成的属性结构体名称为dev_attr_red,类型为struct device_attribute,
// mode为0644,所属者可以读写,其他只能读
static DEVICE_ATTR(red, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH, red_show, red_store);
// 绿色led设备属性,生成的属性结构体名称为dev_attr_green,类型为struct device_attribute,
// mode为0644,所属者可以读写,其他只能读
static DEVICE_ATTR(green, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH, green_show, green_store);
// 蓝色led设备属性,生成的属性结构体名称为dev_attr_blue,类型为struct device_attribute,
// mode为0644,所属者可以读写,其他只能读
static DEVICE_ATTR(blue, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH, blue_show, blue_store);
static struct attribute* led_colour_attrs[] = {
&dev_attr_red.attr,
&dev_attr_green.attr,
&dev_attr_blue.attr,
NULL,
};
/* 创建led颜色属性组,生成的属性结构体名称为led_colour_group,类型为static const
struct attribute_group,使用led_colour_attrs初始化
ATTRIBUTE_GROUPS(led_colour); */
// 使用ATTRIBUTE_GROUPS宏创建属性组时编译会出现警告,这里直接定义led颜色属性组
static struct attribute_group led_colour_group = {
.attrs = led_colour_attrs,
};
// 从设备树中提取信息,初始化gpio
static int led_gpio_init(struct my_led_dev* dev)
{
int ret = 0, ret1 = 0, ret2 = 0, ret3 = 0;
struct device_node* n;
const char* status;
// 设置led引脚的名字
memcpy(dev->r_name, READ_PIN_NAME, strlen(READ_PIN_NAME) + 1);
memcpy(dev->g_name, GREEN_PIN_NAME, strlen(GREEN_PIN_NAME) + 1);
memcpy(dev->b_name, BLUE_PIN_NAME, strlen(BLUE_PIN_NAME) + 1);
// 从设备树中获取设备节点
n = of_find_node_by_path("/rgb_led");
if (NULL == n) {
printk(KERN_ERR "find led node error by /rgb_led\n");
return -EINVAL;
}
dev->node = n;
// 根据设备树中的gpio属性,获取led对应的gpio编号
dev->r_gpio = of_get_named_gpio(dev->node, "red", 0);
if (dev->r_gpio < 0) {
printk(KERN_ERR "can't get %s gpio number\n", dev->r_name);
return -EINVAL;
}
printk(KERN_INFO "%s gpio number %#x\n", dev->r_name, (unsigned int)dev->r_gpio);
dev->g_gpio = of_get_named_gpio(dev->node, "green", 0);
if (dev->g_gpio < 0) {
printk(KERN_ERR "can't get %s gpio number\n", dev->g_name);
return -EINVAL;
}
printk(KERN_INFO "%s gpio number %#x\n", dev->g_name, (unsigned int)dev->g_gpio);
dev->b_gpio = of_get_named_gpio(dev->node, "blue", 0);
if (dev->b_gpio < 0) {
printk(KERN_ERR "can't get %s gpio number\n", dev->b_name);
return -EINVAL;
}
printk(KERN_INFO "%s gpio number %#x\n", dev->b_name, (unsigned int)dev->b_gpio);
// 申请gpio
ret = gpio_request(dev->r_gpio, dev->r_name);
if (ret < 0) {
printk(KERN_ERR "request %s gpio error\n", dev->r_name);
return -EINVAL;
}
ret = gpio_request(dev->g_gpio, dev->g_name);
if (ret < 0) {
printk(KERN_ERR "request %s gpio error\n", dev->g_name);
ret = -EINVAL;
goto free_read_gpio;
}
ret = gpio_request(dev->b_gpio, dev->b_name);
if (ret < 0) {
printk(KERN_ERR "request %s gpio error\n", dev->b_name);
ret = -EINVAL;
goto free_green_gpio;
}
// 从设备树中获取status属性
ret = of_property_read_string(dev->node, "status", &status);
if (ret < 0) {
printk(KERN_ERR "find rgb_led node status property error\n");
ret = -EINVAL;
goto free_blue_gpio;
}
// 设置GPIO为输入,并根据status属性设置输出值
ret = strcmp(status, "okay");
if (ret == 0) {
ret1 = gpio_direction_output(dev->r_gpio, LED_ON);
dev->r_status = LED_ON;
ret2 = gpio_direction_output(dev->g_gpio, LED_ON);
dev->g_status = LED_ON;
ret3 = gpio_direction_output(dev->b_gpio, LED_ON);
dev->b_status = LED_ON;
} else {
ret1 = gpio_direction_output(dev->r_gpio, LED_OFF);
dev->r_status = LED_OFF;
ret2 = gpio_direction_output(dev->g_gpio, LED_OFF);
dev->g_status = LED_OFF;
ret3 = gpio_direction_output(dev->b_gpio, LED_OFF);
dev->b_status = LED_OFF;
}
if (ret1 < 0 || ret2 < 0 || ret3 < 0) {
printk(KERN_ERR "gpio set direction output error\n");
ret = -EINVAL;
goto free_blue_gpio;
}
return 0;
free_blue_gpio:
gpio_free(dev->b_gpio);
free_green_gpio:
gpio_free(dev->g_gpio);
free_read_gpio:
gpio_free(dev->r_gpio);
return ret;
}
// 模块初始化
static int __init my_led_init(void)
{
int ret = 0;
dev_t devno = 0;
// 动态分配设备号,传入的devno参数为0,使用unregister_chrdev_region注销动态分配的设备号
ret = alloc_chrdev_region(&devno, 0, 1, "my_led");
if (ret < 0) {
printk(KERN_ERR "alloc_chrdev_region() failed %d\n", ret);
return ret;
}
// 分配设备结构体内存并将分配的内存清0
my_led = kzalloc(sizeof(struct my_led_dev), GFP_KERNEL);
if (NULL == my_led) {
ret = -ENOMEM;
printk(KERN_ERR "kzalloc() failed %d\n", ret);
goto unreg_chrdev;
}
my_led->devno = devno;
// 从设备树获取资源
ret = led_gpio_init(my_led);
if (0 != ret) goto free_dev;
ret = set_up_my_led_cdev(my_led, 1);
if (ret < 0) goto free_dev;
// 创建类和设备,当模块加载后会自动在/dev目录下生成设备节点
my_led->my_led_class = class_create(THIS_MODULE, "my_led_class");
if (IS_ERR(my_led->my_led_class)) {
ret = PTR_ERR(my_led->my_led_class);
printk(KERN_ERR "class_create() failed %d\n", ret);
goto del_cdev;
}
my_led->my_led_device = device_create(my_led->my_led_class, NULL,
devno, NULL, "my_led");
if (IS_ERR(my_led->my_led_device)) {
ret = PTR_ERR(my_led->my_led_device);
printk(KERN_ERR "device_create() failed %d\n", ret);
goto clean_class;
}
// 使用sysfs_create_group可以创建一组属性文件,sysfs_remove_group移除一组属性文件
// 使用sysfs_create_groups可以创建多组属性文件,ysfs_remove_groups移除多组属性文件
ret = sysfs_create_group(&my_led->my_led_device->kobj, &led_colour_group);
if(ret != 0) goto clean_device;
// 初始化互斥体
mutex_init(&my_led->mutex);
printk(KERN_INFO "my_led module init OK, major %u, minor %u\n",
MAJOR(devno), MINOR(devno));
return 0;
clean_device:
device_destroy(my_led->my_led_class, devno);
clean_class:
class_destroy(my_led->my_led_class);
del_cdev:
cdev_del(&my_led->cdev);
free_dev:
kfree(my_led);
my_led = NULL;
unreg_chrdev:
unregister_chrdev_region(devno, 1);
return ret;
}
// 模块注销
static void __exit my_led_exit(void)
{
sysfs_remove_group(&my_led->my_led_device->kobj, &led_colour_group);
device_destroy(my_led->my_led_class, my_led->devno);
class_destroy(my_led->my_led_class);
cdev_del(&my_led->cdev);
gpio_free(my_led->b_gpio); // 释放gpio
gpio_free(my_led->g_gpio);
gpio_free(my_led->r_gpio);
unregister_chrdev_region(my_led->devno, 1);
kfree(my_led);
my_led = NULL;
printk(KERN_INFO "my_led module exit\n");
}
module_init(my_led_init);
module_exit(my_led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("liyang.plus@foxmail.com");
MODULE_VERSION("v1.00");
3.测试程序源码
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <fcntl.h>
#define PATH "/dev/my_led"
#define OPT_STRING ":r:g:b:" // 选项字符串
void print_usage()
{
printf("Usage: ./test -r <on/off> -g <on/off> -b <on/off>\n"
"-r: Control red led\n"
"-g: Control green led\n"
"-b: Control blue led\n"
"on: Light on(default)\n"
"off: Light off\n");
}
extern char* optarg; // 指向参数值
extern int optind; // 记录getopt函数处理argv[]数组的位置,一般不需要设置
extern int opterr; // 保存出错信息,非0 getopt会向stderr打印出错信息,如为0时则不打印
/* 遇到无法识别的选项,getopt返回?号,并将?存储到optopt中,如果将选项字符串第一个字符设置
为:号,那么getopt函数在用户未提供值的情况下返回:号而不是?号 */
extern int optopt;
int parse_option(int argc, char* argv[], int* rgb)
{
int opt;
while (-1 != (opt = getopt(argc, argv, OPT_STRING))) {
switch (opt) {
case 'r':
if(0 == strcmp(optarg, "on")) rgb[0] = 1;
else if (0 == strcmp(optarg, "off")) rgb[0] = 0;
else return -1;
break;
case 'g':
if(0 == strcmp(optarg, "on")) rgb[1] = 1;
else if (0 == strcmp(optarg, "off")) rgb[1] = 0;
else return -1;
break;
case 'b':
if(0 == strcmp(optarg, "on")) rgb[2] = 1;
else if (0 == strcmp(optarg, "off")) rgb[2] = 0;
else return -1;
break;
case ':':
print_usage();
return -1;
break;
case '?':
print_usage();
return -1;
break;
default:
print_usage();
return -1;
break;
}
}
return 0;
}
int main(int argc, char* argv[])
{
int fd, ret, val[3] = {0};
if (7 != argc) {
print_usage();
return -1;
}
ret = parse_option(argc, argv, val);
if (0 != ret) {
print_usage();
printf("parse option error\n");
return -1;
}
fd = open(PATH, O_RDWR);
if (fd < 0){
printf("my_led open error\n");
return -1;
}
ret = write(fd, &val, sizeof(val));
if (ret < 0) {
printf("write error\n");
close(fd);
return -1;
}
close(fd);
return 0;
}