LCD和USB驱动程序移植

嵌入式Linux设备驱动开发之LCD和USB驱动程序移植

《嵌入式Linux应用完全开发手册》第4篇第24章总结归纳

本章目标

  1. 了解TTY层下LCD和USB键盘驱动程序的框架
  2. 掌握移植LCD驱动程序的方法
  3. 使用LCD和USB设备

LCD驱动程序移植

LCD和USB键盘驱动程序框架

框架概述

具备人机交互功能的串口可以作为控制台和终端,同样,LCD和键盘组合起来也可以。
对LCD的操作可以像串口一样,通过终端设备层的封装(/dev/ttyx设备)来输出内容,也可以通过frame buffer(/dev/fbx)直接再显存上绘制图像。
frame buffer即帧缓冲,是一种独立于硬件的抽象图形设备,它使得应用程序可以通过一组定义良好的接口访问各类图形设备,不需要了解底层硬件细节。从用户的观点来看,frame buffer设备与/dev目录下其他设备没有区别,通过/dev/fbx设备文件来访问它(fb0表示第一个frame buffer设备、fb1表示第二个、…)。
frame buffer设备提供了一些ioctl接口来查询、设置图形设备的属性,比如分辨率、像素位宽等,另外,它属于“普通的”内存设备,类似/dev/mem:可以读(read)、写(write)、移动访问位置(seek)以及将这块内存映射给用户(mmap)。不同的是frame buffer的内存不是所有的内存,而是显卡专用的内存。应用程序可以直接更改frame buffer内存中的数据,效果立刻就能在显示器中看到。
/de//tty1等终端设备文件通过显示驱动程序和键盘驱动程序(还有其他输入设备,比如触摸屏)为它们提供输出、输入功能。
TTY和frame buffer驱动程序的框架如下图所示,输入设备以USB键盘为例:
img not found
drivers/char/vt.c用来支持显示器/键盘组成的终端设备,之所以被称为“虚拟终端”,是因为可以在一个物理终端设备上运行多个“虚拟终端”(也叫虚拟控制台),比如可以使用第一个虚拟终端来显示系统信息,使用第2个虚拟终端来运行文本模式程序,第3个虚拟终端来运行图形程序。它们可以同时运行,使用一些组合键可以切换到某个虚拟终端上。
虚拟终端层管理着这些虚拟终端,比如为它们分配缓冲区、切换虚拟终端时把它的内容输出到显示器、键盘有输入时把数据填入到当前终端的缓冲区中。它向上提供了封装好的接口,向下通过调用显示器/键盘的接口完成输入输出功能。

  1. 显示驱动程序
    drivers/console/fbcon.c文件向上提供了一个很重要的数据结构fb_con,所有的输出都是通过fb_con中的成员函数来实现的,bitblit.c、font.c也都处于drivers/console目录下,它们和drivers/video/fbmem.c一起,实现fb_con结构中的函数。另外,fbmem.c是frame buffer驱动程序,它向应用层提供/dev/fbx设备的访问接口,应用程序可以通过它绘制图形。
    drivers/video/s3c2410fb.c文件是架构相关的代码,它实现LCD控制器的初始化、向fbmem.c注册frame buffer设备,并提供一些与架构相关的函数,比如设置分辨率、像素位宽等需要设置操作寄存器的函数。
  2. 键盘驱动程序
    drivers/input/input.c表示“输入设备”,有键盘、鼠标等。drivers/keyboard.c是键盘驱动程序的封装,在它的下边,可以是一般的键盘,也可以是符合HID规范的键盘。HID是英文“Human Interface Device”得缩写,它通常指USB-HID规范,但是也有其他类型的遵循HID规范的设备(比如蓝牙键盘、蓝牙鼠标)。所以drivers/hid-core.chid-input.c两个文件将HID规范的共性提炼出来,它们的下面是各类具体实现,比如USB的drivers/hid/hidusb/hid-core.chid-quirks.c等。

操作实例

