在上面一章我们借助Linux驱动分离和分层的概念引出来驱动——总线——设备的概念,然后引出来了platform这种基于设备驱动模型的驱动架构,我们下面通过使用来演示下platform架构是怎么使用的。
前面说过,platform驱动架构的设备端分为支持设备树和不支持设备树两种模式,下面我们先看看如何不借助设备树实现platform框架的驱动构建。
platform设备框架
我们先完成platform设备的基础框架。因为没有设备树,我们需要在驱动代码中使用platform_device结构体来描述我们需要操作都设备。但是platform_device结构体里的成员变量不是所有的我们都需要指定,这里只需要指定我们需要对内容就行了。
1 struct platform_device leddevice = {
2 .name = "imx6ull-led",
3 .id = -1, //-1表示设备无ID
4 .dev = {
5 .release = leddevice_release,
6 },
7 .num_resources = ARRAY_SIZE(led_resources), //资源大小
8 .resource = led_resources,
9 };
10
11 static int __init leddevice_init(void)
12 {
13 //注册platform设备
14 return platform_device_register(&leddevice);
15 }
16
17 static void __exit leddevice_exit(void)
18 {
19 platform_device_unregister(&leddevice);
20 }
21
22 module_init(leddevice_init);
23 module_exit(leddevice_exit);
24 MODULE_LICENSE("GPL");
25 MODULE_AUTHOR("ZZQ");
注意一下我们在加载模块的函数中(11行)使用了plotform_device_register函数来将新的设备注册到内核里,函数的参数就是我们定义的platform_device结构体变量。同样在卸载模块时我们也要使用platform_device_unregister函数将相关资源释放掉。
设备资源
我们这次使用LED作为驱动对应的设备,在前面讲Linux驱动的时候,我们操作LED使用了5个GPIO相关的寄存器,这5个物理寄存器就是我们需要对硬件资源
1 /**
2 * @brief 寄存器物理地址
3 *
4 */
5 #define CCM_CCGR1_BASE (0X020C406C)
6 #define SW_MUX_GPIO1_IO03_BASE (0X020E0068)
7 #define SW_PAD_GPIO1_IO03_BASE (0X020E02F4)
8 #define GPIO1_GDIR_BASE (0X0209C004)
9 #define GPIO1_DR_BASE (0X0209C000)
10
11 #define REGSITER_LENGTH 4
12
13 //5个内存段
14 static struct resource led_resources[] = {
15 [0] = {
16 .start = CCM_CCGR1_BASE,
17 .end = CCM_CCGR1_BASE + REGSITER_LENGTH -1,
18 .flags = IORESOURCE_MEM,
19 },
20 [1] = {
21 .start = SW_MUX_GPIO1_IO03_BASE,
22 .end = SW_MUX_GPIO1_IO03_BASE + REGSITER_LENGTH -1,
23 .flags = IORESOURCE_MEM,
24 },
25 [2] = {
26 .start = SW_PAD_GPIO1_IO03_BASE,
27 .end = SW_PAD_GPIO1_IO03_BASE + REGSITER_LENGTH -1,
28 .flags = IORESOURCE_MEM,
29 },
30 [3] = {
31 .start = GPIO1_GDIR_BASE,
32 .end = GPIO1_GDIR_BASE + REGSITER_LENGTH -1,
33 .flags = IORESOURCE_MEM,
34 },
35 [4] = {
36 .start = GPIO1_DR_BASE,
37 .end = GPIO1_DR_BASE + REGSITER_LENGTH -1,
38 .flags = IORESOURCE_MEM,
39 },
40 };
我们需要将操作的寄存器实际物理地址拿过来,还有个第11行的宏REGISTER_LENGTH表示我们一个寄存器是32位的占用了4个字节。这个资源的定义是在结构体resources里(include/linux/ioport.h)
1 /*
2 * Resources are tree-like, allowing
3 * nesting etc..
4 */
5 struct resource {
6 resource_size_t start;
7 resource_size_t end;
8 const char *name;
9 unsigned long flags;
10 struct resource *parent, *sibling, *child;
11 };
主要用到成员就是start,即资源起始地址,end,资源结束地址,对于内存类型的资源,就是内存的起始和结束地址。还有flags,就是资源类型在下面定义了好多宏可以供我们使用,我们在程序中使用的IORESOURCE_REG就告诉我们该资源属于寄存器相关的,还有其他类型的。
#define IORESOURCE_IRQ 0x00000400
#define IORESOURCE_DMA 0x00000800
#define IORESOURCE_BUS 0x00001000
上面的就是总线类型的、DMA的还有中断类型
如果我们只有一个寄存器或者一段连续的寄存器进行操作,就可以定义一个resource变量,但是可以注意到LED操作的一批寄存器地址是不相连的,所以定义了一个结构体数组来表示。一般使用中,我们都是用结构体数组来表示可用资源的。
在上面的数组中,每个元素都是1个物理寄存器,寄存器结束地址是在起始地址上加了长度+3(起始地址为0时,0~3一共4个字节表示1个寄存器),还有flag是表示资源为寄存器类型。
platform设备结构体
现在回头看看我们声明的platform_device变量
1 struct platform_device leddevice = {
2 .name = "imx6ull-led",
3 .id = -1, //-1表示设备无ID
4 .dev = {
5 .release = leddevice_release,
6 },
7 .num_resources = ARRAY_SIZE(led_resources), //资源大小
8 .resource = led_resources,
9 };
这里只声明了比较重要的几个成员
name是设备的名称,驱动就是通过这个name来匹配设备的。所以这个name和驱动里的name必须一致。
id作用我暂时还不了解,但是-1表示该设备无ID
dev是device结构体,一般来说我们需要对release变量绑定一个函数,表示释放platform_device的时候执行的函数。我们可以简单定义一个函数看一下执行过程
1 void leddevice_release(struct device *dev)
2 {
3 printk("device release\r\n");
4 }
重要的是后面两个和资源有关系的成员,num_resource表示资源的数量,我们用来一个函数ARRAY_SIZE获取了指定的资源对象的大小,对这段程序来说就是led_resources数组的大小。
最后就是使用的资源。我们用了5个寄存器,就把前面写Linux驱动时候LED使用的5个寄存器的宏定义拿过来定义在resource结构体里就行了。
这样就完成了基础的platform设备信息的构建

