Linux驱动开发十三.platform设备驱动——2.无设备树的platform驱动
在上面一章我们借助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设备信息的构建
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/** * @file leddevice.c * @author your name (you@domain.com) * @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");
这个设备框架完成好以后,修改make生成ko模块文件,复制到目标目录下加载一下,可以看到在bus目录下有我们定义的设备(imx6ull-led),对应device的name属性
这时候就完成了设备端的构建,下面要我们来做驱动端。
platform驱动框架
在完成platform的设备端以后,我们要学习驱动的构建。
1 /** 2 * @file leddriver.c 3 * @author your name (you@domain.com) 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那个部分的文件操作集合直接拿过来了
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/** * @file leddriver.c * @author your name (you@domain.com) * @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");
整个驱动就是这样了,最后把操作led的APP也重新搞过来,在加载完成led的驱动和设备以后,就可以通过APP文件操作LED的点亮与熄灭了