下面以几个操作的函数调用过程来理解TTY和frame buffer驱动程序的层次结构。注意:这只是为了在阅读内核源码时,给读者提供一些函数调用间的脉络关系。刚接触某类驱动时,了解各函数、结构间的调用关系是一件困难的事情。

  1. 注册frame buffer设备时,显示LOGO的过程
    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
    s3c2410fb_probe(video/s3c2410fb.c) ->
    register_framebuffer(fbmem.c) ->
    fb_info->node = i;//registered_fb[i]为空项,本例中i=0
    registered_fb[i] = fb_info;
    fb_notifier_call_chain(fb_notify.c) ->//它会调用fbcon_event_notify(fbcon.c)
    fbcon_fb_registered(video/console/fbcon.c)
    info_idx = idx//即info->node,值为上面的i
    fbcon_takeover(1)(video/console/fbcon.c) ->
    con2fb_map[i] = info_idx;//i=0,info_idx=0
    take_over_console(char/vt.c) ->
    register_con_driver(char/vt.c) ->
    csw->con_startup(...) ->//即fbcon_startup(video/console/fbcon.c)
    info = registered_fb[info_idx];
    info->fbops->fb_open(...)(video/s3c2410fb.c)
    bind_con_driver(char/vt.c) ->
    visual_init(char/vt.c) ->
    vc->vc_sw->con_init //即fbcon_init
    fbcon_init(video/console/fbcon.c) ->
    //以下准备LOGO
    fbcon_prepare_logo(video/console/fbcon.c) ->
    fb_prepare_logo(video/fbmem.c) ->
    fb_logo.logo = fb_find_logo(depth);//logo.c
    //打印Console:swicthing to colour frame buffer device 30x40
    update_screen(vc);(include/linux/vt_kern.h) ->
    redraw_screen(char/vt.c) ->
    vc->vc_sw->con_switch(vc);->//即fbcon_switch(fbcon.c)
    fb_show_logo(video/fbmem.c)//显示LOGO
  2. 对/dev/ttyx调用write函数时的过程
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    tty_write(char/tty_io.c)->
    ld = tty_ldisc_ref_wait(tty)//它就是char/n_tty.c中的tty_ldisc_N_TTY
    do_tty_write(ld->write,tty,file,buf,count)(char/tty_io.c)->
    write_chan(就是上面的ld->write,char/n_tty.c中tty_ldisc_N_TTY的成员函数)->
    tty->driver->write(即con_write,char/vt.c)->
    do_con_write(char/vt.c)->
    vc->vc_sw->con_putcs(即fbcon_putcs,video/console/fbcon.c)->
    ops->putcs(即bit_putcs,video/console/bitblit.c)->
    dst = fb_get_buffer_offset(video/fbmem.c)//获取要写入的显存位置
    bit_putcs_aligned/bit_putcs_unaligned(video/console/bitblit.c)
    src = vc->vc_font.data + (src_readw(s++)&charmask)*cellsize;//获得字符的点阵
    __fb_pad_aligned_buffer(fb.h)//将点阵写入显存
    在使用/dev/ttyx作为控制台的shell中,运行某个程序时,如果里面有“printf(“hello world!”)”字样的语句,它会调用到内核的tty_write函数。
    然后会调用行规程的write_chan函数,它又会调用“tty->driver->write”,对于串口,它是drivers/serial/serial_core.c中的uart_write函数,它直接输出ASCII字符;对于显示器,它是drivers/char/vt.c中的con_write函数,它更复杂。在LCD显示器上显示字符时,先要根据这些字符得到它们的点阵,然后再将它们画出来。
    drivers/video/console/fbcon.c中的fbcon_putcs函数通过drivers/video/console/bitblit.cdrivers/video/fbmem.c提供的一些函数来获得点阵、写到显存中去。其中的“vc->vc_font.data”指向某个字库,以字符为索引即可找到它的点阵。在drivers/video/console/fonts.c文件中定义了一个fonts数组,每个表项是一个字库,比如font_vga_8x8、font_vga_8x16等。在devices/video/fbcon.c中初始化frame buffer控制台时,会把vc->vc_font.data指向某个字库。
  3. USB键盘按下时的函数调用过程
    与串口相似,键盘的读取以中断来驱动。以USB键盘为例,调用过程如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    hid_irq_in(hid/usbhid/hid-core.c) ->
    hid_input_report(hid/hid-core.c) ->
    hid_input_field(hid/hid-core.c) ->
    hid_process_event(hid/hid-core.c) ->
    hidinput_hid_evenet(hid/hid-input) ->
    input_event(input/input.c) ->
    dev->event(...)
    handle->handler->event,即kbd_event(char/keyboard.c) ->
    kbd_rawcode/kbd_keycode(char/keyboard.c) ->
    put_queue(vc,data)(char/keyboard.c) ->//数据放入终端缓冲区
    tty_insert_flip_char(include/linux/tty_flip.h)//放数据
    con_schedule_flip(kbd_kern.h)//唤醒等待数据的进程
    hid_irq_in是USB中断传输方式的中断处理函数,当键盘被按下时,它导致后续的一系列函数被调用,与图24.1对应,它从底层的drivers/hid/usbhid/hid-core.c一直向上调用到drivers/input/input.c中的input_event函数,接着input_event函数根据调用drivers/char/keyboard.c注册的处理函数将数据放入虚拟终端设备的缓冲区中,然后等待数据的进程。

