韦东山第二期课程内容概要

  • 2 输入子系统驱动
    • 2.1 输入子系统概念介绍
    • 2.2 基于输入子系统编写驱动程序
  • 3 分层分离驱动
  • 4 LCD驱动
    • 4.1 LCD驱动之层次分析
    • 4.2 LCD驱动程序之硬件操作
    • 4.3-1 LCD驱动程序之编写代码之初步编写
    • 4.3-2 LCD驱动程序之编写代码之硬件设置
    • 4.3 LCD驱动程序之编写代码之显存和调色板设置
    • 4.4 LCD驱动程序之编写代码之编译测试
    • 4.5 JZ2440V2 V3 LCD驱动程序
  • 5 触摸屏驱动
    • 5.1 触摸屏驱动之概念介绍
    • 5.2 触摸屏驱动之编写驱动
    • 5.3 触摸屏驱动之使用TSLIB测试
  • 12 I2C裸板
    • 12.1 I2C设备裸板程序之I2C总线介绍
    • 12.2 I2C设备裸板程序之编写代码
  • 13 Linux 2.6 I2C驱动
    • 13.1 I2C驱动程序之框架

2 输入子系统驱动

2.1 输入子系统概念介绍

  为了写出通用的驱动程序,需要把我们写的驱动融合到Linux现有的驱动框架中,因为Linux内核是通用的,按键驱动属于输入子系统框架。我们需要弄清楚Linux内核的驱动框架,将我们需要的功能加进去,向上提供统一的标准Linux接口。

(一)各个具体设备的驱动程序如何向上注册

输入子系统驱动方面纯软件的代码主要在linux-2.6.22.6\drivers\input\evdev.c中,这个文件中的代码是Linux内核抽取出来像触摸屏、按键等输入设备所共有的东西,是稳定的,不随具体硬件而改变。这些软件主要是管理输入缓冲区以及提供同一的file_operations接口。

关于什么是evdev,见链接https://blog.csdn.net/wanywhn/article/details/83443843

1、evdev.c文件实现最底层的file_operations结构体实例
在Z:\linux-2.6.22.6\drivers\input\evdev.c中系统创建了一个file_operations实例:evdev_fops,并实现了其中的各个函数。

static const struct file_operations evdev_fops = {.owner = THIS_MODULE,.read =        evdev_read,.write =    evdev_write,.poll =        evdev_poll,.open =     evdev_open,.release =  evdev_release,.unlocked_ioctl = evdev_ioctl,
#ifdef CONFIG_COMPAT.compat_ioctl =    evdev_ioctl_compat,
#endif.fasync =    evdev_fasync,.flush =  evdev_flush
};

2、将file_operations实例进一步封装
在evdev.c中,将file_operations实例:evdev_fops进一步封装为一个input_handler结构体实例:evdev_handler

static struct input_handler evdev_handler = {.event =  evdev_event,.connect = evdev_connect,.disconnect =    evdev_disconnect,.fops =       &evdev_fops,.minor =   EVDEV_MINOR_BASE,.name =       "evdev",.id_table =  evdev_ids,//表示evdev能够支持的设备,用于input_attach_handler和设备进行匹配
};

3、evdev文件入口函数向上注册已经实现的input_handler结构体实例:evdev_handler
同样evdev.c中,evdev驱动程序的入口函数static int __init evdev_init(void)中将实现的input_handler实例:evdev_handler向上注册。

return input_register_handler(&evdev_handler);

函数input_register_handler处于输入子系统核心层,所有输入外设(键盘、触摸屏等)在实现具体的input_handler实例(里面包括file_operations实例)后都通过这个函数向上注册,这个函数位于:Z:\linux-2.6.22.6\drivers\input\input.c,函数原型为:

int input_register_handler(struct input_handler *handler)

4、input_register_handler函数将input_handler实例注册到input_table表中
input_table在input.c中声明,是一个普通的指针数组,容量为8。

static struct input_handler *input_table[8];

在input_register_handler函数中根据传入实例的次设备号(最初在evdev文件定义input_handler结构体实例:evdev_handler时传入,在这个例子中是一个固定宏值EVDEV_MINOR_BASE:64),下面的语句将次设备号左移5位,也就是除32,即input_table[2]存放evdev文件的input_handler结构体实例:evdev_handler。

input_table[handler->minor >> 5] = handler;

至此,底层具体的驱动程序向上注册完毕,当系统insmod evdev驱动时,系统会自动调用evdev文件的驱动入口函数:static int __init evdev_init(void),最终会使得input_table中有指向本驱动的input_handler结构体实例:evdev_handler的指针。

5、input_register_handler函数将input_handler实例放入输入子系统handler链表中
input_register_handler函数顾名思义是向上注册输入子系统的解决方法(handler),有两种注册,一种是上文提到的将input_handler实例按次设备号为下标放入数组中,第二种是将evdev.c实现的input_handler实例放入一个输入子系统handler链表中。

list_add_tail(&handler->node, &input_handler_list);

