linux驱动(九)platform驱动模型详解,以及基于platform驱动模型的led驱动
2025-01-07 来源:cnblogs
参考:
http://blog.csdn.net/qq_28992301/article/details/52385518
http://blog.csdn.net/zoe6553/article/details/6372445
http://blog.chinaunix.net/uid-25014876-id-111745.html
1:什么是platform总线?
platform总线是区别于实体总线USB、 I2C、SPI 、PIC总线的虚拟总线,一些usb设备选址的话需要通过USB总线来进行寻址,
而有些类似于SoC内部外设如led 看门狗 定时器是直接通过内存的寻址空间来进行寻址的,cpu与这些设备通信是不需要总线的,2.6内核以后要
对所有设备进行统一管理,通过kset、kobject来建立层次关系,对这些直接通过内存寻址的设备虚拟了一种总线即platform总线,在硬件上
实际是没有这个总线;platform内核纯软件的总线,所有的直接通过内存寻址的设备都映射到这条总线上;
2:platform总线的优点
a:可以通过platform总线,可以遍历所有的platform总线设备;platform本质其实也是kset、kobject,具有kobject的特性
b:实现设备与驱动的分离,通过platform总线,设备与驱动是分开注册的,通过platform总线的probe来随时检测与设备匹配的驱动,如匹配上即进行这个设备的驱动注册;
c:由于上面这个优势,一个驱动可以供同类的几个设备使用;
3:platform总线以及platform总线设备驱动的实现流程
a:platform总线注册
b:platform_device注册
c:platform_driver注册
d:设备与驱动的匹配
e:驱动的注册
platform总线的工作流程如下图:


------------------------------------------------------------------------------------------------------------------------------------------------------------------
1:根据上面的流程我们来分析一下具体代码:
platform总线的注册:platform的注册是linux内核工程师已经设注册好的;重点看一下.match = platform_match函数;platform_driver和platform_device就是通过这个函数来匹配的
1 struct bus_type platform_bus_type = {
2 .name = 'platform',
3 .dev_attrs = platform_dev_attrs,
4 .match = platform_match,
5 .uevent = platform_uevent,
6 .pm = &platform_dev_pm_ops,
7 };
1 int __init platform_bus_init(void)
2 {
3 int error;
4
5 early_platform_cleanup();
6
7 error = device_register(&platform_bus);
8 if (error)
9 return error;
10 error = bus_register(&platform_bus_type);
11 if (error)
12 device_unregister(&platform_bus);
13 return error;
14 }
1 static int platform_match(struct device *dev, struct device_driver *drv)
2 {
3 struct platform_device *pdev = to_platform_device(dev);
4 struct platform_driver *pdrv = to_platform_driver(drv);
5
6 /* match against the id table first */
7 if (pdrv->id_table)
8 return platform_match_id(pdrv->id_table, pdev) != NULL;
9
10 /* fall-back to driver name match */
11 return (strcmp(pdev->name, drv->name) == 0);
12 }
由platform_match_id函数来进行匹配的,如果id_table不为空,则通过id_table来pdev_name匹配,如果为空,则drv->name与pdev->name来进行匹配,
匹配上以后再执行probe函数,这个函数即注册这个设备的驱动;

---------------------------------------------------------------------------------------------------------------------------------------------------------------
2:platform_device的注册
在arch/arm/mach-s3c2440/mach-mini2440.c文件中
这里注意.name、.dev.platform_data 这两个变量
platform_driver和platform_device就是通过name来匹配的。name一致则匹配上;
.dev.platform_data这个元素是中的内容是name、gpio flag def_trigger四个元素

1 static struct platform_device mini2440_led1 = {
2 .name = 's3c24xx_led',
3 .id = 1,
4 .dev = {
5 .platform_data = &mini2440_led1_pdata,
6 },
7 };
8
9 static struct platform_device mini2440_led2 = {
10 .name = 's3c24xx_led',
11 .id = 2,
12 .dev = {
13 .platform_data = &mini2440_led2_pdata,
14 },
15 };