S3C2410/S3C2440 LCD控制器驱动程序移植

从图24.1可知,架构相关的代码为drivers/video/s3c2410fb.c,移植的思想是一样的:先确定LCD控制器所用的资源,然后把它们加入平台设备结构,最后修改代码是这些资源可用。
硬件连线图如下图所示:
img not found

平台设备结构

LCD控制器的平台设备在arch/arm/plat-s3c24xx/devs.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
/* LCD Controller */
static struct resource s3c_lcd_resource[] = {
[0] = {
.start = S3C24XX_PA_LCD,
.end = S3C24XX_PA_LCD + S3C24XX_SZ_LCD - 1,
.flag = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_LCD,
.end = IRQ_LCD,
.flag = IORESOURCE_IRQ,
}
};

static u64 s3c_device_lcd_dmamask = 0xffffffffUL;

struct platform_device s3c_device_lcd = {
.name = "s3c2410-lcd",
.id = -1,
.num_resources = ARRAY_SIZE(s3c_lcd_resource),
.resource = s3c_lcd_resource,
.dev = {
.dma_mask = &s3c_device_lcd_dmamask,
.coherent_dma_mask = 0xffffffffUL,
}
};

s3c_device_lcd结构,已经加入了S3C2410、S3C2440开发板的设备列表中了。

  1. arch/arm/mach-s3c2410/mach-smdk2410.c
    1
    2
    3
    4
    5
    static struct platform_device *smdk2410_devices[] __initdata = {
    ...
    &s3c_device_lcd,
    ...
    };
  2. arch/arm/mach-s3c2440/mach-smdk2440.c
    1
    2
    3
    4
    5
    static struct platform_device *smdk2440_devices[] __initdata = {
    ...
    &s3c_device_lcd,
    ...
    };
    而LCD控制器驱动程序drivers/video/s3c2410fb.c的入口函数为:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    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);
    }
    平台设备s3c_device_lcd和平台驱动s3c2410fb_driver的名字都是“s3c2410-lcd”,所以注册了s3c2410fb_driver之后,它的s3c2410fb_probe函数将被调用来设置LCD控制器。

底层驱动代码分析及修改

s3c2410fb_probe函数完成初始化LCD控制器、注册中断处理函数、注册frame buffer设备等工作,它的流程图如下图所示:
img not found
这个函数中,与单板相关的就是其中的mach-info结构。它是平台设备s3c_device_lcd结构中的dev.platform_data成员,读者可以查看s3c2410fb_init_registers函数来了解它的功能。但是在前面看到的s3c_device_lcd结构中,并没有指定这个成员。它在其他函数中设置;对于S3C2440,单板初始化函数smdk2440_machine_init调用s3c24xx_fb_set_platdata函数来设置;对于S3C2410,没有设置。
smdk2440_machine_init函数在arch/arm/mach-s3c2440/mach-smdk2440.c中,如下所示:

1
2
3
4
5
static void __init smdk2440_machine_init(void)
{
s3c24xx_fb_set_platdata(&smdk2440_lcd_cfg);
...
}

smdk2440_lcd_cfg结构表示LCD控制器的一些配置,比如分辨率、时间特性等。
s3c24xx_fb_set_platdata函数在arch/arm/plat-s3c24xx/devs.c中,它直接将参数smdk2440_lcd_cfg赋给设置平台设备s3c_device_lcd结构中的dev.platform_data成员。代码如下:

1
2
3
4
5
6
7
void __init s3c24xx_fb_set_platdata(struct s3c2410fb_mach_info *pd)
{
...
memcpy(npd,pd,sizeof(*npd));
s3c_device_lcd.dev.platform_data = npd;
...
}

所以,对于S3C2440,需要修改smdk2440_lcd_cfg结构;对于S3C2410,仿照S3C2410增加一个smdk2410_lcd_cfg结构,并调用s3c24xx_fb_set_platdata函数来设置它。
smdk2440_lcd_cfg是s3c2410fb_mach_info结构类型,这个类型在include/asm-arm/arch-s3c2410/fb.h文件中定义,下面分析它的各个成员的意义。

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
struct s3c2410fb_mach_info {
unsigned char fixed_syncs; /* do not update sync/border */

/* LCD types */
int type;

/* Screen size */
int width;
int height;

/* Screen info */
struct s3c2410fb_val xres;
struct s3c2410fb_val yres;
struct s3c2410fb_val bpp;

/* lcd configuration registers */
struct s3c2410fb_hw regs;

/* GPIOs */

unsigned long gpcup;
unsigned long gpcup_mask;
unsigned long gpccon;
unsigned long gpccon_mask;
unsigned long gpdup;
unsigned long gpdup_mask;
unsigned long gpdcon;
unsigned long gpdcon_mask;

/* lpc3600 control register */
unsigned long lpcsel;
};

fixed_syncs被设为1时表示“固定的”时间参数和边框大小,这意味着用户程序无法调整分辨率等参数,因为底层驱动不修改时间参数和边框大小。从s3c2410fb.c中的相关代码来看,它就是不在重新设置LCDCON2/3/4寄存器中的相关位。
type表示LCD的类型,从LCDCON1寄存器位[6:5]可以知道它有4种取值,如下所示:

1
2
3
4
00 = 4-bit dual scan display mode (STN)
01 = 4-bit single scan display mode (STN)
10 = 8-bit single scan display mode (STN)
11 = TFT LCD panel

widthheight用来设置图像的宽度和高度,它们取xres、yres的默认值。
s3c2410fb_val结构的定义如下,xres、yres和bpp分别表示图像宽度、高度和像素位宽的最小、最大、默认值。

1
2
3
4
5
struct s3c2410fb_val {
unsigned int defval;
unsigned int min;
unsigned int max;
};

struct s3c2410fb_hw regs表示LCDCON1-LCDCON5共5个LCD控制器的控制寄存器。它们用来设置LCD类型、像素数据的格式。
gpcupgpcup_maskgpccongpccon_maskgpdupgpdup_maskgpdcongpdcon_mask用来设置GPC、GPD两组GPIO引脚,gpcupgpccon_mask两个成员被用来设置GPCUP寄存器:gpcup表示新值,gpccon_mask表示要设置的位。
lpcsel表示LPCSEL寄存器,它用来支持SEC公司生产的TFT LCD,对于一般的LCD,不用设置这个寄存器。
本开发板使用240x320,16bpp的TFT LCD,内核自带的smdk2440_lcd_cfg结构并不适用于这个开发板,并且它的设置有一些错误:没有指定GPIO寄存器的值,“type”设置错了。原来的值如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static struct s3c2410fb_mach_info smdk2440_lcd_cfg __initdata = {
...
#if 0
/* currently setup by downloader */
.gpccon = 0xaa940659,
.gpccon_mask = 0xffffffff,
.gpcup = 0x0000ffff,
.gpcup_mask = 0xffffffff,
.gpdcon = 0xaa84aaa0,
.gpdcon_mask = 0xffffffff,
.gpdup = 0x0000faff,
.gpdup_mask = 0xffffffff,
#endif
...
.type = S3C2410_LCDCON1_TFT16BPP,
...
};

它把GPIO的值屏蔽掉了,原因是“currently setup by downloader”,这也许是这个驱动的开发者在调试时,另外使用某种下载器来设置GPIO。
上面的type被设置为S3C2410_LCDCON1_TFT16BPP,这是错误的,“type”表示“类型”,而“S3C2410_LCDCON1_TFT16BPP”表示“TFT”类型下数据的格式。应该设为以下4个值之一:

1
2
3
4
#define S3C2410_LCDCON1_DSCAN4	   (0<<5)
#define S3C2410_LCDCON1_STN4 (1<<5)
#define S3C2410_LCDCON1_STN8 (2<<5)
#define S3C2410_LCDCON1_TFT (3<<5)

下面修改代码

  1. 对于S3C2440单板
    修改smdk2440_lcd_cfg结构,它在arch/arm/mach-s3c2440/mach-smdk2440.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
    /* LCD driver info */
    static struct s3c2410fb_mach_info smdk2440_lcd_cfg __initdata = {
    .regs = {

    .lcdcon1 = S3C2410_LCDCON1_TFT16BPP |
    S3C2410_LCDCON1_TFT |
    S3C2410_LCDCON1_CLKVAL(0x04),

    .lcdcon2 = S3C2410_LCDCON2_VBPD(1) |
    S3C2410_LCDCON2_LINEVAL(319) |
    S3C2410_LCDCON2_VFPD(5) |
    S3C2410_LCDCON2_VSPW(1),

    .lcdcon3 = S3C2410_LCDCON3_HBPD(36) |
    S3C2410_LCDCON3_HOZVAL(239) |
    S3C2410_LCDCON3_HFPD(19),

    .lcdcon4 = S3C2410_LCDCON4_MVAL(13) |
    S3C2410_LCDCON4_HSPW(5),

    .lcdcon5 = S3C2410_LCDCON5_FRM565 |
    S3C2410_LCDCON5_INVVLINE |
    S3C2410_LCDCON5_INVVFRAME |
    S3C2410_LCDCON5_PWREN |
    S3C2410_LCDCON5_HWSWP,
    },

    /* currently setup by downloader */
    .gpccon = 0xaaaaaaaa,
    .gpccon_mask = 0xffffffff,
    .gpcup = 0xffffffff,
    .gpcup_mask = 0xffffffff,
    .gpdcon = 0xaaaaaaaa,
    .gpdcon_mask = 0xffffffff,
    .gpdup = 0xffffffff,
    .gpdup_mask = 0xffffffff,

    .fixed_syncs = 1,
    .type = S3C2410_LCDCON1_TFT,

    .width = 240,
    .height = 320,

    .xres = {
    .min = 240,
    .max = 240,
    .defval = 240,
    },

    .yres = {
    .min = 320,
    .max = 320,
    .defval = 320,
    },

    .bpp = {
    .min = 16,
    .max = 16,
    .defval = 16,
    },
    };
  2. 对于S3C2410单板
    仿照arch/arm/mach-s3c2440/mach-smdk2440.c来修改arch/arm/mach-s3c2410/mach-smdk2410.c
    ①增加smdk2410_lcd_cfg结构
    直接把smdk2440_lcd_cfg的内容搬到mach-smdk2410.c中,改名为smdk2410_lcd_cfg即可。
    ②使用smdk2410_lcd_cfg结构
    在S3C2410单板初始化函数smdk2410_init中,调用s3c24xx_fb_set_platdata函数。除增加的smdk2410_lcd_cfg结构外,还要增加如下所示的代码:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #include <asm/arch/fb.h>
    ...
    /* LCD driver info */
    static struct s3c2410fb_mach_info smdk2440_lcd_cfg __initdata = {
    ...
    };
    ...
    static void __init smdk2410_init(void)
    {
    s3c24xx_fb_set_platdata(&smdk2410_lcd_cfg);
    ...
    }

配置内核以使用LCD

对LCD的配置有两方面,一是frame buffer方面的配置,二是控制台方面的配置。
配置内容如下:

