之前的程序说到底只是对字符设备的一种简化写法,使用设备树进行寄存器的地址定义然后使用gpio子系统对寄存器进行设置。但是这些程序都是直接写了一个驱动对Linux进行操作,在实际的工程应用中这种做法的风险很大,如果有多个应用程序希望对同一个设备进行操作,此时会发生两种可能,一种是误操作,一种时堵塞。在实际应用中会有一个中间层负责调度,从而解决这个问题。
接下来我们要做的就是把新字符设备的驱动例程拆分为两部分:驱动程序、设备程序中间通过platform接口进行链接。
新建文件leddevice.c,用于存放设备程序,其中主要为寄存器相关程序。
头文件需要添加 :
#include<linux/platform_device.h>
然后将寄存器相关复制进去:
#definePERIPH_BASE (0x40000000) //查阅手册第158页,外设总线地址为AB1-AB5其地址为0x4000 0000 – 0X5FFF FFFF。
#defineMPU_AHB4_PERIPH_BASE (PERIPH_BASE+ 0x10000000) //查阅手册第158页,gpios总线AHB4,地址为0X5000 0000 - 0x5001 FFFF。
#defineRCC_BASE (MPU_AHB4_PERIPH_BASE + 0x0000) //查阅手册第162页,gpios时钟,地址为0X50000000-0X5000 0FFFF 。
#defineRCC_MP_AHB4ENSETR (RCC_BASE+ 0XA28) //查阅手册第866页,RCC_MP_AHB4ENSETR寄存器的偏移地址为0XA28,其负责GPIOA-K的时钟开关
#defineGPIOI_BASE (MPU_AHB4_PERIPH_BASE+ 0x2000) //查阅手册第162页,GPIOA地址为0X5000 2000-0X5000 23FF 。
//接下来是GPIO相关的寄存器,查阅手册第1086页,可以查阅到各个寄存器的偏移地址。
#defineGPIOI_MODER (GPIOI_BASE + 0x0000)
#defineGPIOI_OTYPER (GPIOI_BASE + 0x0004)
#defineGPIOI_OSPEEDR (GPIOI_BASE + 0x0008)
#defineGPIOI_PUPDR (GPIOI_BASE + 0x000C)
#defineGPIOI_BSRR (GPIOI_BASE+ 0x0018)
之后定义platform结构体用来调用寄存器:
staticstruct platform_device leddevice = {
.name = "xhy-led",
.id = -1,
.dev = {
.release = &led_release,
},
.num_resources =ARRAY_SIZE(led_resources),
.resource = led_resources,
};
结构体中调用的与两个,一个release和一个resources。&led_release为总线释放:
staticvoid led_release(struct device *dev)
{
printk("led devicereleased!\r\n");
}
resource时一个resources数组用于存放寄存器地址。
staticstruct resource led_resources[] = {
[0] = {
.start = RCC_MP_AHB4ENSETR,
.end = (RCC_MP_AHB4ENSETR + REGISTER_LENGTH - 1),
.flags = IORESOURCE_MEM,
},
[1] = {
.start = GPIOI_MODER,
.end = (GPIOI_MODER + REGISTER_LENGTH - 1),
.flags = IORESOURCE_MEM,
},
[2] = {
.start = GPIOI_OTYPER,
.end = (GPIOI_OTYPER + REGISTER_LENGTH - 1),
.flags = IORESOURCE_MEM,
},
[3] = {
.start = GPIOI_OSPEEDR,
.end = (GPIOI_OSPEEDR + REGISTER_LENGTH - 1),
.flags = IORESOURCE_MEM,
},
[4] = {
.start = GPIOI_PUPDR,
.end = (GPIOI_PUPDR + REGISTER_LENGTH - 1),
.flags = IORESOURCE_MEM,
},
[5] = {
.start = GPIOI_BSRR,
.end = (GPIOI_BSRR + REGISTER_LENGTH - 1),
.flags = IORESOURCE_MEM,
},
};
接下来时接口程序:leddevice_init和leddevice_exit,分别是platform的egister和unregister:
staticint __init leddevice_init(void)
{
returnplatform_device_register(&leddevice);
}
staticvoid __exit leddevice_exit(void)
{
platform_device_unregister(&leddevice);
}
设备层的程序写好后需要由驱动程序来调用设备程序,驱动程序中的设备寄存器地址设置删除掉,.release由于在设备程序中已定义所以也需要删除。
接口程序和设备驱动一样为platform的egister和unregister,两程序一模一样。
对应设备程序的platform结构体这里也需要定义一个platform结构体:
staticstruct platform_driver led_driver = {
.driver ={
.name = "xhy-led",
},
.probe =led_probe,
.remove =led_remove,
};//platform驱动结构体
.name和设备程序中一至,这里定义了两个函数:led_probe和led_remove,
其中led_remove存放原来的leddevice_exit程序,也就是取消映射以及设备的注销。led_probe放原来的leddevice_init用于对设备的初始化。
这里需要改动的时在寄存器映射之前需要从设备程序的leddevice中读取6个寄存器的地址,之后才能把地址映射出来:‘
for (i =0; i < 6; i++) {
ledsource =platform_get_resource(dev, IORESOURCE_MEM, i); /* 依次MEM类型资源 */
if (!ledsource) {
dev_err(&dev->dev,"No MEM resource for always on\n");
return -ENXIO;
}
ressize =resource_size(ledsource);
}
/*寄存器地址映射 */
MPU_AHB4_PERIPH_RCC_PI= ioremap(ledsource[0]->start, ressize[0]);
GPIOI_MODER_PI =ioremap(ledsource[1]->start, ressize[1]);
GPIOI_OTYPER_PI= ioremap(ledsource[2]->start, ressize[2]);
GPIOI_OSPEEDR_PI =ioremap(ledsource[3]->start, ressize[3]);
GPIOI_PUPDR_PI =ioremap(ledsource[4]->start, ressize[4]);
GPIOI_BSRR_PI =ioremap(ledsource[5]->start, ressize[5]);
编译以及测试
Ubuntu编译的时候需要注意由于这次需要编译两个程序,所以需要进行修改:
obj-m :=led.o //原来的编译命令
obj-m +=leddevice.o //后面加上另一个需要编译的程序
编译结束后将led.ko和 leddevice.ko都复制到板子中
cpledApp led.ko leddevice.ko /home/helloxhy/nfs/lib/modules/5.4.56/ -rf
之后在板子上跑程序,需要注意的时这次需要加载led和 leddevice两个驱动:
depmod
modprobeled
modprobeleddevice
./ledApp /dev/newled 0
./ledApp /dev/newled1