Linux内核升级LCD驱动的更换(开发板)
内核升级 ,更换驱动 ,LCD
概 述
本文给出了将一个已有的LCD驱动添加进一个新的linux内核中的方法
本文搜集整理了Linux系统编译时的主要配置选项(make config)的详细说明,供Linux裁剪特别是设备驱动和模块功能增删时使用参考。需要注意的是,每个版本linux版本的config各选项意义,命名等都可能有所差异。
1、 修改内核根目录config文件
文本方式打开config文件,将LCD的几个编译开关添加进Frame buffer hardware drivers这一项目下,之后在make menuconfig中就可以看到。
#
# Frame buffer hardware drivers
#
CONFIG_FB_SIDSA=y
CONFIG_FB_TCM=y
# CONFIG_FB_SDRAM is not set
CONFIG_TFT_AT91=y
CONFIG_FB_SIDSA_DEFAULT_BPP=16
# CONFIG_FB_SIDSA_DEBUG is not set
在控制台显示驱动项目中将linux logo显示的编译开关打开,logo显示16色或其他均可。
#
# Console display driver support
#
CONFIG_LOGO=y
# CONFIG_LOGO_LINUX_MONO is not set
CONFIG_LOGO_LINUX_VGA16=y
添加好上述config选项后,使用make menuconfig打开该config文件,即可看到这些选项,一一进行确认:
LCD的配置项位于drivers->graphices support下 分别将frame buffer和logo的选项选中,才能使用逻辑屏和logo图片。Bootup logo中可选黑白图,16色图或256色图其中一种均可。
Frame buffer的设备驱动选中SIDSA控制器,目前逻辑屏比较小为QVGA,数据源选为TCM等选项。开发板具有内置大约1M多的SRAM,当逻辑屏比较大时,应选SDRAM.
2、 添加屏驱动源码到工程目录
Linux的屏驱动一般位于drivers\video 目录下,目前AT91SAM926开发板使用的LCD驱动是sidsafb.c和sidsafb.h,at91sam9261_lcdc.h三个文件,在标准的linux2.6.25上是没有的,从开发板源码包中复制过来放到该目录下(还要进行一些代码修改,后面的步骤会讲到)。
3、修改逻辑屏驱动模块的Makefile文件
打开drivers\video目录下的Makefile文件,在其中添加sidsafb.o文件的编译选项,依赖于编译开关CONFIG_FB_SIDSA,也可使用文件比较的方式和开发板同名makefile比较,将下图蓝色方框部分复制到你的makefile
4、修改模块的Kconfig文件
打开drivers\video目录下的Kconfig文件,添加FB_SIDSA, FB_TCM等几个编译开关的配置,也可使用文件比较器将开发板源码包下的同名Kconfig文件比较,将下图蓝色方框部分的几个编译开关选项复制添加到你的drivers\video\Kconfig中,如下图:
5、源代码的修改
移植驱动时有两种工作方式,一种是所有驱动同时一并移植,这样做的好处是各驱动模块公共文件如寄存器表,相关宏的头文件放置的比较合理,移植完后清晰,缺点是交叉错误多,调试工作量大,一两个人无法完成。另一种是单个模块串行一一移植,好处是调试比较好调,缺点是移植完代码可能比较乱。综合一下,个人认为驱动整合移植时先移植所有公共的头文件,公共的功能函数和宏,不添加具体模块,编译出一个基本版本,在此版本基础上在分头移植各个模块比较合理。
单驱动模块移植时,总结下来源代码的修改主要包括几个部分:
a) 驱动的依赖关系整理清楚,若本驱动还依赖其他驱动,则还需要先移植添加其他驱动模块
b) 理清所添加驱动对外部变量,函数,宏的引用关系,去除编译错误。
c) 修改虚拟地址->物理地址的映射函数为当前平台的映射函数,使寄存器map表映射正确。
d) 修改驱动的注册和加载部分。
具体改动如下:(此部分改动已有改好的版本在FTP,列举只是为了说明问题和给出方法)
● 模块依赖关系和编译错误的整理
由于本开发板的屏是RGB屏,所以实际驱动是由AT91所带的LCDC控制器来完成,因此屏的驱动主要工作是配置LCDC和管理Frame buffer两部分。LCDC依赖于另外两个模块:IO和CLOCK(即代码中的PIO和PMC模块),IO用来控制LCDC的电源,CLOCK用来给出LCDC时序的时基。所以先要添加PIO和PMC部分的代码.为了不至于要挂载整个PIO和PMC模块,开发板的BSP将一些简单的功能独立出来作为函数供其他模块调用。
从开发板源码中找到PIO的寄存器定义相关宏,拷贝出来添加到sidsafb.c头部或sidsafb.h,不嫌麻烦的话也可另建头文件,PIO的寄存器主要是这些宏:
#define PIO_PER (0x0000) /**< PIO Enable Register */
#define PIO_PDR (0x0004) /**< PIO Disable Register */
#define PIO_PSR (0x0008) /**< PIO Status Register */
#define PIO_OER (0x0010) /**< Output Enable Register */
#define PIO_ODR (0x0014) /**< Output Disable Registerr */
#define PIO_OSR (0x0018) /**< Output Status Register */
#define PIO_IFER (0x0020) /**< Input Filter Enable Register */
………
同样添加PMC模块的寄存器宏,主要是这些:
#define PMC_SCER (0x0000) /**< System Clock Enable Register */
#define PMC_SCDR (0x0004) /**< System Clock Disable Register */
#define PMC_SCSR (0x0008) /**< System Clock Status Register */
#define PMC_PCER (0x0010) /**< Peripheral Clock Enable Register */
#define PMC_PCDR (0x0014) /**< Peripheral Clock Disable Register */
#define PMC_PCSR (0x0018) /**< Peripheral Clock Status Register */
#define PMC_MOR (0x0020) /**< Main Oscillator Register */
……..
添加各模块寄存器读写的宏
#define readreg_pmc(offset) readl(AT91C_VA_BASE_PMC + offset)
#define writereg_pmc(value, offset) writel(value, (AT91C_VA_BASE_PMC + offset))
有了这些宏,对IO和CLOCK的操作函数基本就可以加进来编译,屏驱动中引用到的操作函数主要是:
at91_gpio_set_level
at91_gpio_periph_enable
at91_gpio_configure
at91_device_pio_setup
at91_enable_periph_clock
at91_disable_system_clock
at91_enable_system_clock
at91_lcdc_clock_enable
at91_lcdc_power_up
● 虚拟地址-物理地址转换函数的修改
去除上述产生的所有编译错误,将其他一些零碎的未定义的对象添加进来,基本就可以编译通过了。但还不能运行,一般地讲,两个linux版本的虚拟地址到物理地址的转换关系是不一致的,而驱动对寄存器的访问都是通过虚拟地址进行,因此要使驱动能在新的linux内核上运行,必须使用该linux的虚拟地址-物理地址转换公式,获取到正确的寄存器虚拟地址。
Linux系统的设备内存映射都映射到系统空间(0xc00000000-0xFFFFFFFF)的固定映射区之内,映射方式是线性、连续的,因此设备的物理地址和虚拟地址只相差一个常数偏移量,当物理内存小于896M时,高端映射区未用,固定映射区的首地址一般就是紧接在vmalloc区的后面,不同的版本不同的芯片体系中vmalloc区的尾地址定义有所不同,在vmalloc.h中定义:
Linux 2.6.15-AT91定义为
#define VMALLOC_END (0xFF000000 - 0x00200000)
Linux 2.6.25-AT91定义为
#define VMALLOC_END (AT91_VIRT_BASE & PGDIR_MASK)
在Linux2.6.15-AT91体系中,物理地址到虚拟地址的转换相差的常数依赖于VMALLOC_END(/include/asm-arm/arch-at91sam9261/hardware.h):
#define AT91C_IO_PHYS_BASE 0xFFFFF000
#define AT91C_IO_VIRT_BASE VMALLOC_END
/* Convert a physical IO address to virtual IO address */
#define AT91_IO_P2V(x) ((x) - AT91C_IO_PHYS_BASE + AT91C_IO_VIRT_BASE)
即物理地址转换为虚拟地址时,虚拟地址=物理地址-(0xFFFFF000- VMALLOC_END)
在Linux2.6.25-AT91体系中,IO物理地址到物理地址转换公式为:
#define AT91_IO_P2V(x) ((x) - AT91_IO_PHYS_BASE + AT91_IO_VIRT_BASE)
但其物理基地址AT91_IO_PHYS_BASE和虚拟基地址AT91_IO_VIRT_BASE和Linux2.6.15版本均不同,(由于对其的长度不同,因此IO物理基地址可能不同版本都不同,但具体到任意一个寄存器的物理地址,最终换算出来基地址+OFFSET都是一样的),因此寄存器的访问,必须采用当前体系的基地址,当前体系的OFFSET,当前体系的映射方式AT91_IO_P2V,才能得到当前体系下对应的正确的虚拟地址。
代码中将旧的映射函数AT91_IO_P2V屏蔽不用,采用新版本同名的AT91_IO_P2V映射函数,并将所有使用到的寄存器基地址,偏移量修改为当前体系的值,此处只用到LCDC,PIO,PMC三片寄存器,将其基地址改为当前版本定义值:
#define AT91C_VA_BASE_PIOA AT91_IO_P2V(AT91C_BASE_PIOA)
#define AT91C_VA_BASE_PMC AT91_IO_P2V(AT91C_BASE_PMC)
改为
#define AT91C_VA_BASE_PIOA AT91_IO_P2V(AT91_PIOA)
#define AT91C_VA_BASE_PMC AT91_IO_P2V(AT91_PMC)
至此,在当前体系下寄存器的虚拟地址就可以正确地得到了,使用虚拟地址去配置寄存器,才不会产生分页错,data abort之类地内存错误,
● 设备注册和初始化的修改
设备的注册和初始化一般位于arch\arm\对应体系目录下,使用AT91芯片时,主要是at91sam9261.c,at91sam9261_devices.c和board-sam9261ek.c三个代码文件和一些.h文件,第一个at91sam9261.c负责总的调用和定义,第二个at91sam9261_devices.c负责主要片内控制器的定义,初始化,其中LCDC就包含在这里面。第三个board-sam9261ek.c负责片外板上设备的定义和初始化,同一款芯片不同的板子不同的硬件在这里区别。
在at91sam9261_devices.c中找到LCD的定义和初始化,标准Linux2.6.25使用的是CONFIG_FB_ATMEL,将其改为当前开发板对应的代码,主要是RGB屏时序的定义,FB的位置区域,和普通设备device不一样,LCDC属于平台标配设备,其类型为platform_device,需要定义对DMA的使用等,将其设备名改为sidsafb.c文件中定义的"sidsa-lcdc",这样启动时才能找到"sidsa-lcdc"并将其挂载。这样,LCD驱动就移植完毕,编译通过,烧写后启动即可亮屏,如果想修改logo图片,到drivers\video\logo目录下修改图片,或直接修改其对应的数组。
LCDC的设备是:
static struct platform_device at91_lcdc_device = {
.name = "sidsa-lcdc",
.id = 0,
.num_resources = ARRAY_SIZE(lcdc_resources),
.resource = lcdc_resources,
.dev = {
.dma_mask = &lcdc_dmamask,
.coherent_dma_mask = DMA_BIT_MASK(32),
.platform_data = &lcdc_data,
},
};
调试过程中可在关键的函数中使用printk添加一些打印语句,如:
init/main.c中的start_kernel函数是内核启动的第一个函数,在这里进行各项初始化,可在其中加一些打印观察情况;
at91sam9260.c和at91sam9261_devices.c中进行芯片设备的加载,可加一些打印,观察哪些设备加载成功,哪些加载失败。
观察所加的设备"sidsa-lcdc”是否被正常挂载,可在设备的初始化函数sidsafb_init中添加打印,观察是否进入其中初始化,且注册设备driver_register时返回的值是否正确等。
编译时,要使用AT91-SAM926的config文件,由于每个板子的内存基地址和长度不一样,因此要在config中定义,也可以在代码中定义:(include\asm-arm\arch-at91\hardware.h)
#define AT91_SDRAM_BASE 0x20000000
屏基本上可以亮了,但还有个bug,就是显示完logo后之后一会儿就灭掉了,这是因为Linux2.6.25版本上的默认BSP时钟使用情况和现有开发板不一致,LCD定义的CLOCK有冲突,将如下CLK定义宏暂时注销即可正常显示:
//#define AT91_PMC_HCK1(1 << 17) /* AHB Clock (LCD) [AT91SAM9261 only] */
//lingzj remove
#define AT91_PMC_HCK1 (1 << 1) /* AHB Clock (LCD) [AT91SAM9261 only] */
—— 完 ——