设置好platform_device 结构体以后就可以注册platform_device设备了,把我们设置好的platform_device结构体放到mini2440这个结构体数组指针中;
1 static struct platform_device *mini2440_devices[] __initdata = {
2 &s3c_device_ohci,
3 &s3c_device_wdt,
4 &s3c_device_i2c0,
5 &s3c_device_rtc,
6 &s3c_device_usbgadget,
7 &mini2440_device_eth,
8 &mini2440_led1,
9 &mini2440_led2,
10 &mini2440_led3,
11 &mini2440_led4,
12 &mini2440_button_device,
13 &s3c_device_nand,
14 &s3c_device_sdi,
15 &s3c_device_iis,
16 &mini2440_audio,
17 };
在arch/arm/mach-s3c2440/mach-mini2440.c
mini2440_init 函数下
platform_add_devices(mini2440_devices, ARRAY_SIZE(mini2440_devices));
使用的platform_add_devices这个函数把mini2440的所有设备注册到内核中;内核会自动查找platform_device链表以及platform_driver链表,当match以后字自动执行platform_driver的probe函数;
在整理一下platform_device的注册过程:
1:设置好platform_device结构体(对于led驱动来说关键是name、dev->platform_data两个元素)
2:初始化好dev->platform_data结构体,这里主要涉及到led驱动所要用到的gpio,
这里我们可以看到linux内核platform驱动框架的设计思想:首先设备和驱动是分开的,同类设备有共性的部分,不同的部分,不同的部分在初始化的即被设置好;共性的部分内核工程师以及设置好;然后在通过一个匹配函数如果内核链表的设备与驱动链表的驱动匹配,则会自动安装驱动,否则不会安装驱动;
3:把设置好的platform_device设备加入到mini2440_devices中
4:在mini2440_device初始化的时候通过platform_add_devices函数把platform设备注册上去;注册以后再/sys/bus/platform/devices目录下会看到dev.name的文件夹
---------------------------------------------------------------------------------------------------------------------------------------------------------
3:platform_driver的注册
1 struct platform_driver {
2 int (*probe)(struct platform_device *);
3 int (*remove)(struct platform_device *);
4 void (*shutdown)(struct platform_device *);
5 int (*suspend)(struct platform_device *, pm_message_t state);
6 int (*resume)(struct platform_device *);
7 struct device_driver driver;
8 const struct platform_device_id *id_table;
9 };
1 static struct platform_driver s3c24xx_led_driver = {
2 .probe = s3c24xx_led_probe,
3 .remove = s3c24xx_led_remove,
4 .driver = {
5 .name = 's3c24xx_led',
6 .owner = THIS_MODULE,
7 },
8 };
9
10 static int __init s3c24xx_led_init(void)
11 {
12 return platform_driver_register(&s3c24xx_led_driver);
13 }
设置好platform_driver 结构体,使用platform_driver_register注册即可,这里关键的是probe、remove、driver.name 三个变量;
platform_driver_register 使用这个函数注册以后再 /sys/bus/platform/drivers目录下会看到 dev.name的文件夹
内核会自动检测匹配以后会自动执行probe函数;
-----------------------------------------------------------------------------------------------------------------------------------------------------------
代码实战:
led_driver.c driver注册;
1 #include 2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 8 #include 9 #include 10 #include 11 #include 12 #include 13 #include 14 #include 15 #include 16 #include 17 #include 18 #include 19 20 struct led_classdev *led_device; 21 struct s5pv210_led_platdata *pdata; 22 23 24 #define x210_led_on 0 25 #define x210_led_off 1 26 27 static void s5pv210_led_set(struct led_classdev *led_cdev, 28 enum led_brightness value) 29 { 30 31 //真正控制硬件的函数 32 if (value == LED_OFF) { 33 gpio_set_value(pdata->gpio, x210_led_off); 34 printk(KERN_INFO 'LED1 OFF...'); 35 } 36 else { 37 38 gpio_set_value(pdata->gpio, x210_led_on); 39 printk(KERN_INFO 'LED1 ON...'); 40 } 41 42 } 43 44 45 46 47 // 模块安装函数 48 static int s5pv210_led_probe(struct platform_device *dev) 49 { 50 int ret = -1; 51 printk(KERN_INFO 'led_device initn'); 52 53 54 led_device = kzalloc(sizeof(struct led_classdev), GFP_KERNEL); 55 if (led_device == NULL) 56 { 57 printk(KERN_ERR 'No memory for led_devicen'); 58 return -ENOMEM; 59 } 60 61 pdata = dev->dev.platform_data; 62 63 led_device->name = pdata->name; 64 led_device->brightness_set = s5pv210_led_set; 65 66 67 68 //在这里进行注册驱动; 69 ret = led_classdev_register(NULL, led_device); 70 if (ret < 0) 71 { 72 printk(KERN_ERR 'led_classdev_register failedn'); 73 kfree(led_device); 74 return ret; 75 } 76 77 78 79 //初始化gpio 80 ret = gpio_request(pdata->gpio, pdata->name); 81 if (ret < 0) { 82 printk(KERN_ERR 'couldn't claim card detect pin n'); 83 return -1; 84 } 85 gpio_direction_output(pdata->gpio, 1); 86 87 return 0; 88 } 89 90 // 模块删除函数 91 static int s5pv210_led_remove(struct platform_device *dev) 92 { 93 printk(KERN_INFO 'leddev_dev exitn'); 94 95 //注销led设备驱动 96 led_classdev_unregister(led_device); 97 kfree(led_device); 98 99 //删除gpiolib库中引脚 100 gpio_free(pdata->gpio); 101 102 printk(KERN_INFO 'leddev_dev unregist successn'); 103 104 return 0; 105 } 106 107 static struct platform_driver s5pv210_led_driver = { 108 .probe = s5pv210_led_probe, 109 .remove = s5pv210_led_remove, 110 .driver = { 111 .name = 's5pv210_led', 112 .owner = THIS_MODULE, 113 }, 114 }; 115 116 static int __init s5pv210_led_init(void) 117 { 118 return platform_driver_register(&s5pv210_led_driver); 119 } 120 121 static void __exit s5pv210_led_exit(void) 122 { 123 platform_driver_unregister(&s5pv210_led_driver); 124 } 125 126 127 module_init(s5pv210_led_init); 128 module_exit(s5pv210_led_exit); 129 130 // MODULE_xxx这种宏作用是用来添加模块描述信息 131 MODULE_LICENSE('GPL'); // 描述模块的许可证 132 MODULE_AUTHOR('BHC 133 MODULE_DESCRIPTION('led test'); // 描述模块的介绍信息 134 MODULE_ALIAS('alias xxx'); // 描述模块的别名信息 device设备注册 1 static struct s5pv210_led_platdata s5pv210_led1_pdata = { 2 .name = 'led0', 3 .gpio = S5PV210_GPJ0(3), 4 .flags = S3C24XX_LEDF_ACTLOW | S3C24XX_LEDF_TRISTATE, 5 .def_trigger = '', 6 }; 7 static struct s5pv210_led_platdata s5pv210_led2_pdata = { 8 .name = 'led1', 9 .gpio = S5PV210_GPJ0(4), 10 .flags = S3C24XX_LEDF_ACTLOW | S3C24XX_LEDF_TRISTATE, 11 .def_trigger = '', 12 }; 13 static struct s5pv210_led_platdata s5pv210_led3_pdata = { 14 .name = 'led2', 15 .gpio = S5PV210_GPJ0(5), 16 .flags = S3C24XX_LEDF_ACTLOW | S3C24XX_LEDF_TRISTATE, 17 .def_trigger = '', 18 }; 19 20 static struct platform_device s5pv210_led0 = { 21 .name = 's5pv210_led', 22 .id = 1, 23 .dev = { 24 .platform_data = &s5pv210_led1_pdata, 25 }, 26 }; 27 28 static struct platform_device s5pv210_led1 = { 29 .name = 's5pv210_led', 30 .id = 2, 31 .dev = { 32 .platform_data = &s5pv210_led2_pdata, 33 }, 34 }; 35 36 static struct platform_device s5pv210_led2 = { 37 .name = 's5pv210_led', 38 .id = 3, 39 .dev = { 40 .platform_data = &s5pv210_led3_pdata, 41 }, 42 }; static struct platform_device *smdkc110_devices[] __initdata 把下面代码加入到smdkc110_devices 这个结构体中;即可注册设备和驱动了 1 //led device 2 &s5pv210_led1, 3 &s5pv210_led2, 4 &s5pv210_led0,
- 基于迅为iTOP-3568开发板的Linux驱动开发实战:menuconfig图形化配置实验
- 迅为工业RK3568 itop-3568开发板Linux驱动开发实战:RK3568内核模块符号导出详解
- STM32MP157 Linux系统移植开发篇12:Linux内核MIPI LCD驱动移植
- 迅为imx6ull开发板Linux I2C驱动实验-应用程序与I2C通信
- 迅为IMX6ULL开发板Linux驱动初探-最简单的设备驱动-helloworld
- 迅为IMX6ULL开发板-Linux MISC驱动-编写实验程序
- iMX6ULL终结者Linux WIFI驱动实验rtl8723 Wifi联网测试
- 迅为i.MX6ULL终结者Linux MISC驱动运行测试
- 迅为IMX6ULL开发板Linux RS232/485驱动实验(上)
- IMX6ULL开发板Linux_WIFI驱动实验
- 六大全新产品系列推出,MCX A微控制器家族迎来创新
- 意法半导体全新STM32C5系列,重新定义入门级微控制器性能与价值,赋能万千智能设备
- 模组复用与整机重测在SRRC、CCC、CTA/NAL认证中的实践操作指南
- 有源晶振与无源晶振的六大区别详解
- 英飞凌持续巩固全球微控制器市场领导地位
- 使用 Keil Studio for Visual Studio Code开发 STM32 设备
- 从控制到系统:TI利用边缘AI重塑嵌入式MCU的边界
- 蓝牙信道探测技术原理与开发套件实践
- Microchip 推出生产就绪型全栈边缘 AI 解决方案,赋能MCU和MPU实现 智能实时决策
- LoRa、LoRaWAN、NB-IoT与4G DTU技术对比及工业无线方案选型分析