6、input_register_handler函数遍历输入子系统device链表找寻handler对应的设备
子系统这边的结构类似与总线设备驱动模型,也是将驱动和设备分开,驱动在input_handler实例中实现,在调用input_register_handler函数注册input_handler实例时需要遍历输入子系统device链表,确定该input_handler能够处理哪些设备。也就是一个两两匹配的过程。

list_for_each_entry(dev, &input_dev_list, node)input_attach_handler(dev, handler);

上面程序的作用是对input_dev_list中的每一个input_dev实例都执行input_attach_handler(dev, handler)函数,该函数将所注册的input_handler实例与每一个input_dev实例对比,判断两者是否匹配,函数原型为:

static int input_attach_handler(struct input_dev *dev, struct input_handler *handler)

input_attach_handler函数中调用input_match_device函数:

id = input_match_device(handler->id_table, dev);

该函数比较input_handler实例的id和input_dev的id,从而判断是否匹配,如果匹配,调用input_handler实例的connect函数去建立设备和驱动之间的链接,每个input_handler实例的connect函数建立链接的方式都有所不同:

error = handler->connect(handler, dev, id);

问题:在实际操作中有很多具体的输入设备,每一个都需要insmod一次吗?感觉不现实(解答:热拔插)

(二)各个具体设备如何向上注册

(一)讲解了较为稳定的驱动程序如何注册,如evdev.c之类的文件是稳定的,包含在内核中。而注册各个具体的设备的文件一般是作者自己创建的。对于输入子系统其设备注册函数也在input.c中,函数原型为:

int input_register_device(struct input_dev *dev)

在这个函数中主要做两个工作,一是将input_dev实例加入到输入子系统device链表中:

list_add_tail(&dev->node, &input_dev_list);

二是遍历输入子系统handler链表,寻找所注册的设备实例能够被哪些驱动所支持。

list_for_each_entry(handler, &input_handler_list, node)input_attach_handler(dev, handler);

这部分代码基本和驱动注册时对照,如果设备和驱动匹配成功同样调用input_handler实例的connect函数。
也就是说,在注册设备或者驱动以后,如果有匹配的,都会调用到input_handler实例的connect函数,这部分是用户程序还没使用驱动之前就会完成的,用户程序使用驱动之前connect函数已经运行过了。

(三)双向注册完成后,具体如何connect

connect函数的具体实现在各个驱动的文件中,我们这里在evdev.c中,函数原型为:

static int evdev_connect(struct input_handler *handler, struct input_dev *dev,const struct input_device_id *id)

1、声明并分配一个evdev结构体实例
在evdev.c的connect函数中,首先声明一个evdev结构体实例:

struct evdev *evdev;

结构体struct evdev的声明同样在evdev.c中:

struct evdev {int exist;int open;int minor;char name[16];struct input_handle handle;//重点关注,注意与前面的handler相比没有r了wait_queue_head_t wait;struct evdev_client *grab;struct list_head client_list;
};

随后再在connect函数中具体分配定义struct evdev *evdev:

evdev = kzalloc(sizeof(struct evdev), GFP_KERNEL);

2、设置evdev结构体实例中的evdev->handle
从上面我们知道struct evdev中包含struct input_handle handle成员,该结构体声明在input.h中:

struct input_handle {void *private;int open;const char *name;struct input_dev *dev;struct input_handler *handler;struct list_head    d_node;struct list_head h_node;
};

在connect函数中将已经注册的输入设备和驱动分别赋给struct input_handle的对应成员:

evdev->handle.dev = dev;
evdev->handle.handler = handler;

3、注册evdev结构体实例中的evdev->handle
同样在connect函数中,注册已经设置好的evdev->handle实例:

error = input_register_handle(&evdev->handle);

input_register_handle具体实现在input.c中:

int input_register_handle(struct input_handle *handle)
{struct input_handler *handler = handle->handler;list_add_tail(&handle->d_node, &handle->dev->h_list);list_add_tail(&handle->h_node, &handler->h_list);if (handler->start)handler->start(handle);return 0;
}

我们注册的设备input_dev现在是 &handle->dev,注册的input_handler是 &handle->handler,上面函数将所注册的设备和handler的h_list都指向input_handle实例,用c语言概括为:

input_handler->h_list= &input_handle;
input_dev->h_list= &input_handle;

也就是说,在connect函数运行完以后,我们可以直接通过input_handler->h_list或者input_dev->h_list找到对应的input_handle实例,再从input_handle实例提取出需要的input_dev和input_handler。input_handle实例相当于一个中间人。至此,注册和链接工作都已经完成。

(四)驱动和设备注册完成后,上层如何调用

1、所有输入设备的open调用都通过input的open函数来中转
所有的输入子系统open调用最终会调用到input.c中的open函数,在input.c中的入口函数static int __init input_init(void)中:

err = register_chrdev(INPUT_MAJOR, "input", &input_fops);

该语句向上注册一个主设备号为INPUT_MAJOR(13)的字符设备程序,注意这里只注册了主设备号,暂时认为所有的输入子系统的字符设备共用一个主设备号,次设备号定义在各个具体驱动程序中(如本例的evdev)的input_handler结构体实例:evdev_handler中,然后通过input_register_handler向上注册。

input_fops为一个file_operations实例:

