Linux驱动开发(六)---树莓派配合硬件进行字符驱动开发

前文回顾

《Linux驱动开发(一)—环境搭建与hello world》
《Linux驱动开发(二)—驱动与设备的分离设计》
《Linux驱动开发(三)—设备树》
《Linux驱动开发(四)—树莓派内核编译》
《Linux驱动开发(五)—树莓派设备树配合驱动开发》
继续宣传一下韦老师的视频

70天30节Linux驱动开发快速入门系列课程【实战教学、技术讨论、直播答疑】

在这里插入图片描述

基础硬件知识

这里目的就是想通过驱动来配置GPIO的操作,算是能和硬件操作挂钩的最简单做法了,首先要把整个思路理清。
Linux驱动开发(六)---树莓派配合硬件进行字符驱动开发_第1张图片

先介绍一下树莓派的GPIO。
安装wiringPI,用来支持gpio命令,这个命令可以用来查看树莓派的所有引脚数据
Linux驱动开发(六)---树莓派配合硬件进行字符驱动开发_第2张图片
其中physical对应的就是开发板上的排插引脚

Linux驱动开发(六)---树莓派配合硬件进行字符驱动开发_第3张图片

如果你用wiringPI,那就需要关注
Linux驱动开发(六)---树莓派配合硬件进行字符驱动开发_第4张图片
我们这里是自己写驱动,所以关注的是芯片引脚
Linux驱动开发(六)---树莓派配合硬件进行字符驱动开发_第5张图片
树莓派的GPIO配置需要看三类寄存器,共6+2+2=10个地址可能会被用到。

  • GPFSEL0-5
    配置输入输出方向,这个配置挺有意思,pin脚的配置占用三个bit,共有54个pin脚,所以用了6个地址寄存器。
  • GPSET0-1
    配置高低电平
  • GPCLR0-1
    输出清除寄存器

详细的配置可以参考Linux底层驱动之树莓派IO口操作
解释的特别详细。推荐推荐。
Linux驱动开发(六)---树莓派配合硬件进行字符驱动开发_第6张图片

设备树编写

根据前面的介绍,我们如果要想修改某个节点,就需要指定相关的三个地址参数,那么我们在DTS中,就需要定义出这三个参数,然后我们的驱动就可以自动适配到引脚了,灵活性大大提高。

部分代码参考《树莓派开发—初识驱动开发》

我们也选择引脚pin17,需要计算出这个引脚的三个参数
GPFSEL,每三个bit负责一个引脚,所以每个寄存器最多提供10个引脚,那么pin17使用的是GPFSEL1
地址为0x7E20 0004

GPSET,每一个bit负责一个pin,所以使用的是GPSET0,地址为0x7E20 001C

GPCLR,也是每个bit负责一个pin,所以使用GPCLR0,地址是0x7E20 0028

Linux驱动开发(六)---树莓派配合硬件进行字符驱动开发_第7张图片
最后,我们要进行一个虚拟地址与硬件地址的映射,这就是之前说的在内核层面,程序只能访问虚拟地址,所以要将上图中的硬件地址,转化为虚拟地址。
通过cat /proc/iomem 查看。

root@raspberrypi:/home/sunjin# cat /proc/iomem 
00000000-37ffffff : System RAM
  00008000-00efffff : Kernel code
  01000000-011dfbeb : Kernel data
3f006000-3f006fff : dwc_otg
3f007000-3f007eff : 3f007000.dma dma@7e007000
3f00a000-3f00a023 : 3f100000.watchdog watchdog@7e100000
3f00b840-3f00b87b : 3f00b840.mailbox mailbox@7e00b840
3f00b880-3f00b8bf : 3f00b880.mailbox mailbox@7e00b880
3f100000-3f100113 : 3f100000.watchdog watchdog@7e100000
3f101000-3f102fff : 3f101000.cprman cprman@7e101000
3f104000-3f10400f : 3f104000.rng rng@7e104000
3f200000-3f2000b3 : 3f200000.gpio gpio@7e200000
3f201000-3f2011ff : serial@7e201000
  3f201000-3f2011ff : 3f201000.serial serial@7e201000