1
2
3
4
5
6
7
8
9
Device Drivers --->
Graphics support --->
<*> Support for frame buffer devices //支持frame buffer
<*> S3c2410 LCD frame buffer support //支持S3C24xx
Console display driver support --->
<*> Frame buffer Console support //支持frame buffer控制它
[ ] Select Compile-in fonts //选择字库,默认为VGA 8x8、VGA 8x16字库
[*] Bootup logo ---> //启动时显示LOGO
[*] Standard 224-color Linux logo //选择LOGO图像,有单色、16色、244色
  1. 通过LCD显示内核信息
    以前使用串口作为控制台(打印内核信息)时,命令行参数为“console=ttySAC0”,现在可以多加一项,比如“console=ttySAC0 console=tty1”。tty1表示第一个虚拟终端,tty2表示第二个虚拟终端,而tty0表示当前的虚拟终端。
  2. 操作/dev/tty1输出字符:如果使用mdev机制,这个步骤可以省略。
    1
    2
    3
    4
    5
    6
    7
    mknod /dev/tty0 c 4 0
    mknod /dev/tty1 c 4 1
    mknod /dev/tty2 c 4 2
    mknod /dev/tty3 c 4 3
    mknod /dev/tty4 c 4 4
    mknod /dev/tty5 c 4 5
    mknod /dev/tty6 c 4 6
    在串口控制台,使用“echo hello > /dev/tty0”命令可以在LCD上显示“hello”字符串。
  3. 操作/dev/fb0绘制图像
    首先如下创建设备文件,如果使用mdev机制,这个步骤可以省略:
    1
    mknod /dev/fb0 c 29 0
    然后使用frame buffer测试程序执行“fb_test /dev/fb0”即可在LCD上看到很多同心圆,并且在控制台打印出frame buffer的属性。

USB驱动程序移植

USB驱动程序概述

USB(Universal Serial Bus)即“通用串行外部总线”,在各种场所已经大量使用。它接口简单(只有5V和GND、两根数据线D+和D-),可以外接硬盘、键盘、鼠标、打印机等多种设备。要使用尽可能少的接口支持尽可能多的外设,USB是一个好的选择,在嵌入式设备中尤其如此。
USB总线规范有1.1版和2.0版。USB1.1支持两种传输速率:低速(Low Speed)1.5Mbit/s、全速12Mbit/s,对于鼠标、键盘、CD-ROM等设备,这样的速率足够。但是在访问硬盘、摄像机时,就显得很慢。为此,USB2.0提供了一种更高的传输速率:高速,它可以达到480Mbit/s。USB2.0向下兼容USB1.1,可以遵循USB1.1规范的设备连接到USB2.0控制器上,也可以把USB2.0的设备USB1.1控制器上。
USB总线的硬件拓扑结构如下图所示。
USB主机控制器(USB Host Controller)通过根集线器(Root Hub)与其他设备相连接。集线器也属于USB设备,通过它可以在一个USB接口上扩展出多个接口。除根集线器外,最多可以层叠5个集线器,每条USB电缆的最大长度是5m,所以USB总线的最大距离为30m。一条USB总线上可以外接127个设备,包括根集线器和其他集线器。整个结构图就是一个星状结构,一条USB总线上所有设备共享一条通往主机的数据通道,同一时刻只能有一个设备与主机通信。
img not found
通过USB主机控制器来管理外接的USB设备,USB主机控制器共分3种,UHCI、OHCI和EHCI,其中的“HCI”表示“Host Controller Interface”。UHCI、OHCI属于USB1.1的主机控制器规范,而EHCI是USB2.0的主机控制规范。UHCI(Universal HCI),它是由Intel公司制定得标准,它的硬件做的事情少,这使得软件比较复杂。与之相对的是OHC(Open HCI),它由Compaq、Microsoft和National Semiconductor联合制定,在硬件方面它具备更多的智能,使得软件相对简单。
这些差别只存在于底层的USB主机控制器的驱动程序,对它之上的软件没有影响。USB2.0的主机控制程序只有EHCI(Enhanced HCI)一种
在配置内核的时候,经常可以看到“HCD”字样,它表示“Host Controller Drivers”,即主机控制器驱动程序。比如有uhci-hcd、ohci-hcd、ehci-hcd等驱动模块。
USB驱动程序分两类:USB主机控制器驱动程序(Host Controller Drivers)、USB设备驱动程序(USB device drivers)。它们在内核中的层次如图所示。
img not found
USB主机控制器驱动程序提供访问USB设备的接口,它只是一个“数据通道”,至于这些数据有什么用,这要靠上层的USB设备驱动程序来解释。USB设备驱动程序使用下层驱动提供的接口来访问USB设备,不需要关心传输的具体细节。