static const struct file_operations input_fops = {.owner = THIS_MODULE,.open = input_open_file,
};

注意这里只注册了一个open函数实例input_open_file。该函数同样在input.c中定义,原型为:

static int input_open_file(struct inode *inode, struct file *file)

用insmod将input.c中实现的驱动注册后,在应用程序调用open函数时(调用open时会传入参数struct inode inode, struct file file,这两个参数中有主次设备号的信息),会调用到input_open_file函数。由于各个具体的设备驱动程序已经在(一)**中通过次设备号将自身注册到了input_table中,在input_open_file函数中将input_table[iminor(inode) >> 5]得到具体设备的input_handler实例,然后通过fops_get得到input_handler实例中的file_operations实例,将该file_operations实例赋给new_fops,最后通过new_fops->open(inode, file)调用到具体驱动程序的file_operations实例中的具体的open函数。

struct input_handler *handler = input_table[iminor(inode) >> 5];
new_fops = fops_get(handler->fops));
err = new_fops->open(inode, file);
return err;

体会:上面这整套流程有两个点,一是在file_operations结构体的基础上进一步封装为input_handler结构体,个人目前猜想这么做的目的是为了实现Linux的总线-设备-驱动模型;二是将所有的输入子系统的open操作都通过input.c中的open函数来中转,个人猜想这样做体现了Linux驱动框架中分层的思想。具体可见《Linux设备驱动开发详解》第三版本P287。
2、open调用后,应用程序的各种调用最终会调用evdev.c的各种调用
open调用后应用程序知道了文件句柄,后续的read/write之类的调用就不用再通过input.c中转了,直接会调用到evdev.c中file_operations实例evdev_fops中的evdev_read和evdev_write等函数。这些函数主要是处理一些通用的事件,如缓冲区、事件的管理等。下一节具体讲解一下evdev_read函数。

3、如何读取按键值,evdev_read函数
应用空间调用read函数会调用到evdev_read函数,该函数的处理和我们之前写的简单的read函数类似,主要是读取环形缓冲区中的值。如果顺利读到值就调用evdev_event_to_user将值通过事件处理函数发送给用户:

if (evdev_event_to_user(buffer + retval, event))return -EFAULT;

如果没读到且是非阻塞方式打开的,就直接返回:

if (client->head == client->tail && evdev->exist && (file->f_flags & O_NONBLOCK))return -EAGAIN;

如果是阻塞方式,就进入休眠等待唤醒

retval = wait_event_interruptible(evdev->wait,client->head != client->tail || !evdev->exist);

read如果通过wait_event_interruptible陷入休眠,由同样在evdev.c中的evdev_event唤醒。

4、evdev_event由谁调用
evdev_event函数是evdev.c下专门用于处理事件的函数,函数原型为:

static void evdev_event(struct input_handle *handle, unsigned int type, unsigned int code, int value)

调用evdev_event函数的代码一般由驱动编写者处理,也就是我们具体需要做的部分,上面讲述的大部分都是Linux已经做好的,我们要做的就是比如在一个按键中断中上报“有按键按下”这一事件,随后就会调用evdev_event函数进行处理。归纳来说,基于子系统的驱动编写,我们需要做的就是在具体某个设备的文件中(比如中断服务程序中),确定所发生的事件是什么,然后调用相应的input_handler中的event处理函数。
Linux内核中有一个GPIO驱动的例子,linux-2.6.22.6\drivers\input\keyboard\gpio_keys.c,可以看下它的中断处理函数:

static irqreturn_t gpio_keys_isr(int irq, void *dev_id)

该中断处理函数里面调用input_event函数:

input_event(input, type, button->code, !!state);input_sync(input);

该函数在input.c中实现,函数原型为:

/*** input_event() - report new input event* @dev: device that generated the event* @type: type of the event* @code: event code* @value: value of the event** This function should be used by drivers implementing various input devices* See also input_inject_event()*/
void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)

在input_event函数中:

list_for_each_entry(handle, &dev->h_list, d_node)if (handle->open)handle->handler->event(handle, type, code, value);

list_for_each_entry语句表示对dev->h_list链表中的每一个handle都执行下面的if (handle->open)语句,该语句表示如果handle中的open函数已经被调用,则执行对应的event函数。

(五)input子系统总结

1、设备和驱动的注册和链接
设备input_dev实例和驱动input_handler实例分别通过input_register_device和input_register_handler进行注册,input_register_handler注册的功能是将input_handler实例注册到input_table表中、将input_handler实例放入输入子系统handler链表中,input_register_device注册的功能是将input_dev实例加入到输入子系统device链表中;两者在注册的同时都会遍历链表以寻求匹配;匹配成功后会调用input_handler实例中的connect函数,该函数会创建并注册一个 input_handle实例,该结构体会包含注册的input_dev实例和input_handler实例,在connect函数运行完以后,我们可以直接通过input_handler->h_list或者input_dev->h_list找到对应的input_handle实例,再从input_handle实例提取出需要的input_dev和input_handler。input_handle实例相当于一个中间人。至此,注册和链接工作都已经完成。