3f202000-3f2020ff : 3f202000.mmc mmc@7e202000
3f212000-3f212007 : 3f212000.thermal thermal@7e212000
3f215000-3f215007 : 3f215000.aux aux@7e215000
3f300000-3f3000ff : 3f300000.mmcnr mmcnr@7e300000
3f980000-3f98ffff : dwc_otg

可以查看到
3f200000-3f2000b3 : 3f200000.gpio gpio@7e200000
我们可以认为
3f200000表示的就是硬件地址7e200000
那么前面的三个硬件寄存器地址,就需要转化为
3f20 0004
3f20 001c
3f20 0028
所以最终的DTS可以写成
Linux驱动开发(六)---树莓派配合硬件进行字符驱动开发_第8张图片

编译DTB。烧写,然后看一下是否有device-tree

pgg@raspberrypi:~ $ ls /proc/device-tree/mygpio/
compatible  gpclr  gpfsel  gpset  name  pin  status

完全OK,不过事实证明,这块确实存在问题,看完你就明白了
Linux驱动开发(六)---树莓派配合硬件进行字符驱动开发_第9张图片

驱动编写

还是以前面的驱动为框架。同时参考前面的博客,首先修改一下probe函数,获取三个关键参数

static int led_probe(struct platform_device *pdev)
{	
	int minor;
	int i = 0;
	const char *tmp_str;

	struct resource *res;
		
	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);

	if (!pdev->dev.of_node)  /* 普通的platform_device */
	{
		printk("unsupport normal device\n");
		return -1;
	}
	else
	{
		unsigned int pinnum=0;
		
		of_property_read_u32(pdev->dev.of_node,  "gpfsel", GPFSEL);
		of_property_read_u32(pdev->dev.of_node,  "gpset", GPSET);
		of_property_read_u32(pdev->dev.of_node,  "gpclr", GPCLR);
		of_property_read_u32(pdev->dev.of_node,  "pin", &pinnum);


		//of_property_read_string(pdev->dev.of_node, "pin", &tmp_str);
		printk("pin[%d] =  0x%x 0x%x 0x%x\n",pinnum, GPFSEL,GPSET,GPCLR);
		minor = g_ledcnt;
		leds_desc[minor].pin =pinnum;
	}

	/* 记录引脚 */
	leds_desc[minor].minor = minor;

	/* 7.2 辅助信息 */
	/* 创建设备节点 */
	device_create(led_class, NULL, MKDEV(major, minor), NULL, "mygpio%d", minor); /* /dev/100ask_led0,1,... */
	
	platform_set_drvdata(pdev, &leds_desc[minor]);
	
	g_ledcnt++;
	
    return 0;
}

先编写一下,试试能不能获取到参数,编译模块,上传开发板!
Linux驱动开发(六)---树莓派配合硬件进行字符驱动开发_第10张图片

pgg@raspberrypi:~/work/dirver $ sudo insmod mygpio.ko 
pgg@raspberrypi:~/work/dirver $ dmesg 
[   69.034322] drivers/char/mygpio.c led_init line 169
[   69.034815] drivers/char/mygpio.c led_probe 89
[   69.034844] pin[17] =  0x3f200004 0x3f20001c 0x3f200028
pgg@raspberrypi:~/work/dirver $ ls /dev/mygpio0 
/dev/mygpio0

没问题,pin脚和三个关键参数就获取到了。设备也创建好了。那么就开始修改open和write函数吧
open函数

static int led_drv_open (struct inode *node, struct file *file)
{
	int minor = iminor(node);
	
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);

	/* 根据次设备号初始化LED */
	printk("init led pin 0x%x as output\n", leds_desc[minor].pin);

	int pin_num=(leds_desc[minor].pin%10)*3;
	
    *GPFSEL &= ~(0x6 << pin_num);
	*GPFSEL |= (0x1 << pin_num);
	
	return 0;
}

注意这里计算偏移,因为已经知道了是哪个寄存器,每个寄存器只负责10个pin,所以要取余。
write函数

