Linux 驱动讲究驱动分离与分层,pinctrl 和 gpio 子系统就是驱动分离与分层思想下的产物,将驱动与底层IO电气属性配置分开。
对于使用者来说,只需要在设备树中定义好某个pin相关属性即可,其他的初始化工作均由pinctrl子系统完成。
使用pinctrl子系统时我们需要修改的只有pin配置信息。例如:
iomuxc: iomuxc@020e0000 {
compatible = "fsl,imx6ul-iomuxc";
reg = <0x020e0000 0x4000>;
pinctrl-names = "default";
imx6ul-evk {
pinctrl_gpmi_nand: gpmi-nand {
fsl,pins = <
MX6UL_PAD_NAND_CLE__RAWNAND_CLE 0xb0b1
MX6UL_PAD_NAND_ALE__RAWNAND_ALE 0xb0b1
MX6UL_PAD_NAND_WP_B__RAWNAND_WP_B 0xb0b1
MX6UL_PAD_NAND_READY_B__RAWNAND_READY_B 0xb000
MX6UL_PAD_NAND_CE0_B__RAWNAND_CE0_B 0xb0b1
MX6UL_PAD_NAND_CE1_B__RAWNAND_CE1_B 0xb0b1
MX6UL_PAD_NAND_RE_B__RAWNAND_RE_B 0xb0b1
MX6UL_PAD_NAND_WE_B__RAWNAND_WE_B 0xb0b1
MX6UL_PAD_NAND_DATA00__RAWNAND_DATA00 0xb0b1
MX6UL_PAD_NAND_DATA01__RAWNAND_DATA01 0xb0b1
MX6UL_PAD_NAND_DATA02__RAWNAND_DATA02 0xb0b1
MX6UL_PAD_NAND_DATA03__RAWNAND_DATA03 0xb0b1
MX6UL_PAD_NAND_DATA04__RAWNAND_DATA04 0xb0b1
MX6UL_PAD_NAND_DATA05__RAWNAND_DATA05 0xb0b1
MX6UL_PAD_NAND_DATA06__RAWNAND_DATA06 0xb0b1
MX6UL_PAD_NAND_DATA07__RAWNAND_DATA07 0xb0b1
>;
};
.........
}
}
这里我们把一个模块所用到的所有GPIO配置组织到一起。如果需要在iomuxc中添加自定义外设的pin,需要新建子节点,然后将自定义外设的所有pin配置信息都放到这个子节点中。
端口配置信息如下:
MX6UL_PAD_NAND_CLE__RAWNAND_CLE 0xb0b1
其中MX6UL_PAD_NAND_CLE__RAWNAND_CLE 是一个宏定义,作用是配置端口为RAWNAND_CLE 功能。在 imx6ul-pinfunc.h文件中定义如下:
#define MX6UL_PAD_NAND_CLE__RAWNAND_CLE 0x01B4 0x0440 0x0000 0x0 0x0
尾部五个参数含义是
0xb0b1 为conf_reg寄存器的值,有客户定义,用于配置端口的电气属性。
我们用MX6UL_PAD_GPIO1_IO02__GPIO1_IO02 端口做演示。
pinctrl_light: lightgrp {
};
pinctrl_light: lightgrp {
fsl,pins = <
>;
pinctrl_light: lightgrp {
fsl,pins = <
MX6UL_PAD_GPIO1_IO02__GPIO1_IO02 0x1b0b0
>;
至此已经完成向设备树中添加pin配置信息。
GPIO子系统用于初始化GPIO,提供GPIO操作API函数,如:设置GPIO输入输出,读取GPIO值等。驱动开发者在设备树中添加gpio相关信息,就可以在驱动程序中使用gpio子系统提供的API操作GPIO。不需要开发者自己再编写GPIO操作函数。
light {
};
light {
#address-cells = <1>;
#size-cells = <1>;
compatible = "gpio-light";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_light>; //表示设备所使用的pin信息保存在pinctrl_light中。
};
light {
#address-cells = <1>;
#size-cells = <1>;
compatible = "gpio-light";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_light>;
light-gpio = <&gpio1 2 GPIO_ACTIVE_LOW>; //要用的到gpio配置信息,gpio1_2,默认低电平
status = "okay";
};
至此,设备树配置信息添加完毕,接下来编写驱动程序。
int of_gpio_named_count(struct device_node *np, const char *propname)
返回统计到的GPIO数量。
int of_gpio_count(struct device_node *np)
返回统计到的GPIO数量,指定属性的GPIO。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define GPIOLED_CNT 1 //设备号个数
#define GPIOLED_NAME "gpioled"
#define LEDOFF 0 //关灯
#define LEDON 1 //开灯
//gpioled设备结构体
struct gpioled_dev
{
/* data */
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设备
/*
@description : 打开设备
@param - inode : 传递给驱动的inode
@param - filp : 设备文件
@return : 0 成功; 其他 失败
*/
static int led_open(struct inode *inode, struct file *file)
{
file->private_data = &gpioled; //设置私有数据
return 0;
}
/*
@description : 从设备读取数据
@param - filp : 要打开的设备文件
@param - buf : 返回给用户控件的数据缓冲区
@param - cnt : 要读取的数据长度
@param - offt : 相对于文件首地址的偏移
@return : 读取的字节数,若为负值,读取失败
*/
static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt,loff_t *offt)
{
return 0;
}
/*
@description : 向设备写数据
@param - filp : 设备文件
@param - buf : 要写入设备的数据
@param - cnt : 要写入的数据长度
@param - offt : 相对于文件首地址的偏移
@return : 写入的字节数,若为负值,读取失败
*/
static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt,loff_t *offt)
{
int retvalue;
unsigned char datebuf[1];
unsigned char ledstat;
struct gpioled_dev *dev = filp->private_data;
retvalue = copy_from_user(datebuf, buf, cnt);
if(retvalue < 0)
{
printk("kernel write failed!!\r\n");
return -EFAULT;
}
ledstat = datebuf[0];
if(ledstat == LEDON)
{
gpio_set_value(dev->led_gpio,0);//打开led灯
}
else if (ledstat == LEDOFF)
{
gpio_set_value(dev->led_gpio,1);//关闭led灯
}
return 0;
}
/*
@description : 关闭/释放设备
@param - filp : 要关闭的设备文件
@return : 0 成功; 其他 失败
*/
static int led_release(struct inode *inode, struct file *file)
{
return 0;
}
/* 设备操作函数集合 */
static struct file_operations gpioled_fops = {
.owner = THIS_MODULE,
.open = led_open,
.read = led_read,
.write = led_write,
.release = led_release,
};
/*
@description : 驱动入口函数
@param - : 无
@return : 无
*/
static int __init led_init(void)
{
int ret;
/* 获取设备树中的属性数据 */
/* 1.获取设备节点:light */
gpioled.nd = of_find_node_by_path("/light");
if(gpioled.nd == NULL){
printk("lightled node can not found!\r\n");
return -EINVAL;
}
else
{
printk("lightled node has been found!\r\n");
}
/* 2.获取gpio属性内容,得到led使用的gpio编号 */
gpioled.led_gpio = of_get_named_gpio(gpioled.nd,"light-gpio",0);
if(gpioled.led_gpio<0)
{
printk("can't get led-gpio.");
return -EINVAL;
}
printk("led-gpio num = %d\r\n",gpioled.led_gpio);
/* 3.设置GPIO1_IO3为输出,并输出高电平,默认关闭led灯 */
ret = gpio_direction_output(gpioled.led_gpio,1);
if(ret < 0)
{
printk("can't set gpio!\r\n");
}
//注册字符设备驱动
//1.创建设备号
if(gpioled.major)
{
gpioled.devid = MKDEV(gpioled.major,0);
register_chrdev_region(gpioled.devid,GPIOLED_CNT,GPIOLED_NAME);
}
else
{
alloc_chrdev_region(&gpioled.devid,0,GPIOLED_CNT,GPIOLED_NAME);//申请设备号
gpioled.major = MAJOR(gpioled.devid);//获取主设备号
gpioled.minor = MINOR(gpioled.devid);//获取次设备号
}
printk("gpioled major=%d,minor=%d\r\n",gpioled.major,gpioled.minor);
//2.初始化cdev
gpioled.cdev.owner = THIS_MODULE;
cdev_init(&gpioled.cdev,&gpioled_fops);
//3.添加cdev
cdev_add(&gpioled.cdev,gpioled.devid,GPIOLED_CNT);
//4.创建类
gpioled.class = class_create(THIS_MODULE,GPIOLED_NAME);
if(IS_ERR(gpioled.class))
{
return PTR_ERR(gpioled.class);
}
//5.创建设备
gpioled.device = device_create(gpioled.class,NULL,gpioled.devid,NULL,GPIOLED_NAME);
if(IS_ERR(gpioled.device))
{
return PTR_ERR(gpioled.device);
}
return 0;
}
static void __exit led_exit(void)
{
//注销字符驱动
cdev_del(&gpioled.cdev);//删除cdev
unregister_chrdev_region(gpioled.devid,GPIOLED_CNT);
device_destroy(gpioled.class,gpioled.devid);
class_destroy(gpioled.class);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Turing");
设备树节点:
pinctrl节点:
};
设备节点:
light {
#address-cells = <1>;
#size-cells = <1>;
compatible = "gpio-light";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_light>;
light-gpio = <&gpio1 2 GPIO_ACTIVE_LOW>;
status = "okay";
};