2、对驱动的调用
对驱动的调用可以分为两个部分,一是用户程序通过open、read、write的主动调用,二是由具体设备的行为触发中断的被动调用。对于第一种调用,首先肯定要open,所有输入设备的open调用都通过input的open函数来中转,open以后会返回文件句柄,后续的read、write就会直接调用到对应文件的handler的函数。对于第二种调用,主要是写在具体设备文件的中断函数中,中断函数确定所发生的事件是什么,然后调用input_event函数,该函数会通过之前双向注册时用connect函数创建的handle结构体找到本设备对应的驱动中的input_handler中的event处理函数。

几个概念:kobject、kset、subsystem;device、device_deiver、bus_type;/sysfs、/dev

2.2 基于输入子系统编写驱动程序

视频写了一个基于输入子系统的按键驱动程序,我们只需要完成以下几个步骤:
1、建立一个设备相关的文件,比如buttons.c
2、编写驱动的init函数和exit函数,主要讲一下init函数,exit函数是相反的操作。在init函数中主要做以下几个步骤:
(1)分配一个input_dev结构体,也就是定义一个input_dev实例,该实例代表实际的设备
(2)设置该设备能够产生哪类事件,以及能产生这类事件里面的哪些操作。事件类和事件操作都是用一些已经定义了的宏表示。
(3)调用input_register_device函数将该设备向上注册
(4)ioremap,注册相关的中断函数
3、按键按下和松开会触发中断函数,在中断函数中调用input_event函数将事件上报。
自此我们所要编写的驱动程序就结束了。

**体会:**为什么明明可以直接通过输入子系统编写驱动,为什么还要再加一层总线设备驱动模型?回顾2.2单纯基于输入子系统写的驱动,如果要调整硬件,就需要改中断函数,改ioremap,这样实际不太方便,这些东西都可以通过总线设备驱动模型用资源文件来表示,这样每次就只需要改资源文件就可以了。子系统的优点是已经对file_operations这种进行了抽象,模型的优点是对资源进行了分离以及实现引用计数等。

问题: evdev中会对上报的事件进行具体的处理,可能会打印一些信息,这些信息去哪里看呢?具体写好以后实际运行大概是怎样个流程?去看看视频

3 分层分离驱动

(一)子系统和总线设备驱动模型之间的联系与区别

1、双向注册并配对
子系统以输入子系统为例,其表示输入设备的结构体为input_dev,表示输入驱动的结构体为input_handler,其设备注册函数为input_register_device,其驱动注册函数为input_register_handler,这两个函数分别将input_dev实例和input_handler实例加入到输入子系统device链表和输入子系统handler链表中,并且每次设备或者驱动注册加入对应链表后都会遍历链表寻求配对,遍历函数都为input_attach_handler,该函数内部又都调用input_match_device来配对。子系统的处理核心在于其对应的handler实例,比如输入子系统的input_handler实例,里面有事件的处理函数,有对应的open、write、read函数等,这些函数有对输入设备一般需要的输入缓冲区的处理,这些是输入子系统所特有的。

总线设备驱动模型同样需要双向注册并配对,下面用模型代指总线设备驱动模型。模型比子系统更底层一些,在模型之上实现各种子系统,模型本身并不包含像输入子系统那样处理输入缓冲区的功能,模型的实现依赖于更下一层的kobject和kset等。模型用bus_type、device、device_diver三个结构体来表示总线、设备和驱动。一般我们不需要再对总线进行初始化或者注册等,总线就用Linux本身自带的就行。各种设备是挂靠在各种总线上的,如pci等,没有挂靠的一般用虚拟平台总线platform。一般将device、device_diver进一步封装为对应总线的设备和驱动,比如封装为platform_device、platform_diver。platform_device和platform_diver分别通过platform_device_register和platform_driver_register进行注册,函数里面分别具体调用了device_register和driver_register进行注册。platform_diver和platform_device的注册会将该platform_diver和platform_device实例加入到platform总线所管理的驱动和设备链表中,并且每次设备或者驱动注册加入对应链表后都会遍历链表寻求配对,遍历函数都为bus_type结构体中的match函数成员。

2、配对以后建立链接
子系统以输入子系统为例,配对成功后会调用input_handler实例中的connect函数,具体connect操作见上文,注意connect操作基本上是Linux写好了的,是固定的,下面的probe函数是要自己写的。

总线设备驱动模型,简称模型,在配对成功后,会自动调用platform_diver实例中的probe函数。具体在probe函数中做什么的话,没有硬性要求,模型仅仅只是提供了这样一种机制,能够通过注册自动调用到probe函数。比如Linux内核中有一个GPIO驱动的例子gpio_keys.c,由于按键又属于输入子系统,所以在这个文件的probe函数中又进行了一些输入子系统相关的注册工作,从而将模型与子系统链接起来。

问题: gpio_keys.c中的probe函数具体怎么链接到输入子系统呢? 答:类似2.2,具体看宋宝华第三版302

实际上如果按照正常的先总线设备驱动模型再加子系统的话,我们在2.2中写的东西应该放到probe函数中。

