开发板自带的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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
|
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/fb.h>
#include <linux/init.h>
#include <linux/dma-mapping.h>
#include <linux/interrupt.h>
#include <linux/workqueue.h>
#include <linux/wait.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <asm/div64.h>
#include <asm/mach/map.h>
#include <asm/arch/regs-lcd.h>
#include <asm/arch/regs-gpio.h>
#include <asm/arch/fb.h>
static
int
s3c_lcdfb_setcolreg(unsigned
int
regno, unsigned
int
red,
unsigned
int
green, unsigned
int
blue,
unsigned
int
transp,
struct
fb_info *info);
struct
lcd_regs {
unsigned
long
lcdcon1;
unsigned
long
lcdcon2;
unsigned
long
lcdcon3;
unsigned
long
lcdcon4;
unsigned
long
lcdcon5;
unsigned
long
lcdsaddr1;
unsigned
long
lcdsaddr2;
unsigned
long
lcdsaddr3;
unsigned
long
redlut;
unsigned
long
greenlut;
unsigned
long
bluelut;
unsigned
long
reserved[9];
unsigned
long
dithmode;
unsigned
long
tpal;
unsigned
long
lcdintpnd;
unsigned
long
lcdsrcpnd;
unsigned
long
lcdintmsk;
unsigned
long
lpcsel;
};
static
struct
fb_ops s3c_lcdfb_ops = {
.owner = THIS_MODULE,
.fb_setcolreg = s3c_lcdfb_setcolreg,
.fb_fillrect = cfb_fillrect,
.fb_copyarea = cfb_copyarea,
.fb_imageblit = cfb_imageblit,
};
static
struct
fb_info *s3c_lcd;
static
volatile
unsigned
long
*gpbcon;
static
volatile
unsigned
long
*gpbdat;
static
volatile
unsigned
long
*gpccon;
static
volatile
unsigned
long
*gpdcon;
static
volatile
unsigned
long
*gpgcon;
static
volatile
struct
lcd_regs* lcd_regs;
static
u32 pseudo_palette[16];
/* from pxafb.c */
static
inline unsigned
int
chan_to_field(unsigned
int
chan,
struct
fb_bitfield *bf)
{
chan &= 0xffff;
chan >>= 16 - bf->length;
return
chan << bf->offset;
}
static
int
s3c_lcdfb_setcolreg(unsigned
int
regno, unsigned
int
red,
unsigned
int
green, unsigned
int
blue,
unsigned
int
transp,
struct
fb_info *info)
{
unsigned
int
val;
if
(regno > 16)
return
1;
/* 用red,green,blue三原色构造出val */
val = chan_to_field(red, &info->
var
.red);
val |= chan_to_field(green, &info->
var
.green);
val |= chan_to_field(blue, &info->
var
.blue);
//((u32 *)(info->pseudo_palette))[regno] = val;
pseudo_palette[regno] = val;
return
0;
}
static
int
lcd_init(
void
)
{
/* 1. 分配一个fb_info */
s3c_lcd = framebuffer_alloc(0, NULL);
/* 2. 设置 */
/* 2.1 设置固定的参数 */
strcpy(s3c_lcd->fix.id,
"mylcd"
);
s3c_lcd->fix.smem_len = 480*272*32/8;
/* TQ2440的LCD位宽是24,但是2440里会分配4字节即32位(浪费1字节) */
s3c_lcd->fix.type = FB_TYPE_PACKED_PIXELS;
s3c_lcd->fix.visual = FB_VISUAL_TRUECOLOR;
/* TFT */
s3c_lcd->fix.line_length = 480*4;
/* 2.2 设置可变的参数 */
s3c_lcd->
var
.xres = 480;
s3c_lcd->
var
.yres = 272;
s3c_lcd->
var
.xres_virtual = 480;
s3c_lcd->
var
.yres_virtual = 272;
s3c_lcd->
var
.bits_per_pixel = 32;
/* RGB:565 */
s3c_lcd->
var
.red.offset = 16;
s3c_lcd->
var
.red.length = 8;
s3c_lcd->
var
.green.offset = 8;
s3c_lcd->
var
.green.length = 8;
s3c_lcd->
var
.blue.offset = 0;
s3c_lcd->
var
.blue.length = 8;
s3c_lcd->
var
.activate = FB_ACTIVATE_NOW;
/* 2.3 设置操作函数 */
s3c_lcd->fbops = &s3c_lcdfb_ops;
/* 2.4 其他的设置 */
s3c_lcd->pseudo_palette = pseudo_palette;
//s3c_lcd->screen_base = ; /* 显存的虚拟地址 */
s3c_lcd->screen_size = 480*272*32/8;
/* 3. 硬件相关的操作 */
/* 3.1 配置GPIO用于LCD */
gpbcon = ioremap(0x56000010, 8);
gpbdat = gpbcon+1;
gpccon = ioremap(0x56000020, 4);
gpdcon = ioremap(0x56000030, 4);
gpgcon = ioremap(0x56000060, 4);
*gpccon = 0xaaaaaaaa;
/* GPIO管脚用于VD[7:0],LCDVF[2:0],VM,VFRAME,VLINE,VCLK,LEND */
*gpdcon = 0xaaaaaaaa;
/* GPIO管脚用于VD[23:8] */
*gpgcon |= (3<<8);
/* GPG4用作LCD_PWREN */
/* 3.2 根据LCD手册设置LCD控制器, 比如VCLK的频率等 */
lcd_regs = ioremap(0x4D000000,
sizeof
(
struct
lcd_regs));
/*
* TQ2440 4.3英寸LCD手册为WXCAT43-TG6#001_V1.0.pdf第22、23页
*
* LCD手册和2440手册"Figure 15-6. TFT LCD Timing Example"一对比就知道参数含义了
*/
/* bit[17:8]: VCLK = HCLK / [(CLKVAL+1) x 2], LCD手册P22 (Dclk=9MHz~15MHz)
* 10MHz(100ns) = 100MHz / [(CLKVAL+1) x 2]
* CLKVAL = 4
* bit[6:5]: 0b11, TFT LCD
* bit[4:1]: 0b1101, 24 bpp for TFT
* bit[0] : 0 = Disable the video output and the LCD control signal.
*/
lcd_regs->lcdcon1 = (4<<8) | (3<<5) | (0x0d<<1);
/* 垂直方向的时间参数
* bit[31:24]: VBPD, VSYNC之后再过多长时间才能发出第1行数据
* LCD手册 tvb=2
* VBPD=1
* bit[23:14]: 多少行, 272, 所以LINEVAL=272-1=271
* bit[13:6] : VFPD, 发出最后一行数据之后,再过多长时间才发出VSYNC
* LCD手册tvf=2, 所以VFPD=2-1=1
* bit[5:0] : VSPW, VSYNC信号的脉冲宽度, LCD手册tvp=10, 所以VSPW=10-1=9
*/
lcd_regs->lcdcon2 = (1<<24) | (271<<14) | (1<<6) | (9<<0);
/* 水平方向的时间参数
* bit[25:19]: HBPD, VSYNC之后再过多长时间才能发出第1行数据
* LCD手册 thb=2
* HBPD=1
* bit[18:8]: 多少列, 480, 所以HOZVAL=480-1=479
* bit[7:0] : HFPD, 发出最后一行里最后一个象素数据之后,再过多长时间才发出HSYNC
* LCD手册thf=2, 所以HFPD=2-1=1
*/
lcd_regs->lcdcon3 = (1<<19) | (479<<8) | (1<<0);
/* 水平方向的同步信号
* bit[7:0] : HSPW, HSYNC信号的脉冲宽度, LCD手册Thp=41, 所以HSPW=41-1=40
*/
lcd_regs->lcdcon4 = 40;
/* 信号的极性
* bit[11]: 1=565 format, 对于24bpp这个不用设
* bit[10]: 0 = The video data is fetched at VCLK falling edge
* bit[9] : 1 = HSYNC信号要反转,即低电平有效
* bit[8] : 1 = VSYNC信号要反转,即低电平有效
* bit[6] : 0 = VDEN不用反转
* bit[3] : 0 = PWREN输出0
*
* BSWP = 0, HWSWP = 0, BPP24BL = 0 : 当bpp=24时,2440会给每一个象素分配32位即4字节,哪一个字节是不使用的? 看2440手册P412
* bit[12]: 0, LSB valid, 即最高字节不使用
* bit[1] : 0 = BSWP
* bit[0] : 0 = HWSWP
*/
lcd_regs->lcdcon5 = (0<<10) | (1<<9) | (1<<8) | (0<<12) | (0<<1) | (0<<0);
/* 3.3 分配显存(framebuffer), 并把地址告诉LCD控制器 */
s3c_lcd->screen_base = dma_alloc_writecombine(NULL, s3c_lcd->fix.smem_len, &s3c_lcd->fix.smem_start, GFP_KERNEL);
lcd_regs->lcdsaddr1 = (s3c_lcd->fix.smem_start >> 1) & ~(3<<30);
lcd_regs->lcdsaddr2 = ((s3c_lcd->fix.smem_start + s3c_lcd->fix.smem_len) >> 1) & 0x1fffff;
lcd_regs->lcdsaddr3 = (480*32/16);
/* 一行的长度(单位: 2字节) */
//s3c_lcd->fix.smem_start = xxx; /* 显存的物理地址 */
/* 启动LCD */
lcd_regs->lcdcon1 |= (1<<0);
/* 使能LCD控制器 */
lcd_regs->lcdcon5 |= (1<<3);
/* 使能LCD本身: LCD_PWREN */
// *gpbdat |= 1; /* 输出高电平, 使能背光, TQ2440的背光电路也是通过LCD_PWREN来控制的 */
/* 4. 注册 */
register_framebuffer(s3c_lcd);
return
0;
}
static
void
lcd_exit(
void
)
{
unregister_framebuffer(s3c_lcd);
lcd_regs->lcdcon1 &= ~(1<<0);
/* 关闭LCD控制器 */
lcd_regs->lcdcon1 &= ~(1<<3);
/* 关闭LCD本身 */
// *gpbdat &= ~1; /* 关闭背光 */
dma_free_writecombine(NULL, s3c_lcd->fix.smem_len, s3c_lcd->screen_base, s3c_lcd->fix.smem_start);
iounmap(lcd_regs);
iounmap(gpbcon);
iounmap(gpccon);
iounmap(gpdcon);
iounmap(gpgcon);
framebuffer_release(s3c_lcd);
}
module_init(lcd_init);
module_exit(lcd_exit);
MODULE_LICENSE(
"GPL"
);
|
基于platform的代码在这就不贴出来了,太多了,可以参考/driver/videv/s3c2410fb.c