本篇pinctl和gpio子系统驱动框架
学习来自正点原子,原文链接:https://blog.youkuaiyun.com/weixin_55796564/article/details/120033581
一、首先,简单的,裸机模式,直接操作寄存器
例子1、gpio接口输出高低电平,控制led,不带设备树,有时候项目开发,也是可以直接操作的
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/uaccess.h>
#include <linux/delay.h>
#define on 1
#define off 0
#define BUFFER_SIZE 20
struct hi3559av100_led
{
int dev_major ;
struct class *cls;
struct device *dev;
int value;
};
struct hi3559av100_led *led_dev;
volatile unsigned long *gpio1_1_conf;
volatile unsigned char *gpio1_1_data;
int led_drv_open(struct inode *inode, struct file *filp)
{
printk("%s\n", __FUNCTION__);
*gpio1_1_conf |= (0x1<<1); //set pin out
*gpio1_1_data &= ~(0x1<<1); //set low off
return 0;
}
int led_drv_close(struct inode *inode, struct file *filp)
{
printk("%s\n", __FUNCTION__);
//*gpio1_1_data &= ~(0x1<<1); // close led
return 0;
}
ssize_t led_drv_write(struct file *filp, const char __user *buf, size_t count, loff_t *fpos)
{
printk("%s\n", __FUNCTION__);
char *led_buf;
int ret = -1;
led_buf = kmalloc(min_t(size_t, count+1, BUFFER_SIZE), GFP_KERNEL);
if(led_buf == NULL) {
printk("%s kmalloc error\n",__FUNCTION__);
return -EFAULT;
} else {
printk("%s kmalloc %d byte success\n",__FUNCTION__,count+1);
}
// copy data from app
ret = copy_from_user(led_buf, buf, count);
if(ret != 0) {
printk("%s copy_from_user error\n",__FUNCTION__);
kfree(led_buf);
return -EFAULT;
} else if(ret == 0) {
printk("%s copy %d byte success value:%s\n",__FUNCTION__,count,led_buf);
}
// parse data
if(*led_buf == '1') //led on
{
*gpio1_1_data |= (0x1<<1);
printk("%s led on\n",__FUNCTION__);
}
else if(*led_buf == '0') //led off
{
*gpio1_1_data &= ~(0x1<<1);
printk("%s led off\n",__FUNCTION__);
}
else
{
printk("%s unknow data\n",__FUNCTION__);
printk("%s\n",led_buf);
return -EFAULT;
}
kfree(led_buf);
return count;
}
long led_drv_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
switch(cmd)
{
case on :
//led on
printk("%s led on\n",__FUNCTION__);
*gpio1_1_data |= (0x1<<1);
break;
case off :
//led off
printk("%s led off\n",__FUNCTION__);
*gpio1_1_data &= ~(0x1<<1);
break;
default :
printk("%s unkown cmd\n",__FUNCTION__);
return -EINVAL;
}
return 0;
}
const struct file_operations red_led_fops = {
.open = led_drv_open,
.write = led_drv_write,
.release = led_drv_close,
.unlocked_ioctl = led_drv_ioctl,
};
static int __init led_drv_init(void)
{
int ret;
printk("%s\n", __FUNCTION__);
led_dev = kzalloc(sizeof(struct hi3559av100_led), GFP_KERNEL);
if(led_dev == NULL)
{
printk(KERN_ERR,"kzalloc error\n");
return -ENOMEM;
}
// 1, 申请主设备号
led_dev->dev_major = 0;
led_dev->dev_major = register_chrdev(led_dev->dev_major, "led_red", &red_led_fops);
if(led_dev->dev_major < 0)
{
printk("register_chrdev error\n");
ret = -EINVAL;
goto err_free;
}
// 2 ---自动创建设备节点
led_dev->cls = class_create(THIS_MODULE,"led_red");
if(IS_ERR(led_dev->cls))
{
printk("class_create error\n");
ret = PTR_ERR(led_dev->cls);
goto err_unregister;
}
//创建一个设备节点
led_dev->dev = device_create(led_dev->cls, NULL,MKDEV(led_dev->dev_major, 0), NULL, "led_red");
if(IS_ERR(led_dev->dev))
{
printk("device_create error\n");
ret = PTR_ERR(led_dev->dev);
goto err_class_destroy;
}
//返回值--映射之后的虚拟地址
gpio1_1_conf = ioremap(0x12141400,16);
gpio1_1_data = ioremap(0x12141008,8);
*gpio1_1_conf |= (0x1<<1); //set pin out
*gpio1_1_data &= ~(0x1<<1); //set low off
return 0;
err_class_destroy:
class_destroy(led_dev->cls);
err_unregister:
unregister_chrdev(led_dev->dev_major, "led_red");
err_free:
kfree(led_dev);
return ret;
}
static void __exit led_drv_exit(void)
{
printk("%s\n", __FUNCTION__);
*gpio1_1_data &= ~(0x1<<1); // close led
iounmap(gpio1_1_conf);
iounmap(gpio1_1_data);
device_destroy(led_dev->cls, MKDEV(led_dev->dev_major, 0));
class_destroy(led_dev->cls);
unregister_chrdev(led_dev->dev_major, "led_red");
kfree(led_dev);
}
module_init(led_drv_init);
module_exit(led_drv_exit);
MODULE_LICENSE("GPL");
测试方法
1:可以在终端控制台输入:
echo 1 > /dev/led_red
echo 0 > /dev/led_red
2:app测试:提供三种方法
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <linux/fs.h>
#include <linux/gpio.h>
#if 0
int main(int argc, char **argv)
{
int fd;
char* filename=NULL;
unsigned long arg;
int ret = -1;
filename = argv[1];
fd = open(filename, O_RDWR);
if (fd < 0)
{
printf("app open %s fail!\n", filename);
return 0;
}
else
{
printf("app open led success %s!\r\n",filename);
}
if(!strcmp(argv[2], "on"))
{
arg = 1;
ret = ioctl(fd, 1, arg);
if(ret < 0 )
printf("app oprate led on fail\r\n");
else
printf("app oprate led on success\r\n");
}
else if(!strcmp(argv[2], "off"))
{
arg = 1;
ret = ioctl(fd, 0, arg);
if(ret < 0 )
printf("app oprate led off fail\r\n");
else
printf("app oprate led off success\r\n");
}
close(fd);
return 0;
}
#elif 1
int main()
{
int fd0 = -1;
int fd1 = -1;
char *filename0 = NULL;
char *filename1 = NULL;
char buf[100]={0};
int ret = -1;
filename0 = "/dev/led_green";
filename1 = "/dev/led_red";
//led0
fd0 = open(filename0, O_RDWR);
if (fd0 < 0)
{
printf("app open %s fail\n", filename0);
return 0;
}
else
{
printf("app open led success %s\r\n",filename0);
}
//led1
fd1 = open(filename1, O_RDWR);
if (fd1 < 0)
{
printf("app open %s fail\n", filename1);
return 0;
}
else
{
printf("app open led success %s\r\n",filename1);
}
//led0 on
memset(buf,0,sizeof(buf));
buf[0]= '1';
ret = write(fd0,buf,1);
if(ret <= 0 )
printf("app write led fail\n");
sleep(1);
//led0 off
memset(buf,0,sizeof(buf));
buf[0]= '0';
write(fd0,buf,1);
if(ret <= 0 )
printf("app write led fail\n");
sleep(1);
//led0 on
memset(buf,0,sizeof(buf));
buf[0]= '1';
write(fd0,buf,1);
if(ret <= 0 )
printf("app write led fail\n");
sleep(1);
//led0 off
memset(buf,0,sizeof(buf));
buf[0]= '0';
write(fd0,buf,1);
if(ret <= 0 )
printf("app write led fail\n");
//sleep wait
sleep(2);
//led1 on
memset(buf,0,sizeof(buf));
buf[0]= '1';
write(fd1,buf,1);
if(ret <= 0 )
printf("app write led fail\n");
sleep(1);
//led1 off
memset(buf,0,sizeof(buf));
buf[0]= '0';
write(fd1,buf,1);
if(ret <= 0 )
printf("app write led fail\n");
sleep(1);
//led1 on
memset(buf,0,sizeof(buf));
buf[0]= '1';
write(fd1,buf,1);
if(ret <= 0 )
printf("app write led fail\n");
sleep(1);
//led1 off
memset(buf,0,sizeof(buf));
buf[0]= '0';
write(fd1,buf,1);
if(ret <= 0 )
printf("app write led fail\n");
//sleep wait
sleep(2);
//led1 and led0 on
write(fd1,"1",1);
write(fd0,"1",1);
sleep(2);
//led1 and led0 off
write(fd1,"0",1);
write(fd0,"0",1);
sleep(2);
//led1 and led0 on
write(fd1,"on",2);
write(fd0,"on",2);
sleep(2);
//led1 and led0 off
write(fd1,"off",3);
write(fd0,"off",3);
close(fd0);
close(fd1);
return 0;
}
#elif 0
int main(int argc, char **argv)
{
int fd;
char* filename=NULL;
unsigned char buf[10]={0};
int ret = -1;
int i = 0;
filename = argv[1];
fd = open(filename, O_RDWR);
if (fd < 0)
{
printf("app open %s fail\n", filename);
return 0;
}
else
{
printf("app open led success %s\n",filename);
}
while (1)
{
memset(buf, 0 , sizeof(buf));
printf("请输入 on | off | quit \n");
scanf("%s", buf);
if (!strcmp(buf, "on"))
{
write(fd, "1", 1);
}
else if (!strcmp(buf, "off"))
{
write(fd, "0", 1);
}
else if (!strcmp(buf, "flash"))
{
for (i=0; i<3; i++)
{
write(fd, "1", 1);
sleep(1);
write(fd, "0", 1);
sleep(1);
}
}
else if (!strcmp(buf, "quit"))
{
break;
}
}
close(fd);
return 0;
}
#endif
例子2、正点原子,用read,write操作寄存器
1、不带设备树
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#define NEWCHRLED_NAME "newchrled"
#define NEWCHRLED_COUNT 1
/* 寄存器物理地址 */
#define CCM_CCGR1_BASE (0X020C406C)
#define SW_MUX_GPIO1_IO03_BASE (0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE (0X020E02F4)
#define GPIO1_DR_BASE (0X0209C000)
#define GPIO1_GDIR_BASE (0X0209C004)
/* 地址映射后的虚拟地址指针 */
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;
#define LEDOFF 0 /* 关闭 */
#define LEDON 1 /* 打开 */
/* LED设备结构体 */
struct newchrled_dev{
struct cdev cdev; /* 字符设备 */
dev_t devid; /* 设备号 */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
};
struct newchrled_dev newchrled; /* led设备 */
/* LED灯打开/关闭 */
static void led_switch(u8 sta)
{
u32 val = 0;
if(sta == LEDON) {
val = readl(GPIO1_DR);
val &= ~(1 << 3); /* bit3清零,打开LED灯 */
writel(val, GPIO1_DR);
} else if(sta == LEDOFF) {
val = readl(GPIO1_DR);
val |= (1 << 3); /* bit3清零,打开LED灯 */
writel(val, GPIO1_DR);
}
}
static int newchrled_open(struct inode *inode, struct file *filp)
{
filp->private_data = &newchrled;
return 0;
}
static int newchrled_release(struct inode *inode, struct file *filp)
{
struct newchrled_dev *dev = (struct newchrled_dev*)filp->private_data;
return 0;
}
static ssize_t newchrled_write(struct file *filp, const char __user *buf,
size_t count, loff_t *ppos)
{
int retvalue;
unsigned char databuf[1];
retvalue = copy_from_user(databuf, buf, count);
if(retvalue < 0) {
printk("kernel write failed!\r\n");
return -EFAULT;
}
/* 判断是开灯还是关灯 */
led_switch(databuf[0]);
return 0;
}
static const struct file_operations newchrled_fops = {
.owner = THIS_MODULE,
.write = newchrled_write,
.open = newchrled_open,
.release= newchrled_release,
};
/*入口 */
static int __init newchrled_init(void)
{
int ret = 0;
unsigned int val = 0;
printk("newchrled_init\r\n");
/* 1,初始化LED灯,地址映射 */
IMX6U_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4);
SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);
SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE, 4);
GPIO1_DR = ioremap(GPIO1_DR_BASE, 4);
GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4);
/* 2,初始化 */
val = readl(IMX6U_CCM_CCGR1);
val &= ~(3 << 26); /* 先清除以前的配置bit26,27 */
val |= 3 << 26; /* bit26,27置1 */
writel(val, IMX6U_CCM_CCGR1);
writel(0x5, SW_MUX_GPIO1_IO03); /* 设置复用 */
writel(0X10B0, SW_PAD_GPIO1_IO03); /* 设置电气属性 */
val = readl(GPIO1_GDIR);
val |= 1 << 3; /* bit3置1,设置为输出 */
writel(val, GPIO1_GDIR);
val = readl(GPIO1_DR);
val |= (1 << 3); /* bit3置1,关闭LED灯 */
writel(val, GPIO1_DR);
newchrled.major = 0; /* 设置为0,表示由系统申请设备号 */
/* 2,注册字符设备 */
if(newchrled.major){ /* 给定主设备号 */
newchrled.devid = MKDEV(newchrled.major, 0);
ret = register_chrdev_region(newchrled.devid, NEWCHRLED_COUNT, NEWCHRLED_NAME);
} else { /* 没有给定主设备号 */
ret = alloc_chrdev_region(&newchrled.devid, 0, NEWCHRLED_COUNT, NEWCHRLED_NAME);
newchrled.major = MAJOR(newchrled.devid);
newchrled.minor = MINOR(newchrled.devid);
}
if(ret < 0) {
printk("newchrled chrdev_region err!\r\n");
goto fail_devid;
}
printk("newchrled major=%d, minor=%d\r\n", newchrled.major, newchrled.minor);
/* 3,注册字符设备 */
newchrled.cdev.owner = THIS_MODULE;
cdev_init(&newchrled.cdev, &newchrled_fops);
ret = cdev_add(&newchrled.cdev, newchrled.devid, NEWCHRLED_COUNT);
if(ret < 0) {
goto fail_cdev;
}
/* 4,自动创建设备节点 */
newchrled.class = class_create(THIS_MODULE, NEWCHRLED_NAME);
if (IS_ERR(newchrled.class)) {
ret = PTR_ERR(newchrled.class);
goto fail_class;
}
newchrled.device = device_create(newchrled.class, NULL,
newchrled.devid, NULL, NEWCHRLED_NAME);
if (IS_ERR(newchrled.device)) {
ret = PTR_ERR(newchrled.device);
goto fail_device;
}
return 0;
fail_device:
class_destroy(newchrled.class);
fail_class:
cdev_del(&newchrled.cdev);
fail_cdev:
unregister_chrdev_region(newchrled.devid, NEWCHRLED_COUNT);
fail_devid:
return ret;
}
/* 出口 */
static void __exit newchrled_exit(void)
{
unsigned int val = 0;
printk("newchrled_exit\r\n");
val = readl(GPIO1_DR);
val |= (1 << 3); /* bit3清零,打开LED灯 */
writel(val, GPIO1_DR);
/* 1,取消地址映射 */
iounmap(IMX6U_CCM_CCGR1);
iounmap(SW_MUX_GPIO1_IO03);
iounmap(SW_PAD_GPIO1_IO03);
iounmap(GPIO1_DR);
iounmap(GPIO1_GDIR);
/* 1,删除字符设备 */
cdev_del(&newchrled.cdev);
/* 2,注销设备号 */
unregister_chrdev_region(newchrled.devid, NEWCHRLED_COUNT);
/* 3,摧毁设备 */
device_destroy(newchrled.class, newchrled.devid);
/* 4,摧毁类 */
class_destroy(newchrled.class);
}
/* 注册和卸载驱动 */
module_init(newchrled_init);
module_exit(newchrled_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");
2、带设备树的
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#define DTSLED_CNT 1 /* 设备号个数 */
#define DTSLED_NAME "dtsled" /* 名字 */
#define LEDOFF 0 /* 关闭 */
#define LEDON 1 /* 打开 */
/* 地址映射后的虚拟地址指针 */
static void __iomem *IMX6U_CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;
/* desled设备结构体 */
struct dtsled_dev{
dev_t devid; /* 设备号 */
struct cdev cdev; /* 字符设备 */
struct class *class; /* 类 */
struct device *device; /* 设备 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
struct device_node *nd; /* 设备节点 */
};
struct dtsled_dev dtsled; /* led设备 */
/* LED灯打开/关闭 */
static void led_switch(u8 sta)
{
u32 val = 0;
if(sta == LEDON) {
val = readl(GPIO1_DR);
val &= ~(1 << 3); /* bit3清零,打开LED灯 */
writel(val, GPIO1_DR);
} else if(sta == LEDOFF) {
val = readl(GPIO1_DR);
val |= (1 << 3); /* bit3清零,打开LED灯 */
writel(val, GPIO1_DR);
}
}
static int dtsled_open(struct inode *inode, struct file *filp)
{
filp->private_data = &dtsled;
return 0;
}
static int dtsled_release(struct inode *inode, struct file *filp)
{
struct dtsled_dev *dev = (struct dtsled_dev*)filp->private_data;
return 0;
}
static ssize_t dtsled_write(struct file *filp, const char __user *buf,
size_t count, loff_t *ppos)
{
struct dtsled_dev *dev = (struct dtsled_dev*)filp->private_data;
int retvalue;
unsigned char databuf[1];
retvalue = copy_from_user(databuf, buf, count);
if(retvalue < 0) {
return -EFAULT;
}
/* 判断是开灯还是关灯 */
led_switch(databuf[0]);
return 0;
}
/* dtsled字符设备操作集 */
static const struct file_operations dtsled_fops = {
.owner = THIS_MODULE,
.write = dtsled_write,
.open = dtsled_open,
.release= dtsled_release,
};
/* 入口 */
static int __init dtsled_init(void)
{
int ret = 0;
const char *str;
u32 regdata[10];
u8 i = 0;
unsigned int val = 0;
/* 注册字符设备 */
/* 1,申请设备号 */
dtsled.major = 0; /* 设备号由内核分配 */
if(dtsled.major) { /* 定义了设备号 */
dtsled.devid = MKDEV(dtsled.major, 0);
ret = register_chrdev_region(dtsled.devid, DTSLED_CNT, DTSLED_NAME);
} else { /* 没有给定设备号 */
ret = alloc_chrdev_region(&dtsled.devid, 0, DTSLED_CNT, DTSLED_NAME);
dtsled.major = MAJOR(dtsled.devid);
dtsled.minor = MINOR(dtsled.devid);
}
if(ret < 0) {
goto fail_devid;
}
/* 2,添加字符设备 */
dtsled.cdev.owner = THIS_MODULE;
cdev_init(&dtsled.cdev, &dtsled_fops);
ret = cdev_add(&dtsled.cdev, dtsled.devid, DTSLED_CNT);
if(ret < 0)
goto fail_cdev;
/* 3,自动创建设备节点 */
dtsled.class = class_create(THIS_MODULE, DTSLED_NAME);
if (IS_ERR(dtsled.class)) {
ret = PTR_ERR(dtsled.class);
goto fail_class;
}
dtsled.device = device_create(dtsled.class, NULL, dtsled.devid, NULL, DTSLED_NAME);
if (IS_ERR(dtsled.device)) {
ret = PTR_ERR(dtsled.device);
goto fail_device;
}
/* 获取设备树属性内容 */
dtsled.nd = of_find_node_by_path("/alphaled");
if (dtsled.nd == NULL) { /* 失败 */
ret = -EINVAL;
goto fail_findnd;
}
/* 获取属性 */
ret = of_property_read_string(dtsled.nd, "status", &str);
if(ret < 0) {
goto fail_rs;
} else {
printk("status = %s\r\n", str);
}
ret = of_property_read_string(dtsled.nd, "compatible", &str);
if(ret < 0) {
goto fail_rs;
} else {
printk("compatible = %s\r\n", str);
}
#if 0
ret = of_property_read_u32_array(dtsled.nd, "reg", regdata, 10);
if(ret < 0) {
goto fail_rs;
} else {
printk("reg data:\r\n");
for(i = 0; i < 10; i++) {
printk("%#X ", regdata[i]);
}
printk("\r\n");
}
/* LED灯初始化 */
/* 1,初始化LED灯,地址映射 */
IMX6U_CCM_CCGR1 = ioremap(regdata[0], regdata[1]);
SW_MUX_GPIO1_IO03 = ioremap(regdata[2], regdata[3]);
SW_PAD_GPIO1_IO03 = ioremap(regdata[4], regdata[5]);
GPIO1_DR = ioremap(regdata[6], regdata[7]);
GPIO1_GDIR = ioremap(regdata[8], regdata[9]);
#endif
IMX6U_CCM_CCGR1 = of_iomap(dtsled.nd, 0);
SW_MUX_GPIO1_IO03 = of_iomap(dtsled.nd, 1);
SW_PAD_GPIO1_IO03 = of_iomap(dtsled.nd, 2);
GPIO1_DR = of_iomap(dtsled.nd, 3);
GPIO1_GDIR = of_iomap(dtsled.nd, 4);
/* 2,初始化 */
val = readl(IMX6U_CCM_CCGR1);
val &= ~(3 << 26); /* 先清除以前的配置bit26,27 */
val |= 3 << 26; /* bit26,27置1 */
writel(val, IMX6U_CCM_CCGR1);
writel(0x5, SW_MUX_GPIO1_IO03); /* 设置复用 */
writel(0X10B0, SW_PAD_GPIO1_IO03); /* 设置电气属性 */
val = readl(GPIO1_GDIR);
val |= 1 << 3; /* bit3置1,设置为输出 */
writel(val, GPIO1_GDIR);
val = readl(GPIO1_DR);
val |= (1 << 3); /* bit3置1,关闭LED灯 */
writel(val, GPIO1_DR);
return 0;
fail_rs:
fail_findnd:
device_destroy(dtsled.class, dtsled.devid);
fail_device:
class_destroy(dtsled.class);
fail_class:
cdev_del(&dtsled.cdev);
fail_cdev:
unregister_chrdev_region(dtsled.devid, DTSLED_CNT);
fail_devid:
return ret;
}
/* 出口 */
static void __exit dtsled_exit(void)
{
unsigned int val = 0;
val = readl(GPIO1_DR);
val |= (1 << 3); /* bit3清零,打开LED灯 */
writel(val, GPIO1_DR);
/* 1,取消地址映射 */
iounmap(IMX6U_CCM_CCGR1);
iounmap(SW_MUX_GPIO1_IO03);
iounmap(SW_PAD_GPIO1_IO03);
iounmap(GPIO1_DR);
iounmap(GPIO1_GDIR);
/* 删除字符设备 */
cdev_del(&dtsled.cdev);
/* 释放设备号 */
unregister_chrdev_region(dtsled.devid, DTSLED_CNT);
/* 摧毁设备*/
device_destroy(dtsled.class, dtsled.devid);
/* 摧毁类 */
class_destroy(dtsled.class);
}
/* 注册驱动和卸载驱动 */
module_init(dtsled_init);
module_exit(dtsled_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");
二、pinctl和gpio子系统驱动框架
之前控制引脚的方法都是操作配置寄存器:
现在可以不用这种方法,linux有现成的框架,这个框架就是pinctl子系统和gpio子系统,可以pinctl子系统设置引脚的复用功能,设置引脚的电气属性。
Linux内核提供了pinctrl子系统和gpio子系统用于GPIO驱动,当然pinctrl子系统负责不仅仅是GPIO的驱动,而是所有pin脚配置。pinctrl子系统是随着设备树的加入而加入的,依赖设备树。GPIO子系统在之前的内核也是存在的,但是pinctrl子系统的加入使得GPIO子系统有很大的改变。
在以前的内核版本中,如果要配置GPIO的话一般要使用SOC厂家实现的配置函数,例如三星的配置函数s3c_gpio_cfgpin等,这样带来的问题就是各家有个家的接口函数与是实现方式,不但内核的代码复用率低而且开发者很难记住这么多的函数,如果要使用多种平台的话背函数都是很麻烦的,所以在引入设备树后对GPIO子系统进行大的改造,使用设备树来实现并提供统一的接口。通过GPIO子系统功能主要实现引脚功能的配置,如设置为GPIO,特殊功能,GPIO的方向,设置为中断等。
1、pinctrl 子系统主要工作内容
<1>获取设备树中 pin 信息,管理系统中所有的可以控制的 pin, 在系统初始化的时候, 枚举所有可以控制的 pin, 并标识这些 pin。
<2>根据获取到的 pin 信息来设置 pin 的复用功能,对于 SOC 而言, 其引脚除了配置成普通的 GPIO 之外,若干个引脚还可以组成一个 pin group, 形成特定的功能。
<3>根据获取到的 pin 信息来设置 pin 的电气特性,比如上/下拉、速度、驱动能力等。
对应使用者来说,只需要在设备树里面设置好某个 pin 的相关属性即可,其他的初始化工作均由 pinctrl 子系统来完成。
2、gpio子系统主要工作内容
当使用 pinctrl 子系统将引脚的复用设置为 GPIO,可以使用 GPIO 子系统来操作GPIO,Linux 内核提供了 pinctrl 子系统和 gpio 子系统用于 GPIO 驱动。
通过 GPIO 子系统功能要实现:
<1>引脚功能的配置(设置为 GPIO,GPIO 的方向, 输入输出模式,读取/设置 GPIO 的值)
<2>实现软硬件的分离(分离出硬件差异, 有厂商提供的底层支持; 软件分层。 驱动只需要调用接口 API 即可操作 GPIO)
<3>iommu 内存管理(直接调用宏即可操作 GPIO)
gpio 子系统的主要目的就是方便驱动开发者使用 gpio,驱动开发者在设备树中添加 gpio 相关信息,然后就可以在驱动程序中使用 gpio 子系统提供的 API函数来操作 GPIO, Linux 内核向驱动开发者屏蔽掉了 GPIO 的设置过程,极大的方便了驱动开发者使用 GPIO。
设备树使用pinctrl和gpio子系统描述一个gpio,例如test名字的pinctl,gpio
test1:test{
#address-cells = <1>;
#size-cells = <1>;
compatible = "test";
reg = <0x20ac000 0x00000004>;//描述数据寄存器的地址
pinctrl-names = "default";
pintrl-0 = <&pinctrl_test>;
test-gpio = <gpio1 3 GPIO_ACTICE_LOW>;//gpio1 表示第一组,3表示第一组第三个引脚,GPIO_ACTICE_LOW表示低电平
};
常用的gpio子系统提供的api函数,这些函数的定义在include\linux\gpio.h
1 、gpio_request函数
作用: gpio_request函数用于申请一个gpio管脚。
int gpio_request(unsigned gpio, const char *label)
参数:
gpio:要申请的gpio标号,使用of_get_named_gpio函数从设备树获取指定的GPIO属性信息,此函数会返回这个GPIO标号。
label:给gpio设置个名字。
返回值:0,申请成功,其他值申请失败。
2、 gpio_free函数
作用:如果不使用某个GPIO了,那么就可以调用gpio_free函数进行释放。
void gpio_free(unsigned gpio);
参数:
gpio:要释放的gpio标号。
返回值:无
3 、gpio_direction_input函数
作用:此函数用于设置某个GPIO为输入。
int gpio_direction_input(unsigned gpio);
参数:
gpio:要设置为输入的GPIO标号。
返回值:0,设置成功,其他值设置失败。
4、gpio_direction_output函数
作用:此函数用于设置某个GPIO为输出,并且设置默认输出值。
int gpio_direction_output(unsigned gpio, int value);
参数:
gpio:要设置为输出的GPIO标号。
value:GPIO默认输出值。
返回值:0,设置成功,设置失败返回负值。
5 、gpio_get_value函数
作用:此函数用于获取某个GPIO的值(0或1)
int gpio_get_value(unsigned int gpio);
gpio:要获取的gpio标号
返回值:0,成功,失败返回负值。
6、 gpio_set_value函数
作用:此函数用于获取某个GPIO的值(0或1)
void gpio_set_value(unsigned int gpio, int value);
gpio:要设置的gpio标号
value:要设置的值。
返回值:无。
总结
pinctl子系统的作用就是设置引脚的复用功能和电气属性。gpio子系统就是当pinctl子系统把引脚设置成GPIO功能以后就可以使用gpio子系统来操作我们引脚了,比如说设置输入、输出或者引脚的高低电平等等。
2 、gpio子系统
讲解了pinctrl子系统,pinctrl子系统重点是设置PIN(有的SOC叫做PAD)的复用和电气属性,如果pinctrl子系统将一个PIN复用为GPIO的话,那么接下来就要用到gpio子系统了。gpio子系统顾名思义,就是用于初始化GPIO并且提供相应的API函数,比如设置GPIO为输入输出,读取GPIO的值等。gpio子系统的主要目的就是方便驱动开发者使用gpio,驱动开发者在设备树中添加gpio相关信息,然后就可以在驱动程序中使用gpio子系统提供的API函数来操作GPIO,Linux内核向驱动开发者屏蔽掉了GPIO的设置过程,极大的方便了驱动开发者使用GPIO。
I.MX6ULL的gpio子系统驱动
1、设备树中的gpio信息
I.MX6ULL-ALPHA开发板上的UART1_RTS_B做为SD卡的检测引脚,UART1_RTS_B复用为GPIO1_IO19,通过读取这个GPIO的高低电平就可以知道SD卡有没有插入。首先肯定是将UART1_RTS_B这个PIN复用为GPIO1_IO19,并且设置电气属性,也就是上一小节讲的pinctrl节点。打开imx6ull-alientek-emmc.dts, UART1_RTS_B这个PIN的pincrtl设置如下:
示例代码45.2.2.1 SD卡CD引脚PIN配置参数
316 pinctrl_hog_1: hoggrp-1 {
317 fsl,pins = <
318 MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059 /* SD1 CD */
......
322 >;
323 };
第318行,设置UART1_RTS_B这个PIN为GPIO1_IO19。
pinctrl配置好以后就是设置gpio了,SD卡驱动程序通过读取GPIO1_IO19的值来判断SD卡有没有插入,但是SD卡驱动程序怎么知道 CD引脚连接的GPIO1_IO19呢?肯定是需要设备树告诉驱动啊!在设备树中SD卡节点下添加一个属性来描述SD卡的CD引脚就行了,SD卡驱动直接读取这个属性值就知道SD卡的CD引脚使用的是哪个GPIO了。SD卡连接在I.MX6ULL的usdhc1接口上,在imx6ull-alientek-emmc.dts中找到名为“usdhc1”的节点,这个节点就是SD卡设备节点,如下所示:
760 &usdhc1 {
761 pinctrl-names = "default", "state_100mhz", "state_200mhz";
762 pinctrl-0 = <&pinctrl_usdhc1>;
763 pinctrl-1 = <&pinctrl_usdhc1_100mhz>;
764 pinctrl-2 = <&pinctrl_usdhc1_200mhz>;
765 /* pinctrl-3 = <&pinctrl_hog_1>; */
766 cd-gpios = <&gpio1 19 GPIO_ACTIVE_LOW>;
767 keep-power-in-suspend;
768 enable-sdio-wakeup;
769 vmmc-supply = <®_sd1_vmmc>;
770 status = "okay";
771 };
第765行,此行本来没有,是作者添加的,usdhc1节点作为SD卡设备总节点,usdhc1节点需要描述SD卡所有的信息,因为驱动要使用。本行就是描述SD卡的CD引脚pinctrl信息所在的子节点,因为SD卡驱动需要根据pincrtl节点信息来设置CD引脚的复用功能等。762764行的pinctrl-02都是SD卡其他PIN的pincrtl节点信息。但是大家会发现,其实在usdhc1节点中并没有“pinctrl-3 = <&pinctrl_hog_1>”这一行,也就是说并没有指定CD引脚的pinctrl信息,那么SD卡驱动就没法设置CD引脚的复用功能啊?这个不用担心,因为在“iomuxc”节点下引用了pinctrl_hog_1这个节点,所以Linux内核中的iomuxc驱动就会自动初始化pinctrl_hog_1节点下的所有PIN。
第766行,属性“cd-gpios”描述了SD卡的CD引脚使用的哪个IO。属性值一共有三个,我们来看一下这三个属性值的含义,“&gpio1”表示CD引脚所使用的IO属于GPIO1组,“19”表示GPIO1组的第19号IO,通过这两个值SD卡驱动程序就知道CD引脚使用了GPIO1_IO19这GPIO。“GPIO_ACTIVE_LOW”表示低电平有效,如果改为“GPIO_ACTIVE_HIGH”就表示高电平有效。
根据上面这些信息,SD卡驱动程序就可以使用GPIO1_IO19来检测SD卡的CD信号了,打开imx6ull.dtsi,在里面找到如下所示内容:
504 gpio1: gpio@0209c000 {
505 compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
506 reg = <0x0209c000 0x4000>;
507 interrupts = <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>,
508 <GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH>;
509 gpio-controller;
510 #gpio-cells = <2>;
511 interrupt-controller;
512 #interrupt-cells = <2>;
513 };
gpio1节点信息描述了GPIO1控制器的所有信息,重点就是GPIO1外设寄存器基地址以及兼容属性。关于I.MX系列SOC的GPIO控制器绑定信息请查看文档Documentation/devicetree/bindings/gpio/ fsl-imx-gpio.txt。
第505行,设置gpio1节点的compatible属性有两个,分别为“fsl,imx6ul-gpio”和“fsl,imx35-gpio”,在Linux内核中搜索这两个字符串就可以找到I.MX6UL的GPIO驱动程序。
第506行,reg属性设置了GPIO1控制器的寄存器基地址为0X0209C000,大家可以打开《I.MX6ULL参考手册》找到“Chapter 28:General Purpose Input/Output(GPIO)”章节第28.5小节,有如图45.2.2.1所示的寄存器地址表:
从图45.2.2.1可以看出,GPIO1控制器的基地址就是0X0209C000。
第509行,“gpio-controller”表示gpio1节点是个GPIO控制器。
第510行,“#gpio-cells”属性和“#address-cells”类似,#gpio-cells应该为2,表示一共有两个cell,第一个cell为GPIO编号,比如“&gpio1 3”就表示GPIO1_IO03。第二个cell表示GPIO极性,如果为0(GPIO_ACTIVE_HIGH)的话表示高电平有效,如果为1(GPIO_ACTIVE_LOW)的话表示低电平有效。
2、GPIO驱动程序简介
本小节会涉及到Linux驱动分层与分离、平台设备驱动等还未讲解的知识,所以本小节教程可以不用看,不会影响后续的实验。如果对Linux内核的GPIO子系统实现原理感兴趣的话可以看本小节。
gpio1节点的compatible属性描述了兼容性,在Linux内核中搜索“fsl,imx6ul-gpio”和“fsl,imx35-gpio”这两个字符串,查找GPIO驱动文件。drivers/gpio/gpio-mxc.c就是I.MX6ULL的GPIO驱动文件,在此文件中有如下所示of_device_id匹配表:
示例代码45.2.2.3 mxc_gpio_dt_ids匹配表
152 static const struct of_device_id mxc_gpio_dt_ids[] = {
153 { .compatible = "fsl,imx1-gpio", .data = &mxc_gpio_devtype[IMX1_GPIO], },
154 { .compatible = "fsl,imx21-gpio", .data = &mxc_gpio_devtype[IMX21_GPIO], },
155 { .compatible = "fsl,imx31-gpio", .data = &mxc_gpio_devtype[IMX31_GPIO], },
156 { .compatible = "fsl,imx35-gpio", .data = &mxc_gpio_devtype[IMX35_GPIO], },
157 { /* sentinel */ }
158 };
第156行的compatible值为“fsl,imx35-gpio”,和gpio1的compatible属性匹配,因此gpio-mxc.c就是I.MX6ULL的GPIO控制器驱动文件。gpio-mxc.c所在的目录为drivers/gpio,打开这个目录可以看到很多芯片的gpio驱动文件, “gpiolib”开始的文件是gpio驱动的核心文件,如图45.2.2.2所示:
我们重点来看一下gpio-mxc.c这个文件,在gpio-mxc.c文件中有如下所示内容:
示例代码45.2.2.4 mxc_gpio_driver结构体
496 static struct platform_driver mxc_gpio_driver = {
497 .driver = {
498 .name = "gpio-mxc",
499 .of_match_table = mxc_gpio_dt_ids,
500 },
501 .probe = mxc_gpio_probe,
502 .id_table = mxc_gpio_devtype,
503 };
可以看出GPIO驱动也是个平台设备驱动,因此当设备树中的设备节点与驱动的of_device_id匹配以后probe函数就会执行,在这里就是mxc_gpio_probe函数,这个函数就是I.MX6ULL的GPIO驱动入口函数。我们简单来分析一下mxc_gpio_probe这个函数,函数内容如下:
403 static int mxc_gpio_probe(struct platform_device *pdev)
404 {
405 struct device_node *np = pdev->dev.of_node;
406 struct mxc_gpio_port *port;
407 struct resource *iores;
408 int irq_base;
409 int err;
410
411 mxc_gpio_get_hw(pdev);
412
413 port = devm_kzalloc(&pdev->dev, sizeof(*port), GFP_KERNEL);
414 if (!port)
415 return -ENOMEM;
416
417 iores = platform_get_resource(pdev, IORESOURCE_MEM, 0);
418 port->base = devm_ioremap_resource(&pdev->dev, iores);
419 if (IS_ERR(port->base))
420 return PTR_ERR(port->base);
421
422 port->irq_high = platform_get_irq(pdev, 1);
423 port->irq = platform_get_irq(pdev, 0);
424 if (port->irq < 0)
425 return port->irq;
426
427 /* disable the interrupt and clear the status */
428 writel(0, port->base + GPIO_IMR);
429 writel(~0, port->base + GPIO_ISR);
430
431 if (mxc_gpio_hwtype == IMX21_GPIO) {
432 /*
433 * Setup one handler for all GPIO interrupts. Actually
434 * setting the handler is needed only once, but doing it for
435 * every port is more robust and easier.
436 */
437 irq_set_chained_handler(port->irq, mx2_gpio_irq_handler);
438 } else {
439 /* setup one handler for each entry */
440 irq_set_chained_handler(port->irq, mx3_gpio_irq_handler);
441 irq_set_handler_data(port->irq, port);
442 if (port->irq_high > 0) {
443 /* setup handler for GPIO 16 to 31 */
444 irq_set_chained_handler(port->irq_high,
445 mx3_gpio_irq_handler);
446 irq_set_handler_data(port->irq_high, port);
447 }
448 }
449
450 err = bgpio_init(&port->bgc, &pdev->dev, 4,
451 port->base + GPIO_PSR,
452 port->base + GPIO_DR, NULL,
453 port->base + GPIO_GDIR, NULL, 0);
454 if (err)
455 goto out_bgio;
456
457 port->bgc.gc.to_irq = mxc_gpio_to_irq;
458 port->bgc.gc.base = (pdev->id < 0) ? of_alias_get_id(np, "gpio")
459 * 32 : pdev->id * 32;
460
461 err = gpiochip_add(&port->bgc.gc);
462 if (err)
463 goto out_bgpio_remove;
464
465 irq_base = irq_alloc_descs(-1, 0, 32, numa_node_id());
466 if (irq_base < 0) {
467 err = irq_base;
468 goto out_gpiochip_remove;
469 }
470
471 port->domain = irq_domain_add_legacy(np, 32, irq_base, 0,
472 &irq_domain_simple_ops, NULL);
473 if (!port->domain) {
474 err = -ENODEV;
475 goto out_irqdesc_free;
476 }
477
478 /* gpio-mxc can be a generic irq chip */
479 mxc_gpio_init_gc(port, irq_base);
480
481 list_add_tail(&port->node, &mxc_gpio_ports);
482
483 return 0;
......
494 }
第405行,设备树节点指针。
第406行,定义一个结构体指针port,结构体类型为mxc_gpio_port。gpio-mxc.c的重点工作就是维护mxc_gpio_port,mxc_gpio_port就是对I.MX6ULL GPIO的抽象。mxc_gpio_port结构体定义如下:
61 struct mxc_gpio_port {
62 struct list_head node;
63 void __iomem *base;
64 int irq;
65 int irq_high;
66 struct irq_domain *domain;
67 struct bgpio_chip bgc;
68 u32 both_edges;
69 };
mxc_gpio_port的bgc成员变量很重要,因为稍后的重点就是初始化bgc。
继续回到mxc_gpio_probe函数函数,第411行调用mxc_gpio_get_hw函数获取gpio的硬件相关数据,其实就是gpio的寄存器组,函数mxc_gpio_get_hw里面有如下代码:
364 static void mxc_gpio_get_hw(struct platform_device *pdev)
365 {
366 const struct of_device_id *of_id =
367 of_match_device(mxc_gpio_dt_ids, &pdev->dev);
368 enum mxc_gpio_hwtype hwtype;
......
383
384 if (hwtype == IMX35_GPIO)
385 mxc_gpio_hwdata = &imx35_gpio_hwdata;
386 else if (hwtype == IMX31_GPIO)
387 mxc_gpio_hwdata = &imx31_gpio_hwdata;
388 else
389 mxc_gpio_hwdata = &imx1_imx21_gpio_hwdata;
390
391 mxc_gpio_hwtype = hwtype;
392 }
注意第385行,mxc_gpio_hwdata是个全局变量,如果硬件类型是IMX35_GPIO的话设置mxc_gpio_hwdat为imx35_gpio_hwdata。对于I.MX6ULL而言,硬件类型就是IMX35_GPIO,imx35_gpio_hwdata是个结构体变量,描述了GPIO寄存器组,内容如下:
101 static struct mxc_gpio_hwdata imx35_gpio_hwdata = {
102 .dr_reg = 0x00,
103 .gdir_reg = 0x04,
104 .psr_reg = 0x08,
105 .icr1_reg = 0x0c,
106 .icr2_reg = 0x10,
107 .imr_reg = 0x14,
108 .isr_reg = 0x18,
109 .edge_sel_reg = 0x1c,
110 .low_level = 0x00,
111 .high_level = 0x01,
112 .rise_edge = 0x02,
113 .fall_edge = 0x03,
114 };
大家将imx35_gpio_hwdata中的各个成员变量和图45.2.2.1中的GPIO寄存器表对比就会发现,imx35_gpio_hwdata结构体就是GPIO寄存器组结构。这样我们后面就可以通过mxc_gpio_hwdata这个全局变量来访问 GPIO的相应寄存器了。
继续回到示例代码45.2.2.5的mxc_gpio_probe函数中,第417行,调用函数platform_get_resource获取设备树中内存资源信息,也就是reg属性值。前面说了reg属性指定了GPIO1控制器的寄存器基地址为0X0209C000,在配合前面已经得到的mxc_gpio_hwdata,这样Linux内核就可以访问gpio1的所有寄存器了。
第418行,调用devm_ioremap_resource函数进行内存映射,得到0x0209C000在Linux内核中的虚拟地址。
第422、423行,通过platform_get_irq函数获取中断号,第422行获取高16位GPIO的中断号,第423行获取底16位GPIO中断号。
第428、429行,操作GPIO1的IMR和ISR这两个寄存器,关闭GPIO1所有IO中断,并且清除状态寄存器。
第438~448行,设置对应GPIO的中断服务函数,不管是高16位还是低16位,中断服务函数都是mx3_gpio_irq_handler。
第450~453行,bgpio_init函数第一个参数为bgc,是bgpio_chip结构体指针。bgpio_chip结构体有个gc成员变量,gc是个gpio_chip结构体类型的变量。gpio_chip结构体是抽象出来的GPIO控制器,gpio_chip结构体如下所示(有缩减):
74 struct gpio_chip {
75 const char *label;
76 struct device *dev;
77 struct module *owner;
78 struct list_head list;
79
80 int (*request)(struct gpio_chip *chip,
81 unsigned offset);
82 void (*free)(struct gpio_chip *chip,
83 unsigned offset);
84 int (*get_direction)(struct gpio_chip *chip,
85 unsigned offset);
86 int (*direction_input)(struct gpio_chip *chip,
87 unsigned offset);
88 int (*direction_output)(struct gpio_chip *chip,
89 unsigned offset, int value);
90 int (*get)(struct gpio_chip *chip,
91 unsigned offset);
92 void (*set)(struct gpio_chip *chip,
93 unsigned offset, int value);
......
145 };
可以看出,gpio_chip大量的成员都是函数,这些函数就是GPIO操作函数。bgpio_init函数主要任务就是初始化bgc->gc。bgpio_init里面有三个setup函数:bgpio_setup_io、bgpio_setup_accessors和bgpio_setup_direction。这三个函数就是初始化bgc->gc中的各种有关GPIO的操作,比如输出,输入等等。第451~453行的GPIO_PSR、GPIO_DR和GPIO_GDIR都是I.MX6ULL的GPIO寄存器。这些寄存器地址会赋值给bgc参数的reg_dat、reg_set、reg_clr和reg_dir这些成员变量。至此,bgc既有了对GPIO的操作函数,又有了I.MX6ULL有关GPIO的寄存器,那么只要得到bgc就可以对I.MX6ULL的GPIO进行操作。
继续回到mxc_gpio_probe函数,第461行调用函数gpiochip_add向Linux内核注册gpio_chip,也就是port->bgc.gc。注册完成以后我们就可以在驱动中使用gpiolib提供的各个API函数。
45.2.3 gpio子系统API函数
对于驱动开发人员,设置好设备树以后就可以使用gpio子系统提供的API函数来操作指定的GPIO,gpio子系统向驱动开发人员屏蔽了具体的读写寄存器过程。这就是驱动分层与分离的好处,大家各司其职,做好自己的本职工作即可。gpio子系统提供的常用的API函数有下面几个:
1、gpio_request函数
gpio_request函数用于申请一个GPIO管脚,在使用一个GPIO之前一定要使用gpio_request进行申请,函数原型如下:
int gpio_request(unsigned gpio, const char *label)
函数参数和返回值含义如下:
gpio:要申请的gpio标号,使用of_get_named_gpio函数从设备树获取指定GPIO属性信息,此函数会返回这个GPIO的标号。
label:给gpio设置个名字。
返回值:0,申请成功;其他值,申请失败。
2、gpio_free函数
如果不使用某个GPIO了,那么就可以调用gpio_free函数进行释放。函数原型如下:
void gpio_free(unsigned gpio)
函数参数和返回值含义如下:
gpio:要释放的gpio标号。
返回值:无。
3、gpio_direction_input函数
此函数用于设置某个GPIO为输入,函数原型如下所示:
int gpio_direction_input(unsigned gpio)
函数参数和返回值含义如下:
gpio:要设置为输入的GPIO标号。
返回值:0,设置成功;负值,设置失败。
4、gpio_direction_output函数
此函数用于设置某个GPIO为输出,并且设置默认输出值,函数原型如下:
int gpio_direction_output(unsigned gpio, int value)
函数参数和返回值含义如下:
gpio:要设置为输出的GPIO标号。
value:GPIO默认输出值。
返回值:0,设置成功;负值,设置失败。
5、gpio_get_value函数
此函数用于获取某个GPIO的值(0或1),此函数是个宏,定义所示:
#define gpio_get_value __gpio_get_value
int __gpio_get_value(unsigned gpio)
函数参数和返回值含义如下:
gpio:要获取的GPIO标号。
返回值:非负值,得到的GPIO值;负值,获取失败。
6、gpio_set_value函数
此函数用于设置某个GPIO的值,此函数是个宏,定义如下
#define gpio_set_value __gpio_set_value
void __gpio_set_value(unsigned gpio, int value)
函数参数和返回值含义如下:
gpio:要设置的GPIO标号。
value:要设置的值。
返回值:无
关于gpio子系统常用的API函数就讲这些,这些是我们用的最多的。
4、 设备树中添加gpio节点模板
继续完成test设备,讲解了如何创建test设备的pinctrl节点。本节我们来学习一下如何创建test设备的GPIO节点。
1、创建test设备节点
在根节点“/”下创建test设备子节点,如下所示:
示例代码45.2.4.1 test设备节点
1 test {
2 /* 节点内容 */
3 };
2、添加pinctrl信息
在45.1.3中我们创建了pinctrl_test节点,此节点描述了test设备所使用的GPIO1_IO00这个PIN的信息,我们要将这节点添加到test设备节点中,如下所示:
1 test {
2 pinctrl-names = "default";
3 pinctrl-0 = <&pinctrl_test>;
4 /* 其他节点内容 */
5 };
第2行,添加pinctrl-names属性,此属性描述pinctrl名字为“default”。
第3行,添加pinctrl-0节点,此节点引用45.1.3中创建的pinctrl_test节点,表示tset设备的所使用的PIN信息保存在pinctrl_test节点中。
3、添加GPIO属性信息
我们最后需要在test节点中添加GPIO属性信息,表明test所使用的GPIO是哪个引脚,添加完成以后如下所示:
1 test {
2 pinctrl-names = "default";
3 pinctrl-0 = <&pinctrl_test>;
4 gpio = <&gpio1 0 GPIO_ACTIVE_LOW>;
5 };
第4行,test设备所使用的gpio。
关于pinctrl子系统和gpio子系统就讲解到这里,接下来就使用pinctrl和gpio子系统来驱动I.MX6ULL-ALPHA开发板上的LED灯。
5 、与gpio相关的OF函数
在示例代码中,我们定义了一个名为“gpio”的属性,gpio属性描述了test这个设备所使用的GPIO。在驱动程序中需要读取gpio属性内容,Linux内核提供了几个与GPIO有关的OF函数,常用的几个OF函数如下所示:
1、of_gpio_named_count函数
of_gpio_named_count函数用于获取设备树某个属性里面定义了几个GPIO信息,要注意的是空的GPIO信息也会被统计到,比如:
gpios = <0
&gpio1 1 2
0
&gpio2 3 4>;
上述代码的“gpios”节点一共定义了4个GPIO,但是有2个是空的,没有实际的含义。通过of_gpio_named_count函数统计出来的GPIO数量就是4个,此函数原型如下:
int of_gpio_named_count(struct device_node *np, const char *propname)
函数参数和返回值含义如下:
np:设备节点。
propname:要统计的GPIO属性。
返回值:正值,统计到的GPIO数量;负值,失败。
2、of_gpio_count函数
和of_gpio_named_count函数一样,但是不同的地方在于,此函数统计的是“gpios”这个属性的GPIO数量,而of_gpio_named_count函数可以统计任意属性的GPIO信息,函数原型如下所示:
int of_gpio_count(struct device_node *np)
函数参数和返回值含义如下:
np:设备节点。
返回值:正值,统计到的GPIO数量;负值,失败。
3、of_get_named_gpio函数
此函数获取GPIO编号,因为Linux内核中关于GPIO的API函数都要使用GPIO编号,此函数会将设备树中类似<&gpio5 7 GPIO_ACTIVE_LOW>的属性信息转换为对应的GPIO编号,此函数在驱动中使用很频繁!函数原型如下:
int of_get_named_gpio(struct device_node *np,const char *propname,int index)
函数参数和返回值含义如下:
np:设备节点。
propname:包含要获取GPIO信息的属性名。
index:GPIO索引,因为一个属性里面可能包含多个GPIO,此参数指定要获取哪个GPIO的编号,如果只有一个GPIO信息的话此参数为0。
返回值:正值,获取到的GPIO编号;负值,失败。
6、我们继续研究LED灯
通过设备树向dtsled.c文件传递相应的寄存器物理地址,然后在驱动文件中配置寄存器。本章实验我们使用pinctrl和gpio子系统来完成LED灯驱动。
1 、修改设备树文件
1、添加pinctrl节点
I.MX6U-ALPHA开发板上的LED灯使用了GPIO1_IO03这个PIN,打开imx6ull-alientek-emmc.dts,在iomuxc节点的imx6ul-evk子节点下创建一个名为“pinctrl_led”的子节点,节点内容如下所示:
示例代码45.4.1.1 GPIO1_IO03 pinctrl节点
1 pinctrl_led: ledgrp {
2 fsl,pins = <
3 MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0 /* LED0 */
4 >;
5 };
第3行,将GPIO1_IO03这个PIN复用为GPIO1_IO03,电气属性值为0X10B0。
2、添加LED设备节点
在根节点“/”下创建LED灯节点,节点名为“gpioled”,节点内容如下:
1 gpioled {
2 #address-cells = <1>;
3 #size-cells = <1>;
4 compatible = "atkalpha-gpioled";
5 pinctrl-names = "default";
6 pinctrl-0 = <&pinctrl_led>;
7 led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
8 status = "okay";
9 };
第6行,pinctrl-0属性设置LED灯所使用的PIN对应的pinctrl节点。
第7行,led-gpio属性指定了LED灯所使用的GPIO,在这里就是GPIO1的IO03,低电平有效。稍后编写驱动程序的时候会获取led-gpio属性的内容来得到GPIO编号,因为gpio子系统的API操作函数需要GPIO编号。
3、检查PIN是否被其他外设使用
这一点非常重要!!!
很多初次接触设备树的驱动开发人员很容易因为这个小问题栽了大跟头!因为我们所使用的设备树基本都是在半导体厂商提供的设备树文件基础上修改而来的,而半导体厂商提供的设备树是根据自己官方开发板编写的,很多PIN的配置和我们所使用的开发板不一样。比如A这个引脚在官方开发板接的是I2C的SDA,而我们所使用的硬件可能将A这个引脚接到了其他的外设,比如LED灯上,接不同的外设,A这个引脚的配置就不同。一个引脚一次只能实现一个功能,如果A引脚在设备树中配置为了I2C的SDA信号,那么A引脚就不能再配置为GPIO,否则的话驱动程序在申请GPIO的时候就会失败。检查PIN有没有被其他外设使用包括两个方面:
①、检查pinctrl设置。
②、如果这个PIN配置为GPIO的话,检查这个GPIO有没有被别的外设使用。
在本章实验中LED灯使用的PIN为GPIO1_IO03,因此先检查GPIO_IO03这个PIN有没有被其他的pinctrl节点使用,在imx6ull-alientek-emmc.dts中找到如下内容:
pinctrl_tsc节点
480 pinctrl_tsc: tscgrp {
481 fsl,pins = <
482 MX6UL_PAD_GPIO1_IO01__GPIO1_IO01 0xb0
483 MX6UL_PAD_GPIO1_IO02__GPIO1_IO02 0xb0
484 MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0xb0
485 MX6UL_PAD_GPIO1_IO04__GPIO1_IO04 0xb0
486 >;
487 };
pinctrl_tsc节点是TSC(电阻触摸屏接口)的pinctrl节点,从第484行可以看出,默认情况下GPIO1_IO03作为了TSC外设的PIN。所以我们需要将第484行屏蔽掉!和C语言一样,在要屏蔽的内容前后加上“/”和“/”符号即可。其实在I.MX6U-ALPHA开发板上并没有用到TSC接口,所以第482~485行的内容可以全部屏蔽掉。
因为本章实验我们将GPIO1_IO03这个PIN配置为了GPIO,所以还需要查找一下有没有其他的外设使用了GPIO1_IO03,在imx6ull-alientek-emmc.dts中搜索“gpio1 3”,找到如下内容:
示例代码45.4.1.4 tsc节点
723 &tsc {
724 pinctrl-names = "default";
725 pinctrl-0 = <&pinctrl_tsc>;
726 xnur-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
727 measure-delay-time = <0xffff>;
728 pre-charge-time = <0xfff>;
729 status = "okay";
730 };
tsc是TSC的外设节点,从726行可以看出,tsc外设也使用了GPIO1_IO03,同样我们需要将这一行屏蔽掉。然后在继续搜索“gpio1 3”,看看除了本章的LED灯以外还有没有其他的地方也使用了GPIO1_IO03,找到一个屏蔽一个。
设备树编写完成以后使用“make dtbs”命令重新编译设备树,然后使用新编译出来的imx6ull-alientek-emmc.dtb文件启动Linux系统。启动成功以后进入“/proc/device-tree”目录中查看“gpioled”节点是否存在,如果存在的话就说明设备树基本修改成功(具体还要驱动验证),结果如图所示:
4、 LED灯驱动程序编写
设备树准备好以后就可以编写驱动程序了,在前面的例子中进行修改
gpio子系统框架下led例子
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#define GPIOLED_CNT 1
#define GPIOLED_NAME "gpioled"
#define LEDOFF 0
#define LEDON 1
/* gpioled设备结构体 */
struct gpioled_dev{
dev_t devid;
int major;
int minor;
struct cdev cdev;
struct class *class;
struct device *device;
struct device_node *nd;
int led_gpio;
};
struct gpioled_dev gpioled; /* LED */
static int led_open(struct inode *inode, struct file *filp)
{
filp->private_data = &gpioled;
return 0;
}
static int led_release(struct inode *inode, struct file *filp)
{
return 0;
}
static ssize_t led_write(struct file *filp, const char __user *buf,
size_t count, loff_t *ppos)
{
int ret;
unsigned char databuf[1];
struct gpioled_dev *dev = filp->private_data;
ret = copy_from_user(databuf, buf, count);
if(ret < 0) {
return -EINVAL;
}
if(databuf[0] == LEDON) {
gpio_set_value(dev->led_gpio, 0);
} else if(databuf[0] == LEDOFF) {
gpio_set_value(dev->led_gpio, 1);
}
return 0;
}
/* 操作集 */
static const struct file_operations led_fops = {
.owner = THIS_MODULE,
.write = led_write,
.open = led_open,
.release = led_release,
};
/* 驱动入口函数 */
static int __init led_init(void)
{
int ret = 0;
/* 注册字符设备驱动 */
gpioled.major = 0;
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, &led_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);
}
/* 1,获取设备节点 */
gpioled.nd = of_find_node_by_path("/gpioled");
if(gpioled.nd == NULL) {
ret = -EINVAL;
goto fail_findnode;
}
/* 2, 获取LED所对应的GPIO */
gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpios", 0);
if(gpioled.led_gpio < 0) {
printk("can't find led gpio\r\n");
ret = -EINVAL;
goto fail_findnode;
}
printk("led gpio num = %d\r\n", gpioled.led_gpio);
/* 3,申请IO */
ret = gpio_request(gpioled.led_gpio, "led-gpio");
if (ret) {
printk("Failed to request the led gpio\r\n");
ret = -EINVAL;
goto fail_findnode;
}
/* 4,使用IO,设置为输出 */
ret = gpio_direction_output(gpioled.led_gpio, 1);
if (ret) {
goto fail_setoutput;
}
/* 5,输出底电平,点亮LED灯*/
gpio_set_value(gpioled.led_gpio, 0);
return 0;
fail_setoutput:
gpio_free(gpioled.led_gpio);
fail_findnode:
return ret;
}
/* 驱动出口函数 */
static void __exit led_exit(void)
{
/* 关灯 */
gpio_set_value(gpioled.led_gpio, 1);
/* 注销字符设备驱动 */
cdev_del(&gpioled.cdev);
unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);
device_destroy(gpioled.class, gpioled.devid);
class_destroy(gpioled.class);
/* 释放IO */
gpio_free(gpioled.led_gpio);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zuozhongkai");
第41行,在设备结构体gpioled_dev中加入led_gpio这个成员变量,此成员变量保存LED等所使用的GPIO编号。
第55行,将设备结构体变量gpioled设置为filp的私有数据private_data。
第85行,通过读取filp的private_data成员变量来得到设备结构体变量,也就是gpioled。这种将设备结构体设置为filp私有数据的方法在Linux内核驱动里面非常常见。
第96、97行,直接调用gpio_set_value函数来向GPIO写入数据,实现开/关LED的效果。不需要我们直接操作相应的寄存器。
第133行,获取节点“/gpioled”。
第142行,通过函数of_get_named_gpio函数获取LED所使用的LED编号。相当于将gpioled节点中的“led-gpio”属性值转换为对应的LED编号。
第150行,调用函数gpio_direction_output设置GPIO1_IO03这个GPIO为输出,并且默认高电平,这样默认就会关闭LED灯。
可以看出gpioled.c文件中的内容和第四十四章的dtsled.c差不多,只是取消掉了配置寄存器的过程,改为使用Linux内核提供的API函数。在GPIO操作上更加的规范化,符合Linux代码框架,而且也简化了GPIO驱动开发的难度,以后我们所有例程用到GPIO的地方都采用此方法。
需要注意的是,使用gpio框架,不再使用ioremap来,地址映射,gpio_request时,就映射了,应该。
需要注意的是,使用gpio框架,不再使用ioremap来,地址映射,gpio_request时,就映射了,应该。
需要注意的是,使用gpio框架,不再使用ioremap来,地址映射,gpio_request时,就映射了,应该。
app测试代码
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
/*
*argc:应用程序参数个数
*argv[]:具体的参数内容,字符串形式
*./ledAPP <filename> <0:1> 0表示关灯,1表示开灯
* ./ledAPP /dev/dtsled 0 关灯
* ./ledAPP /dev/dtsled 1 开灯
*/
#define LEDOFF 0
#define LEDON 1
int main(int argc, char *argv[])
{
int fd, retvalue;
char *filename;
unsigned char databuf[1];
if(argc != 3) {
printf("Error Usage!\r\n");
return -1;
}
filename = argv[1];
fd = open(filename, O_RDWR);
if(fd < 0) {
printf("file %s open failed!\r\n", filename);
return -1;
}
databuf[0] = atoi(argv[2]); /* 将字符转换为数字 */
retvalue = write(fd, databuf, sizeof(databuf));
if(retvalue < 0) {
printf("LED Control Failed!\r\n");
close(fd);
return -1;
}
close(fd);
return 0;
}
运行测试
将编译出来的gpioled.ko和ledApp这两个文件拷贝到开发板,输入如下命令加载gpioled.ko驱动模块:
depmod //第一次加载驱动的时候需要运行此命令
modprobe gpioled.ko //加载驱动
驱动加载成功以后会在终端中输出一些信息
可以看出,gpioled这个节点找到了,并且GPIO1_IO03这个GPIO的编号为3。驱动加载成功以后就可以使用ledApp软件来测试驱动是否工作正常。
输入如下命令打开LED灯:
./ledApp /dev/gpioled 1 //打开LED灯
./ledApp /dev/gpioled 0 //关闭LED灯