问题: 既然我们在2.2中不用设备模型也能直接用子系统,干嘛还要用到probe函数呢,直接类似2.2不就行了?
答:有可能是子系统并不十全十美,比如点灯应该用哪个子系统?
(二)实际写一个符合总线设备驱动模型的程序
两个c文件,一个是设备文件,一个是驱动文件。
对于设备文件,存放和硬件相关的代码,在改动程序是只改动设备文件,不改动驱动文件。在设备文件的init函数中注册平台总线设备,表明该设备能使用的资源resource(关于资源的描述参考Linux设备驱动开发详解第三版295),定义该设备被释放时的release函数。对于设备的修改往往就是修改其所拥有的资源。

问题: 这里的设备跟输入子系统的设备有何区别,在2.2中我们也注册了输入子系统设备。

对于驱动文件,在init函数中注册平台驱动,驱动中主要实现probe和remove两个函数,另外单独建立files_operation实例,编写open和write函数,没有close函数。平台设备和平台驱动配对后首先运行probe函数,在该函数中获取平台设备的资源信息,然后ioremap,随后将该文件中实现的files_operation实例进行注册,最后通过mdev自动创建设备节点,probe函数所做工作和从前不加总线设备驱动模型时的init函数类似,probe函数函数运行完以后,驱动已经注册,设备对应的地址也已经映射完成。用户程序调用open和write函数就会自动调用对应的驱动。当驱动卸载时自动调用remove函数,功能和以前的close函数类似。

由于我们这里是led驱动,是点灯,不是输入,所以没必要在probe中再连接输入子系统。如果连接了某个子系统,我们就不需要自己写对应的files_operation实例,子系统的handler实例中已经写好了,我们需要在驱动文件的probe函数中写类似2.2节的内容,在设备文件中注册设备和资源。

4 LCD驱动

4.1 LCD驱动之层次分析

视频介绍:介绍了frame buffer子系统的基本框架,注意和input子系统区分开。
(一)层次介绍
LCD驱动归属于frame buffer子系统,整体框架也是和input子系统类似,先基于总线设备驱动模型,调用到对应的probe函数,在probe函数中链接到frame buffer子系统。
问题:怎么链接?

frame buffer子系统是Linux内核对frame buffer 设备的抽象,类似input子系统是对输入设备的抽象,frame buffer子系统主要代码在\linux-2.6.22\drivers\video\fbmem.c中。

在fbmem.c中的入口函数fbmem_init(void)中,通过:

register_chrdev(FB_MAJOR,"fb",&fb_fops)

向上注册一个file_operation结构体实例fb_fops,该实例也在fbmem.c中:

static const struct file_operations fb_fops = {.owner =    THIS_MODULE,.read =        fb_read,.write =   fb_write,.ioctl =  fb_ioctl,
#ifdef CONFIG_COMPAT.compat_ioctl = fb_compat_ioctl,
#endif.mmap =      fb_mmap,.open =        fb_open,.release = fb_release,
#ifdef HAVE_ARCH_FB_UNMAPPED_AREA.get_unmapped_area = get_fb_unmapped_area,
#endif
#ifdef CONFIG_FB_DEFERRED_IO.fsync =   fb_deferred_io_fsync,
#endif
};

在该file_operation结构体实例fb_fops中实现了open、read(建议去看看这些函数)等操作,这些函数内部进行具体操作的前提都需要:

 struct inode *inode = file->f_path.dentry->d_inode;int fbidx = iminor(inode);struct fb_info *info = registered_fb[fbidx];

即通过对应设备的次设备号fbidx,索引到registered_fb数组中对应的struct fb_info结构,struct fb_info结构中记录了具体frame buffer设备的信息,所有对于frame buffer设备的操作都依赖于这个结构体进行展开,struct fb_info结构体声明在linux-2.6.22\include\linux\fb.h中,里面囊括了frame buffer设备的所有细节,一般我们需要关注的有:

struct fb_info {int node;int flags;struct fb_var_screeninfo var; /* Current var */可变参数struct fb_fix_screeninfo fix;  /* Current fix */ 固定参数struct fb_ops *fbops;                            操作函数,这里面可以定义设备自己对显存的操作,比如读写等struct device *device;     /* This is the parent */struct device *dev;     /* This is this fb device */char __iomem *screen_base;  /* Virtual address */
};

其中固定参数有:

struct fb_fix_screeninfo {char id[16];           /* identification string eg "TT Builtin" */名字,随便写unsigned long smem_start; /* Start of frame buffer mem */frame buffr,也就是显存(内存中划分出来的一块区域)的首地址/* (physical address) */注意这些都是物理地址__u32 smem_len;    /* Length of frame buffer mem */frame buffr长度,这个需要看LCD手册,分辨率(像素个数)乘以每个像素占据的位数__u32 type;            /* see FB_TYPE_*        */LCD种类,去看FB_TYPE_宏里面选一个__u32 type_aux;          /* Interleave for interleaved Planes */__u32 visual;            /* see FB_VISUAL_*      */ 颜色种类,单色、双色、真彩等__u16 xpanstep;         /* zero if no hardware panning  */__u16 ypanstep;           /* zero if no hardware panning  */__u16 ywrapstep;      /* zero if no hardware ywrap    */__u32 line_length;        /* length of a line in bytes    */一行多少个像素*每个像素多少位unsigned long mmio_start;  /* Start of Memory Mapped I/O   *//* (physical address) */__u32 mmio_len;           /* Length of Memory Mapped I/O  */__u32 accel;          /* Indicate to driver which *//*  specific chip/card we have    */__u16 reserved[3];        /* Reserved for future compatibility */
};