配置内核支持USB键盘、USB鼠标和USB硬盘

S3C2410/S3C2440的USB控制器有如下特性
符合OHCI1.0规范
支持USB1.1版本
有两个插口
支持低速设备和全速设备

Linux内核中对OHCI主机控制器支持完善,并有多种USB设备驱动程序。Linux2.6.22.6也已经支持S3C2410/S3C2440的USB控制器,只不过第二个插口上电后默认为USB Device插口,如果要将它改为USB Host插口(比如没有USB集线器,却需要同时接入USB键盘、USB鼠标时),只要设置MISCCR寄存器的位3即可,所有的修改都在文件drivers/usb/host/ohci-s3c2410.c中完成,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <asm/arch/regs-gpio.h>
...
static int usb_hcd_s3c2410_probe(const struct hc_driver *driver,
struct platform_device *dev)
{
struct usb_hcd *hcd = NULL;
int retval;

/* 2 host port */
writel(readl(S3C2410_MISCCR) | S3C2410_MISCCR_USBHOST, S3C2410_MISCCR);

s3c2410_usb_set_power(dev->dev.platfrom_data,1,1);
...
}

现在只需要配置内核启用它们:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Device Drivers --->
SCSI device support --->
<*> SCSI device support //要支持USB磁盘,这项要选上
[*] legacy /proc/scsi/ support //在/proc/scsi 目录下提供以下信息
<*> SCSI disk support //SCSI硬盘,要支持U盘等,这项要选上

USB support --->
<*> Support for Host-side USB //USB主机控制器
[*] USB device filesystem //在/proc文件系统中提供一些信息,调试用
<*> OHCI HCD support //OHCI主机控制器驱动程序
<*> USB mass Storage support //USB存储设备

HID Devices --->
<*> USB Human Interface Device (full HID) support //USB键盘、USB鼠标等HID设备
[*] /dev/hiddev raw HID device support //以原始(raw)的方式访问HID设备

USB控制器的时钟是在U-Boot中设置的,UCLK必须设为48MHZ。

USB设备的使用

连接USB设备时需要注意:S3C2410/S3C2440既可以作为USB主机,也可以作为USB设备。作为USB主机时对外提供两个接口,对应板上叠起来的两个USB接口,下面的称为HOST1,上面的称为HOST2;作为USB设备时,对外也提供一个接口,对应板上的USB_DEVICE接口。
HOST2和USB_DEVICE在S3C2410/S3C2440上的引脚是复用的。要在开发板上使用两个USB设备时,除HOST1外,可以设置跳线使用HOST2;要使用更多的USB设备,必须通过USB集线器来连接。

使用LCD和USB键盘作为终端

现有的内核已经支持LCD和USB键盘,可以使用它们来作为控制台、终端了。前面说过,在命令行参数中增加“console=tty1”就可以在LCD上显示内核信息,不过要想使用它们来登录系统,需要修改/etc/inittab文件,增加以下6行:

1
2
3
4
5
6
tty1::askfirst:-/bin/sh
tty2::askfirst:-/bin/sh
tty3::askfirst:-/bin/sh
tty4::askfirst:-/bin/sh
tty5::askfirst:-/bin/sh
tty6::askfirst:-/bin/sh

它们在6个虚拟终端上启动shell程序,接上USB键盘和LCD后,可以看到如下字样的提示信息:

1
Please press Enter to activate this consorl

在键盘上按回车键,就可以像在串口终端上一样使用USB键盘、LCD来控制系统了。

使用U盘

首先在开发板上创建如下设备文件

1
2
3
4
5
mknod /dev/sda b 8 0
mknod /dev/sda1 b 8 1
mknod /dev/sda2 b 8 2
mknod /dev/sda3 b 8 3
mknod /dev/sda4 b 8 4

接U盘后,即可像前面使用硬盘、SD卡一样来使用U盘了。

1
2
3
fdsik /dev/sda              //进入菜单,对U盘进行分区,修改分了一个主分区/dev/sda1
mkdosfs -F 32 /dev/sda1 //格式化为FAT32文件系统
mount /dev/sda1 /mnt //挂接