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

(c)2006-2024 SYSTEM All Rights Reserved IT常识