其中可变参数有:

struct fb_var_screeninfo {__u32 xres;            /* visible resolution       */x方向分辨率__u32 yres;                                       y方向分辨率__u32 xres_virtual;     /* virtual resolution       */虚拟X方向分辨率,其实屏幕分辨率在出厂时已经定死了,但是我电脑桌面还是可以调整分辨率,调整的就是这个虚拟分辨率,我们不用这个__u32 yres_virtual;__u32 xoffset;           /* offset from virtual to visible */__u32 yoffset;          /* resolution           */__u32 bits_per_pixel;     /* guess what           */每个像素多少位__u32 grayscale;       /* != 0 Graylevels instead of colors */struct fb_bitfield red;     /* bitfield in fb mem if true color, */在每个像素的颜色中,红色从哪位开始,哪位结束struct fb_bitfield green;    /* else only length is significant */struct fb_bitfield blue;struct fb_bitfield transp; /* transparency         */  __u32 nonstd;           /* != 0 Non standard pixel format */__u32 activate;            /* see FB_ACTIVATE_*        */__u32 height;         /* height of picture in mm    */__u32 width;            /* width of picture in mm     */__u32 accel_flags;      /* (OBSOLETE) see fb_info.flags *//* Timing: All values in pixclocks, except pixclock (of course) */__u32 pixclock;         /* pixel clock in ps (pico seconds) */__u32 left_margin;        /* time from sync to picture    */__u32 right_margin;       /* time from picture to sync    */__u32 upper_margin;       /* time from sync to picture    */__u32 lower_margin;__u32 hsync_len;       /* length of horizontal sync    */__u32 vsync_len;      /* length of vertical sync  */__u32 sync;           /* see FB_SYNC_*        */__u32 vmode;          /* see FB_VMODE_*       */__u32 rotate;         /* angle we rotate counter clockwise */__u32 reserved[5];       /* Reserved for future compatibility */
};

所有需要用到frame buffer设备驱动的设备,在各自的xxxfb.c文件中会通过

register_framebuffer(struct fb_info *fb_info)

函数将自身的frame buffer设备对应的struct fb_info结构体以次设备号为索引注册到registered_fb[fbidx]数组中,register_framebuffer的具体实现在fbmem.c中。
(二)我们需要做什么
我们需要做的就是编写自己的xxxfb.c文件,Linux内核中有许多现成的例子,比如\linux-2.6.22\drivers\video\s3c2410fb.c,可以看看例子怎么做的。在该文件的init函数中:

static struct platform_driver s3c2410fb_driver = {.probe        = s3c2410fb_probe,.remove      = s3c2410fb_remove,.suspend    = s3c2410fb_suspend,.resume        = s3c2410fb_resume,.driver     = {.name   = "s3c2410-lcd",.owner   = THIS_MODULE,},
};int __devinit s3c2410fb_init(void)
{return platform_driver_register(&s3c2410fb_driver);
}

依照总线设备驱动模型,注册了一个平台总线驱动s3c2410fb_driver,里面实现了probe等函数。

4.2 LCD驱动程序之硬件操作

视频介绍:重新简单讲了一下LCD的硬件原理,不喜欢看第一期的视频可以通过这个视频看个大概。

4.3-1 LCD驱动程序之编写代码之初步编写

视频介绍:讲解我们需要做什么,讲了struct fb_info 中固定参数、可变参数、操作函数的具体意义。我们要做的就是编写xxxfb.c文件。
主要操作都在init函数中(实际正规应该用总线设备模型,放在probe函数中):
1、分配一个fb_info结构体
2、设置
2.1 根据实际的设备,设置fb_info结构体中的固定参数、可变参数
2.2 设置操作函数(三个通用函数)
2.3 其他设置
3、硬件相关的操作
3.1 设置GPIO用于LCD
3.2 根据LCD手册设置LCD控制器, 比如VCLK的频率等
3.3 分配显存(framebuffer), 并把地址告诉LCD控制器(这里是Linux和LCD控制器沟通的接口,Linux的驱动知道要把像素数据放到这个地址,LCD控制器知道从这里取数据刷到LCD屏幕上)问题:应用程序是如何通过驱动将一副图片的数据写到这个显存上的呢?
4、以上操作丰富了fb_info结构体,调用register_framebuffer将fb_info结构体向上注册,这里注册会提交次设备号,以后应用时,用户空间传递次设备号给内核,Linux内核通过次设备号调用到这里注册的fb_info结构体。

4.3-2 LCD驱动程序之编写代码之硬件设置

4.3 LCD驱动程序之编写代码之显存和调色板设置

4.4 LCD驱动程序之编写代码之编译测试