static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
	int err;
	char status;
	struct inode *inode = file_inode(file);
	int minor = iminor(inode);
	int pin_num=leds_desc[minor].pin%32;
	
	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
	err = copy_from_user(&status, buf, 1);

	/* 根据次设备号和status控制LED */
	if(status == 1)
	{
		*GPSET |= 0x1<< pin_num;  //设置为1
		printk("set led pin %d as 1\n", leds_desc[minor].pin);

	}else if(status ==0)
	{
		*GPCLR |= 0x1<< pin_num; //清除
		printk("set led pin %d as 0\n", leds_desc[minor].pin);
	}
	else
	{
		printk("undo\n");
	}

	return 1;
}

注意这里取余要取32的余数。

加上用户程序,统一测试一下。
Linux驱动开发(六)---树莓派配合硬件进行字符驱动开发_第11张图片
就爆了个大错
Linux驱动开发(六)---树莓派配合硬件进行字符驱动开发_第12张图片
这个虚拟地址看来不对。没关系,继续修改。

重新修理

DTS不变
Linux驱动开发(六)---树莓派配合硬件进行字符驱动开发_第13张图片

然后在获取参数后,转化为虚拟地址。在probe函数中,进行如下修改

of_property_read_u32(pdev->dev.of_node,  "gpfsel", &a);
of_property_read_u32(pdev->dev.of_node,  "gpset", &b);
of_property_read_u32(pdev->dev.of_node,  "gpclr", &c);
of_property_read_u32(pdev->dev.of_node,  "pin", &pinnum);


//of_property_read_string(pdev->dev.of_node, "pin", &tmp_str);
printk("pin[%d] =  0x%x 0x%x 0x%x\n",pinnum, a,b,c);

GPFSEL= (volatile unsigned int *)ioremap(a,4);
GPSET = (volatile unsigned int *)ioremap(b,4);
GPCLR = (volatile unsigned int *)ioremap(c,4);

重新更新dtb,更新模块,再次测试

pgg@raspberrypi:~/work/dirver $ sudo insmod mygpio.ko 
pgg@raspberrypi:~/work/dirver $ sudo ./userapp /dev/mygpio0 on
pgg@raspberrypi:~/work/dirver $ sudo ./userapp /dev/mygpio0 off
pgg@raspberrypi:~/work/dirver $ dmesg

[   71.570789] drivers/char/mygpio.c led_init line 192
[   71.571042] drivers/char/mygpio.c led_probe 106
[   71.571056] pin[17] =  0x3f200004 0x3f20001c 0x3f200028
[   82.604078] drivers/char/mygpio.c led_drv_open line 50
[   82.604116] init led pin 0x11 as output
[   82.604144] drivers/char/mygpio.c led_drv_write line 71
[   82.604160] set led pin 17 as 1
[   86.368982] drivers/char/mygpio.c led_drv_open line 50
[   86.369019] init led pin 0x11 as output
[   86.369047] drivers/char/mygpio.c led_drv_write line 71
[   86.369063] set led pin 17 as 0

完美没毛病。不过到底引脚是不是1,手里没有万用表,只能用舌头试一下了。
Linux驱动开发(六)---树莓派配合硬件进行字符驱动开发_第14张图片

回头望月

那么回头看一下,之前根据iomem查看的地址,为什么不对,那么我们打印出真实有效的地址看一下吧

GPFSEL= (volatile unsigned int *)ioremap(a,4);
GPSET = (volatile unsigned int *)ioremap(b,4);
GPCLR = (volatile unsigned int *)ioremap(c,4);

printk("real addr 0x%x 0x%x 0x%x\n",(unsigned int)GPFSEL,(unsigned int)GPSET,(unsigned int)GPCLR);

打印发现
[ 71.571072] real addr 0xb88e2004 0xb88e401c 0xb88e6028
所以完整的内存访问方式应该是

Linux驱动开发(六)---树莓派配合硬件进行字符驱动开发_第15张图片
从文档,到程序,需要三步转化。
这个过程很重要

结束语

今天的社评暂停一下,审核不过哦,哈哈
Linux驱动开发(六)---树莓派配合硬件进行字符驱动开发_第16张图片

Linux驱动开发(六)---树莓派配合硬件进行字符驱动开发_第17张图片

你可能感兴趣的:(linux知识,驱动开发,操作系统,驱动开发,linux,树莓派,GPIO)