S3C2440 LCD驱动(FrameBuffer)实例开发<二>(转)
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了S3C2440 LCD驱动(FrameBuffer)实例开发<二>(转)相关的知识,希望对你有一定的参考价值。
开发板自带的LCD驱动是基于platform总线写的,所以如果要使其它的LCD能够在自己的开发板上跑起来,那么就先了解platform驱动的架构,下面简单记录下自己看platform驱动时体会,简单的说platform是一种虚拟总线,那么它也是一条总线,所以它分为3个部分,platform_bus,platform_device,platform_driver。在platform_device向platform_bus注册设备,platform_driver向platform_bus注册驱动,注册后在platform_bus中会有一条device链表和driver链表,platform_bus中match函数将匹配两者的名字,如果相同那就把驱动和设备进行绑定。Linux platform driver机制和传统的device driver机制(通过driver_register进行注册)相比,一个明显的优势在于platform机制将设备本身的资源注册进内核,由内核统一管理,在驱动中使用这些资源时通过platform device提供的标准结构进行申请并使用。这样提高了驱动和资源的独立性,并且具有较好的可移植性和安全性(这些标准接口是安全的)。下面举一个简单platform驱动的例子来分析platform_device和platform_driver是怎么联系,platform_driver是怎么使用platform_device提供硬件信息的。
led_dev.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
|
#include <linux/module.h> #include <linux/version.h> #include <linux/init.h> #include <linux/kernel.h> #include <linux/types.h> #include <linux/interrupt.h> #include <linux/list.h> #include <linux/timer.h> #include <linux/init.h> #include <linux/serial_core.h> #include <linux/platform_device.h> /* 分配/设置/注册一个platform_device */ static struct resource led_resource[] = { [0] = { .start = 0x56000010, /* TQ2440的LED是GPB5,6,7,8, GPBCON地址是0x56000010 */ .end = 0x56000010 + 8 - 1, .flags = IORESOURCE_MEM, /* 标识led控制器io端口*/ }, [1] = { .start = 5, /* LED1 */ .end = 5, .flags = IORESOURCE_IRQ, /* 标识LED中断 */ } }; /* 必须提供realease函数,可以不实现 */ static void led_release( struct device * dev) { } static struct platform_device led_dev = { .name = "myled" , /* 设备名 */ .id = -1, /* 一般设为-1,表示同样名字的设备只有一个 */ .num_resources = ARRAY_SIZE(led_resource), /* 资源数量*/ .resource = led_resource, .dev = { .release = led_release, /* 引用上面定义的资源 */ }, }; static int led_dev_init( void ) { platform_device_register(&led_dev); /* 注册平台设备 */ return 0; } static void led_dev_exit( void ) { platform_device_unregister(&led_dev); /* 注销平台设备*/ } module_init(led_dev_init); module_exit(led_dev_exit); MODULE_LICENSE( "GPL" ); |
Led_drv.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
|
#include <linux/fs.h> #include <linux/interrupt.h #include <linux/irq.h> #include <linux/sched.h> #include <linux/pm.h> #include <linux/sysctl.h> #include <linux/proc_fs.h> #include <linux/delay.h> #include <linux/platform_device.h> #include <linux/input.h> #include <linux/irq.h> #include <asm/uaccess.h> #include <asm/io.h> static int major; static struct class *cls; static volatile unsigned long *gpio_con; static volatile unsigned long *gpio_dat; static int pin; static int led_open( struct inode *inode, struct file *file) { //printk("first_drv_open\n"); /* 配置为输出 */ *gpio_con &= ~(0x3<<(pin*2)); *gpio_con |= (0x1<<(pin*2)); return 0; } static ssize_t led_write( struct file *file, const char __user *buf, size_t count, loff_t * ppos) { int val; //printk("first_drv_write\n"); copy_from_user(&val, buf, count); // copy_to_user(); if (val == 1) { // 点灯 *gpio_dat &= ~(1<<pin); } else { // 灭灯 *gpio_dat |= (1<<pin); } return 0; } static struct file_operations led_fops = { .owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */ .open = led_open, .write = led_write, }; static int led_probe( struct platform_device *pdev) { struct resource *res; /* 分配/设置/注册一个platform_driver */ /* 根据platform_device的资源进行ioremap */ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); gpio_con = ioremap(res->start, res->end - res->start + 1); gpio_dat = gpio_con + 1; res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); pin = res->start; /* 注册字符设备驱动程序 */ printk( "led_probe, found led\n" ); /* 注册设备,生成设备文件*/ major = register_chrdev(0, "myled" , &led_fops); cls = class_create(THIS_MODULE, "myled" ); class_device_create(cls, NULL, MKDEV(major, 0), NULL, "led" ); /* /dev/led */ return 0; } static int led_remove( struct platform_device *pdev) { /* 卸载字符设备驱动程序 */ /* iounmap */ printk( "led_remove, remove led\n" ); class_device_destroy(cls, MKDEV(major, 0)); class_destroy(cls); unregister_chrdev(major, "myled" ); iounmap(gpio_con); return 0; } struct platform_driver led_drv = { .probe = led_probe, .remove = led_remove, .driver = { .name = "myled" , } }; static int led_drv_init( void ) { platform_driver_register(&led_drv); /* 注册平台驱动 */ return 0; } static void led_drv_exit( void ) { platform_driver_unregister(&led_drv); /* 注销平台驱动 */ } module_init(led_drv_init); module_exit(led_drv_exit); MODULE_LICENSE( "GPL" ); |
这就是一个简单的platform驱动,这两个文件我们完全可以写道一个文件中去实现,现在看下由一个文件如何实现这个驱动:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
|
#include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/init.h> #include <linux/delay.h> #include <asm/uaccess.h> #include <asm/irq.h> #include <asm/io.h> #include <asm/arch/regs-gpio.h> #include <asm/hardware.h> static struct class *seconddrv_class; static struct class_device *seconddrv_class_dev; volatile unsigned long *gpfcon; volatile unsigned long *gpfdat; static int second_drv_open( struct inode *inode, struct file *file) { /* * K1,K2,K3,K4对应GPF1、GPF4、GPF2、GPF0 */ /* 配置GPF1、GPF4、GPF2、GPF0为输入引脚 */ *gpfcon &= ~((0x3<<(1*2)) | (0x3<<(4*2)) | (0x3<<(2*2)) | (0x3<<(0*2))); return 0; } ssize_t second_drv_read( struct file *file, char __user *buf, size_t size, loff_t *ppos) { /* 返回4个引脚的电平 */ unsigned char key_vals[4]; int regval; if (size != sizeof (key_vals)) return -EINVAL; regval = *gpfdat; key_vals[0] = (regval & (1<<1)) ? 1 : 0; key_vals[1] = (regval & (1<<4)) ? 1 : 0; key_vals[2] = (regval & (1<<2)) ? 1 : 0; key_vals[3] = (regval & (1<<0)) ? 1 : 0; copy_to_user(buf, key_vals, sizeof (key_vals)); return sizeof (key_vals); } static struct file_operations sencod_drv_fops = { .owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */ .open = second_drv_open, .read = second_drv_read, }; int major; static int second_drv_init( void ) { major = register_chrdev(0, "second_drv" , &sencod_drv_fops); seconddrv_class = class_create(THIS_MODULE, "second_drv" ); seconddrv_class_dev = class_device_create(seconddrv_class, NULL, MKDEV(major, 0), NULL, "buttons" ); /* /dev/buttons */ gpfcon = ( volatile unsigned long *)ioremap(0x56000050, 16); gpfdat = gpfcon + 1; return 0; } static void second_drv_exit( void ) { unregister_chrdev(major, "second_drv" ); class_device_unregister(seconddrv_class_dev); class_destroy(seconddrv_class); iounmap(gpfcon); return 0; } module_init(second_drv_init); module_exit(second_drv_exit); MODULE_LICENSE( "GPL" ); |
由此可见,如果由platform驱动去实现led驱动将会多出很多东西,而这些多出来的就是我们如何把一个驱动程序融合到platform里面,那既然用platform驱动要多写那么多东西那问什么还要写基于platform的驱动呢?我的理解是基于以下几个原因:如果一个设备挂在总线上,其结果是配套的sysfs结点,设备电源管理都成为可能;隔离了BSP和驱动,在BSP中定义platform设备和设备使用的资源,设备的具体配置信息,而在驱动中,只要通过API去获取资源和数据,做到了板相关代码与驱动代码的分离,使得驱动具有更好的可移植性和更好的扩展性和跨平台性;假如我们要实现一个LCD驱动,那么我们只需修改BSP相关的代码,platform基本上不需修改,避免重复着轮子的可能性;基于platformplatform机制将设备本身的资源注册进内核,由内核统一管理。
上面platform的例子还没有完全按照linux platform的标准去写,因为阅读代码可知linux platform驱动把platform_device相关的代码放在一块,然后统一进行注册!
下面记录下如何把device添加到板级文件中(最开始还是建议写成两个文件,分别编译成模块加载,因为如果是放到板级文件中那么需要重新编译内核,这个将在编译上浪费很多时间)
步骤:
1. 在/arch/arm/plat-s3c24xx/devs.c中定义led 相关的设备及资源,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
static struct resource led_resource[] = { [0] = { .start = 0x56000010, /* TQ2440的LED是GPB5,6,7,8, GPBCON地址是0x56000010 */ .end = 0x56000010 + 8 - 1, .flags = IORESOURCE_MEM, /* 标识led控制器io端口*/ }, [1] = { .start = 5, /* LED1 */ .end = 5, .flags = IORESOURCE_IRQ, /* 标识LED中断 */ } }; EXPORT_SYMBOL(s3c_device_lcd); |
请注意最后一行是导出led平台设备,会在mach-smdk2440.c中添加到平台设备列表中。
2. 如果还需定义led的其它信息,那么在/arch/arm/plat-s3c2410/include/mach/fb.h 为led平台设备定义一个s3c2410fb_mach_info结果体。led很简单所以没有必要再去定义,但是后面讲的LCD的平台设备需要定义!因为仅仅通过resource,驱动还无法操作LCD。
3. 不要实现,只是记录下系统还会做什么事! 在mach-smdk2440.c中将会调用platform_add_devices(),注册平台设备。
好了对platform有了一定了解后,那么看看如果将LCD驱动写成linux设备模型的platform驱动,同样给出不使用platform架构是一个完整的字符设备驱动的lcd驱动,在给出一个符合platform模型的lcd驱动,通过这两个文件的对比,就可以知道在符合platform模型的lcd驱动中可以找到LCD驱动的全部信息,也会发现基本上所有plat_form驱动出了驱动本身的东西外,框架性的东西基本上一样的,所以如果理解了一两个platform驱动,那么以后写platform驱动或看platform代码跟不以flatform写出的代码是没有什么区别的!代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
以上是关于S3C2440 LCD驱动(FrameBuffer)实例开发<二>(转)的主要内容,如果未能解决你的问题,请参考以下文章 |