记得先装载三个函数的驱动
装载lcd驱动后,由于链接了framebuffer子系统,会出现一个新的/dev/fb文件,/dev/fb是设备文件(具体可看《深入理解Linux内核》535)可以直接往里面写入数据,这些数据就会在LCD屏幕上显示。这也应该是图片浏览器项目中应用程序和LCD驱动之间的接口,应用程序通过操作dev/fb文件去控制LCD的显示内容。

问题:直接echo hello 到 /dev/fb文件中,LCD就会显示hello,LCD是如何识别出hello的?这是framebuffer子系统的功能吗

17:30介绍了内核自带s3c2410fb.c应用总线设备驱动模型之后的框架,韦写的这个是不带驱动模型的。不同的板子有具体的设备文件(这个设备文件不是/dev那种),里面有设备文件,设备文件中有私有数据,里面进行寄存器的操作。如果我们想从内核现有的驱动程序中修改一下拿来用的话,只需要修改私有数据中的寄存器就可以了,驱动端的程序不用动。

4.5 JZ2440V2 V3 LCD驱动程序

讲解了一些关于总线设备驱动模型的东西

5 触摸屏驱动

5.1 触摸屏驱动之概念介绍

前面简单介绍,3min后开始写代码,参考Linux自带的s3c2410的触摸屏驱动,s3c2410_ts.c,但是代码并未用总线设备驱动模型,而是类似2.2,直接根据输入子系统来写。

5.2 触摸屏驱动之编写驱动

没看,和不带输入子系统的区别就是结构类似2.2,然后在中断中的处理变成了识别事件并上报事件。注意一个点就是在输入子系统中事件上报后处理的结果在哪里看(测试方法),我们之前是直接打印出触摸屏点的位置,链接输入子系统后可以通过hexdump看事件的输出信息,具体操作在2.2,5.3有具体触摸屏的应用。

S3C2440的ADC是8路,精度是10位。
Linux内核自带有LCD和触摸屏的驱动,需要先通过make menu config 去掉这些驱动,再insmod自己的驱动。
到这个视频结束,能够支持长按和滑动,能给输出xy的值,方法是直接printk到控制台。
后续我们将prink的操作变成向输入子系统上报事件即可,具体是通input_report_abs函数,三个参数:input_dev,x和y坐标,input_report_abs函数内部是通过input_event实现的。
问题:通过printk打印xy坐标信息很简单,直接在控制台看就可以,但是上报到输入子系统以后,在哪里看呢?
答:视频在5.3的5min处,是通过hexdump将/dev/eventx文件(装载之前ls /dev/event*一下,装载之后再ls一下,确定哪个event是装载后新加的)中的信息转换成十六进制打印到控制台来看。
/dev/eventx中的内容是输入子系统加进去的,hexdump打印到控制台后,会有事件发生时间、事件类型(比如绝对位移)、事件值(XY坐标)

装载触摸屏驱动,会新加/dev/eventx文件,这类event文件都是归属于输入子系统
装载LCD驱动,会新加/dev/fbx文件,这类fb文件都是归属于framebuffer子系统

5.3 触摸屏驱动之使用TSLIB测试

9min之前,包括5.2,只是实现了点击触摸屏能够输出电压信息,还没和LCD联系起来。9min后开始讲解用TSLIB将触摸屏和LCD联系起来,第一期触摸屏部分有具体讲怎么造轮子(五点校正),这里直接讲怎么调用TSLIB库。
在有TSLIB库的基础上,我们的触摸屏驱动只需要做到能显示电压值就行了,不需要显示坐标,剩下的交给库函数。

TSLIB库运行需要先装载LCD和触摸屏驱动,然后编译TSLIB库,然后将编译好的文件装载到根目录,然后做一些配置,比如配置触摸屏是哪个event文件,LCD是哪个fb文件。TSLIB库编译好后会有一些用于校验、测试的可执行程序,接下来直接运行这些程序就会在LCD上出现校验十字架,然后人为点击十字架就可以完成校验,校验完后还可以进行测试等等,此时由于经过了前面的校验,测试时就不再输出电压值,而是输出对应的LCD坐标。

问题1: 在图片浏览器项目中,我如果通过读dev/eventx文件的数据完成应用层和触摸屏驱动的数据交换,应用层是如何知道具体是哪个编号的event文件呢?还是说先两次ls确定好后再在项目中写死某个event编号?
问题2: 图片浏览器项目中,我点击触摸屏,应用程序实际上会得到对应的LCD坐标。这一功能现在已经由TSLIB库实现了,可是视频中TSLIB库是直接将LCD坐标输出到控制台,在应用程序运行时,从哪里去取LCD坐标?实际上问题2覆盖了问题1,应用程序取的不是触摸屏的电压信息,而是经由TSLIB库转换之后的LCD坐标信息。
问题3: 图片浏览器项目中,jpg的库和这里的tslib的库,怎么编译,怎么链接,库的可执行文件存放在哪?

12 I2C裸板

12.1 I2C设备裸板程序之I2C总线介绍

JZ2440上的S3C2440有I2C接口,但是JZ2440这个板子上没有I2C外设。视频和第一期总线的介绍重复。

12.2 I2C设备裸板程序之编写代码

没看

13 Linux 2.6 I2C驱动

13.1 I2C驱动程序之框架

