Linux驱动.之pinctl和gpio子系统驱动框架,不带设备树和带设备树(一)

本篇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的值(01int gpio_get_value(unsigned int gpio);
gpio:要获取的gpio标号
返回值:0,成功,失败返回负值。

6、 gpio_set_value函数

作用:此函数用于获取某个GPIO的值(01void 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 = <&reg_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灯

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值