《Linux驱动开发(一)—环境搭建与hello world》
《Linux驱动开发(二)—驱动与设备的分离设计》
《Linux驱动开发(三)—设备树》
《Linux驱动开发(四)—树莓派内核编译》
《Linux驱动开发(五)—树莓派设备树配合驱动开发》
继续宣传一下韦老师的视频
70天30节Linux驱动开发快速入门系列课程【实战教学、技术讨论、直播答疑】
这里目的就是想通过驱动来配置GPIO的操作,算是能和硬件操作挂钩的最简单做法了,首先要把整个思路理清。
先介绍一下树莓派的GPIO。
安装wiringPI,用来支持gpio命令,这个命令可以用来查看树莓派的所有引脚数据
其中physical对应的就是开发板上的排插引脚
如果你用wiringPI,那就需要关注
我们这里是自己写驱动,所以关注的是芯片引脚
树莓派的GPIO配置需要看三类寄存器,共6+2+2=10个地址可能会被用到。
详细的配置可以参考Linux底层驱动之树莓派IO口操作
解释的特别详细。推荐推荐。
根据前面的介绍,我们如果要想修改某个节点,就需要指定相关的三个地址参数,那么我们在DTS中,就需要定义出这三个参数,然后我们的驱动就可以自动适配到引脚了,灵活性大大提高。
部分代码参考《树莓派开发—初识驱动开发》
我们也选择引脚pin17,需要计算出这个引脚的三个参数
GPFSEL,每三个bit负责一个引脚,所以每个寄存器最多提供10个引脚,那么pin17使用的是GPFSEL1
地址为0x7E20 0004
GPSET,每一个bit负责一个pin,所以使用的是GPSET0,地址为0x7E20 001C
GPCLR,也是每个bit负责一个pin,所以使用GPCLR0,地址是0x7E20 0028
最后,我们要进行一个虚拟地址与硬件地址的映射,这就是之前说的在内核层面,程序只能访问虚拟地址,所以要将上图中的硬件地址,转化为虚拟地址。
通过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可以写成
编译DTB。烧写,然后看一下是否有device-tree
pgg@raspberrypi:~ $ ls /proc/device-tree/mygpio/
compatible gpclr gpfsel gpset name pin status
还是以前面的驱动为框架。同时参考前面的博客,首先修改一下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;
}
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的余数。
加上用户程序,统一测试一下。
就爆了个大错
这个虚拟地址看来不对。没关系,继续修改。
然后在获取参数后,转化为虚拟地址。在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,手里没有万用表,只能用舌头试一下了。
那么回头看一下,之前根据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
所以完整的内存访问方式应该是