Linux上的I2C驱动框架已经帮我们做好了I2C协议上的处理。首先分析linux-2.6.22.6\drivers\i2c\busses\i2c-s3c2410.c文件:

韦东山第二期课程内容概要相关推荐

  1. 韦东山第一期课程内容概要

    韦东山第一期课程内容概要 1一个嵌入式程序要运行所需的东西 1.1第一条指令:b reset 1.2 reset要完成的事件 1.2.1设置开门狗 1.2.2设置时钟 1.2.3判断启动方式并设置堆栈 ...

  2. 韦东山第三期课程内容概要

    韦东山第三期课程内容概要 1文件浏览器&数码相框 1.1 数码相框之系统框架 2.1 数码相框-字符的编码方式 2.2 数码相框-字符的点阵表示 2.3.1 数码相框-freetype理论介绍 ...

  3. 韦东山第一二期衔接课程内容概要

    韦东山第一二期衔接课程内容概要 0 使得一个裸板Jz2440能运行linux应用程序的过程 1 uboot启动内核总结 1.1 u-boot分析之编译体验 1.2 u-boot分析之Makefile结 ...

  4. Linux 声卡驱动程序-韦东山-专题视频课程

    Linux 声卡驱动程序-2513人已学习 课程介绍         3期的声卡驱动更详细,推荐. 课程收益     熟悉内核声卡驱动框架 讲师介绍     韦东山 更多讲师课程     2003 年 ...

  5. 2014年YY公开课录像-韦东山-专题视频课程

    2014年YY公开课录像-5641人已学习 课程介绍         2014年在YY举办的公开课录像,现场回答网友提的普遍性问题 课程收益     回答网友提的普遍性问题 讲师介绍     韦东山 ...

  6. AI实战营第二期——课程目录

    AI实战营第二期 第一节 AI实战营第二期 第一节<姿态估计与MMPose> 课程链接:https://www.bilibili.com/video/BV1kk4y1L7Xb/ 课程说明: ...

  7. 韦东山第一期学习笔记——重定位

    重定位 说明 必须知道的几个概念 什么是代码重定位? 什么是位置无关码 什么是运行地址 为什么要代码重定位? nand flash启动的情况 nor flash启动的情况 两种方式的重定位 代码重定位 ...

  8. 【数据库课程设计】课程内容概要

    现本科三年级下学期,关于数据库课程设计要求如下: 1.学习并掌握工具 powerdesigner 16 要求:熟练掌握 2.选题的语义描述 要求:第7周提交(当前周次:3周) 3.数据库系统设计 概念 ...

  9. elementui table 不显示表头_不懂就问 || 单晶XRD第二期课后答疑出炉啦!!

    单晶XRD第二期课程答疑终于来啦!单晶XRD第二期上课的内容都会了没不会也没关系往下看老师带着答案走来了提问!回答!1 怎么看独立可观测点和衍射点分别是多少?答:可以在后缀是.abs文件中查看.2 没 ...

  10. 开源软件通识基础:第二周课程回顾与总结

    接第一篇<开源软件通识基础:第一周课程回顾与总结>,本文为第二周课程内容的回顾与总结. 本导学班在调研全球开源教育与课程的基础上,通过收集.整理.理解.拓展国际最新的前沿开源课程,采取众创 ...

最新文章

  1. Intel Realsense D435 Realsense View 错误 RT IC2 Config error
  2. Docker(十二):Docker集群管理之Compose
  3. python和json转换_【Python】python和json数据相互转换,json读取和写入,repr和eval()使用...
  4. 《中国人工智能学会通讯》——4.32 数据包丢失
  5. Kotlin入门(24)如何自定义视图
  6. DataSet 更新 心得(转)
  7. 新型超级生物计算机简介,自然形成的超级生物计算机
  8. mount、umount 挂载卸载命令
  9. 报表工具分析总结报告
  10. 3dmax2017卸载/安装失败/如何彻底卸载清除干净3dmax2017注册表和文件的方法
  11. PSB格式转换太麻烦?Aspose.PSD教你用代码将大型PSB文件转换为PDF/JPEG格式
  12. (附源码)node.js宠物医生预约平台 毕业设计030945
  13. FireShot在windows2000上安装的问题
  14. 汽车基础软件「众生相」
  15. Blog 【如何搭建自己的个人技术博客网站】
  16. 一键卸载电脑自带Office2003
  17. Visa发起区块链B2B支付第一阶段测试
  18. 大学物理复习笔记——光学
  19. 明星直播的品牌效应,这几个关键数据你一定要知道!
  20. ati能备份linux格式吗,ATI备份TIB文件的另类用法

热门文章

  1. 年审是当月还是当天_年审年检7月当月审可以吗
  2. Windows如何编辑hosts
  3. Mac上如何更改AirDrop名称呢
  4. 微信服务器会留撤回的消息吗,让你的微信不再被人撤回消息
  5. python实现Content-Type:application/octet-stream
  6. Spring源码分析:Spring的循环依赖分析
  7. 大数据学习——spark运营案例
  8. 新浪短网址和百度短网址那个好?
  9. Eigen优化及并行性能测试
  10. [数据集][转载]ImageNet 2012 1000分类名称和编号