/**
* @file leddevice.c
* @author your name ([email protected])
* @brief platform设备基础框架
* @version 0.1
* @date 2022-08-17
*
* @copyright Copyright (c) 2022
*
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/types.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>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/fcntl.h>
#include <linux/ide.h>
#include <linux/platform_device.h>
/**
* @brief 寄存器物理地址
*
*/
#define CCM_CCGR1_BASE (0X020C406C)
#define SW_MUX_GPIO1_IO03_BASE (0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE (0X020E02F4)
#define GPIO1_GDIR_BASE (0X0209C004)
#define GPIO1_DR_BASE (0X0209C000)
#define REGSITER_LENGTH 4
void leddevice_release(struct device *dev)
{
printk("device release\r\n");
}
//5个内存段
static struct resource led_resources[] = {
[0] = {
.start = CCM_CCGR1_BASE,
.end = CCM_CCGR1_BASE + REGSITER_LENGTH -1,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = SW_MUX_GPIO1_IO03_BASE,
.end = SW_MUX_GPIO1_IO03_BASE + REGSITER_LENGTH -1,
.flags = IORESOURCE_MEM,
},
[2] = {
.start = SW_PAD_GPIO1_IO03_BASE,
.end = SW_PAD_GPIO1_IO03_BASE + REGSITER_LENGTH -1,
.flags = IORESOURCE_MEM,
},
[3] = {
.start = GPIO1_GDIR_BASE,
.end = GPIO1_GDIR_BASE + REGSITER_LENGTH -1,
.flags = IORESOURCE_MEM,
},
[4] = {
.start = GPIO1_DR_BASE,
.end = GPIO1_DR_BASE + REGSITER_LENGTH -1,
.flags = IORESOURCE_MEM,
},
};
struct platform_device leddevice = {
.name = "imx6ull-led",
.id = -1, //-1表示设备无ID
.dev = {
.release = leddevice_release,
},
.num_resources = ARRAY_SIZE(led_resources), //资源大小
.resource = led_resources,
};
static int __init leddevice_init(void)
{
//注册platform设备
return platform_device_register(&leddevice);
}
static void __exit leddevice_exit(void)
{
platform_device_unregister(&leddevice);
}
module_init(leddevice_init);
module_exit(leddevice_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ZZQ");
platform设备基础框架
这个设备框架完成好以后,修改make生成ko模块文件,复制到目标目录下加载一下,可以看到在bus目录下有我们定义的设备(imx6ull-led),对应device的name属性
这时候就完成了设备端的构建,下面要我们来做驱动端。
platform驱动框架
在完成platform的设备端以后,我们要学习驱动的构建。
1 /**
2 * @file leddriver.c
3 * @author your name ([email protected])
4 * @brief platfrom驱动框架
5 * @version 0.1
6 * @date 2022-08-18
7 *
8 * @copyright Copyright (c) 2022
9 *
10 */
11 #include <linux/module.h>
12 #include <linux/kernel.h>
13 #include <linux/init.h>
14 #include <linux/fs.h>
15 #include <linux/uaccess.h>
16 #include <linux/io.h>
17 #include <linux/types.h>
18 #include <linux/cdev.h>
19 #include <linux/device.h>
20 #include <linux/of.h>
21 #include <linux/of_address.h>
22 #include <linux/of_irq.h>
23 #include <linux/gpio.h>
24 #include <linux/of_gpio.h>
25 #include <linux/irq.h>
26 #include <linux/interrupt.h>
27 #include <linux/fcntl.h>
28 #include <linux/ide.h>
29 #include <linux/platform_device.h>
30
31
32 static int led_probe(struct platform_device *dev)
33 {
34 printk("led driver device match\r\n");
35 return 0;
36 }
37
38 static int led_remove(struct platform_device *dev)
39 {
40 printk("led driver remove\r\n");
41 return 0;
42 }
43
44
45 static struct platform_driver leddriver = {
46 .driver = {
47 .name = "imx6ull-led", //驱动name,在和dev匹配时候使用
48 },
49 .probe = led_probe,
50 .remove = led_remove,
51 };
52
53 static int __init leddriver_init(void)
54 {
55 //注册platform驱动
56 return platform_driver_register(&leddriver);
57 }
58
59 static void __exit leddriver_exit(void)
60 {
61 //卸载platform驱动
62 platform_driver_unregister(&leddriver);
63 }
64
65 module_init(leddriver_init);
66 module_exit(leddriver_exit);
67 MODULE_LICENSE("GPL");
68 MODULE_AUTHOR("ZZQ");
驱动的框架很简单,主要就是platform_driver的定义,先把驱动的结构体拿出来
1 static struct platform_driver leddriver = {
2 .driver = {
3 .name = "imx6ull-led", //驱动name,在和dev匹配时候使用
4 },
5 .probe = led_probe,
6 .remove = led_remove,
7 };
没有什么复杂的,一个成员变量是device_driver结构体类型的driver,主要是用来个name成员来和device里的name来进行匹配,所以值必须是imx6ull-led,还有个probe,是在匹配完成后执行的函数,remove是驱动卸载后执行的函数。
make以后加载一下模块,看看效果。下面是分别加载设备和驱动,以及分别卸载设备和驱动的效果
在卸载驱动和设备的时候都会执行驱动里的remove函数,卸载device的时候会执行device文件里的leddevice_exit函数,所以多打印了一行信息。
驱动端资源获取
因为我们在设备中定义了寄存器资源,也就是点亮LED时候需要用到的5个寄存器,在驱动中是要获取到这5个寄存器地址的。在驱动中有个函数是实现这个功能的
extern struct resource *platform_get_resource(struct platform_device *,
unsigned int, unsigned int);
参数platform是要获取资源的dev。第二个整形参数是要获取资源的类型(对应资源里的flags成员属性)。第三个整形数据是要获取指定类型数据的索引。
1 //内存映射后的地址指针
2 static void __iomem *IMX6UL_CCM_CCGR1;
3 static void __iomem *IMX6UL_SW_MUX_GPIO1_IO03;
4 static void __iomem *IMX6UL_SW_PAD_GPIO1_IO03;
5 static void __iomem *IMX6UL_GPIO1_GDIR;
6 static void __iomem *IMX6UL_GPIO1_DR;
7
8 int i=0;
9 struct resource *ledsource[5];
10
11 for(i;i<5;i++)
12 {
13 ledsource[i] = platform_get_resource(dev,IORESOURCE_MEM,i);
14 if(ledsource[i] == NULL)
15 {
16 return -EINVAL;
17 }
18 }
19
20 IMX6UL_CCM_CCGR1 = ioremap(ledsource[0]->start,resource_size(ledsource[0]));
21 IMX6UL_SW_MUX_GPIO1_IO03 = ioremap(ledsource[1]->start,resource_size(ledsource[1]));
22 IMX6UL_SW_PAD_GPIO1_IO03 = ioremap(ledsource[2]->start,resource_size(ledsource[2]));
23 IMX6UL_GPIO1_GDIR = ioremap(ledsource[3]->start,resource_size(ledsource[3]));
24 IMX6UL_GPIO1_DR = ioremap(ledsource[4]->start,resource_size(ledsource[3]));
因为我们用的寄存器资源一共5组,就用for循环5次获取了对应的寄存器。最后5行是在拿到了物理寄存器地址以后进行映射拿到映射后的地址指针。在映射的时候,使用了source结构体里的start元素,表示内存起始地址,还有个函数可以直接用来获取资源的大小
1 static inline resource_size_t resource_size(const struct resource *res)
2 {
3 return res->end - res->start + 1;
4 }
当然我们也可以直接用每个资源成员变量end-start+1的方法获取到资源的大小。
在前面说过,这个我们主要去编写的驱动是probe里面要执行的内容,所以获取资源、设备初始化设么的都要在这个probe函数里。并且这个platform框架是不包含dev下面节点生成的功能的,我们还需要把以前那个字符设备框架放过来,可以单做一个函数放在前面,然后再probe里面调用一下就可以了,然后模块卸载的过程,包括ioremap资源的释放,可以都放在remove 对应的函数中。因为字符设备框架还涉及到文件操作集合,我就把前面讲linux驱动一开始写led那个部分的文件操作集合直接拿过来了

/**
* @file leddriver.c
* @author your name ([email protected])
* @brief platfrom驱动框架
* @version 0.1
* @date 2022-08-18
*
* @copyright Copyright (c) 2022
*
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/types.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>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/fcntl.h>
#include <linux/ide.h>
#include <linux/platform_device.h>
#define LEDOFF 0
#define LEDON 1
#define DEV_NAME "platform_led"
#define DEV_COUNT 1
//内存映射后的地址指针
static void __iomem *IMX6UL_CCM_CCGR1;
static void __iomem *IMX6UL_SW_MUX_GPIO1_IO03;
static void __iomem *IMX6UL_SW_PAD_GPIO1_IO03;
static void __iomem *IMX6UL_GPIO1_GDIR;
static void __iomem *IMX6UL_GPIO1_DR;
struct new_dev
{
dev_t dev_id;
int major;
int minor;
struct class *class;
struct device *device;
struct cdev cdev;
struct device_node *dev_nd;
int dev_gpio;
};
struct new_dev dev;
static int led_open(struct inode *inode, struct file *filp)
{
printk("dev open!\r\n");
return 0;
}
static ssize_t led_read(struct file *filp,
__user char *buf,
size_t count,
loff_t *ppos)
{
int ret = 0;
printk("dev read data!\r\n");
if (ret == 0){
return 0;
}
else{
printk("kernel read data error!");
return -1;
}
}
static void led_switch(u8 sta)
{
printk("led sta change %d\r\n",sta);
u32 val = 0;
if(sta == LEDON){
val = readl(IMX6UL_GPIO1_DR);
val &= ~(1<<3);
writel(val,IMX6UL_GPIO1_DR);
}
else if(sta == LEDOFF){
val = readl(IMX6UL_GPIO1_DR);
val |= (1<<3);
writel(val,IMX6UL_GPIO1_DR);
}
}
/**
* @brief 改变LED状态
*
* @param file
* @param buf
* @param count
* @param ppos
* @return ssize_t
*/
static ssize_t led_write(struct file *file,
const char __user *buf,
size_t count,
loff_t *ppos)
{
int ret = 0;
printk("led write called\r\n");
unsigned char databuf[1]; //待写入的参数
ret = copy_from_user(databuf,buf,count); //获取从用户空间传递来的参数
if (ret == 0){
led_switch(databuf[0]); //根据参数改变LED状态
}
else{
printk("kernelwrite err!\r\n");
return -EFAULT;
}
}
static struct file_operations dev_fops= {
.owner = THIS_MODULE,
.open = led_open,
// .release = led_release,
.read = led_read,
.write = led_write,
};
static int dev_init(void){
int ret = 0;
dev.major =0; //主设备号设置为0,由系统分配设备号
/*程序中未经指定设备号,直接注册设备*/
if(dev.major){
dev.dev_id = MKDEV(dev.major,0); //调用MKDEV函数构建设备号
ret = register_chrdev_region(dev.dev_id,1,DEV_NAME); //注册设备
}
/*程序中未指定设备号,申请设备号*/
else{
ret = alloc_chrdev_region(&dev.dev_id,0,1,DEV_NAME);
dev.major = MAJOR(dev.dev_id);
dev.minor = MINOR(dev.dev_id);
}
if(ret<0){
printk("new device region err!\r\n");
return -1;
}
printk("dev_t = %d,major = %d,minor = %d\r\n",dev.dev_id,dev.major,dev.minor);
dev.cdev.owner = THIS_MODULE;
cdev_init(&dev.cdev, &dev_fops);
ret = cdev_add(&dev.cdev,dev.dev_id, 1);
dev.class = class_create(THIS_MODULE,DEV_NAME);
if (IS_ERR(dev.class))
{return PTR_ERR(dev.class);}
dev.device = device_create(dev.class, NULL,dev.dev_id,NULL,DEV_NAME);
if (IS_ERR(dev.device))
{return PTR_ERR(dev.device);}
printk(&dev.device);
return 0;
}
static int led_probe(struct platform_device *dev)
{
printk("led driver device match\r\n");
int i=0;
int ret = 0;
unsigned int val = 0;
struct resource *ledsource[5];
for(i;i<5;i++){
ledsource[i] = platform_get_resource(dev,IORESOURCE_MEM,i);
if(ledsource[i] == NULL){
return -EINVAL;
}
}
IMX6UL_CCM_CCGR1 = ioremap(ledsource[0]->start,resource_size(ledsource[0]));
IMX6UL_SW_MUX_GPIO1_IO03 = ioremap(ledsource[1]->start,resource_size(ledsource[1]));
IMX6UL_SW_PAD_GPIO1_IO03 = ioremap(ledsource[2]->start,resource_size(ledsource[2]));
IMX6UL_GPIO1_GDIR = ioremap(ledsource[3]->start,resource_size(ledsource[3]));
IMX6UL_GPIO1_DR = ioremap(ledsource[4]->start,resource_size(ledsource[3]));
val = readl(IMX6UL_CCM_CCGR1); //读取CCM_CCGR1的值
val &= ~(3<<26); //清除bit26、27
val |= (3<<26); //bit26、27置1
writel(val, IMX6UL_CCM_CCGR1);
printk("CCM init finished!\r\n");
/*GPIO初始化*/
writel(0x5, IMX6UL_SW_MUX_GPIO1_IO03);
writel(0x10B0, IMX6UL_SW_PAD_GPIO1_IO03);
val = readl(IMX6UL_GPIO1_GDIR);
val |= 1<<3; //bit3=1,设置为输出
writel(val, IMX6UL_GPIO1_GDIR);
val = readl(IMX6UL_GPIO1_DR);
val &= ~(1<<3);
writel(val,IMX6UL_GPIO1_DR);
dev_init();
return 0;
}
static int led_remove(struct platform_device *plt_dev)
{
printk("led driver remove\r\n");
iounmap(IMX6UL_CCM_CCGR1);
iounmap(IMX6UL_SW_MUX_GPIO1_IO03);
iounmap(IMX6UL_SW_PAD_GPIO1_IO03);
iounmap(IMX6UL_GPIO1_DR);
iounmap(IMX6UL_GPIO1_GDIR);
cdev_del(&dev.cdev);
//注销设备号
unregister_chrdev_region(dev.dev_id,1);
device_destroy(dev.class,dev.dev_id);
class_destroy(dev.class);
return 0;
}
static struct platform_driver leddriver = {
.driver = {
.name = "imx6ull-led", //驱动name,在和dev匹配时候使用
},
.probe = led_probe,
.remove = led_remove,
};
static int __init leddriver_init(void)
{
//注册platform驱动
return platform_driver_register(&leddriver);
}
static void __exit leddriver_exit(void)
{
//鞋子platform驱动
platform_driver_unregister(&leddriver);
}
module_init(leddriver_init);
module_exit(leddriver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ZZQ");
platform驱动led
整个驱动就是这样了,最后把操作led的APP也重新搞过来,在加载完成led的驱动和设备以后,就可以通过APP文件操作LED的点亮与熄灭了