跬步千里,窥叶知秋

跬步千里,窥叶知秋

Lcd(二)添加至内核

一、LCD驱动框架

#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/module.h>
#include <linux/fb.h>
#include <linux/dma-mapping.h>
#include <linux/clk.h>
#include <linux/string.h>

static struct fb_info *lcd_info;

static int lcd_init(void)
{
	/* 1. 分配一个fb_info */
	lcd_info = framebuffer_alloc(0, NULL);

	/* 2. 设置 */
	/* 2.1 设置固定的参数 fix */
	/* 2.2 设置可变的参数 var */
	/* 2.3 设置操作函数 fbops*/
	/* 2.4 其他的设置 */

	/* 3. 硬件相关的操作 */
	/* 3.1 配置GPIO用于LCD */
	/* 3.2 根据LCD手册设置LCD控制器, 比如VCLK的频率等 */
	/* 3.3 分配显存(framebuffer), 并把地址告诉LCD控制器 */

	/* 4. 注册 */
	register_framebuffer(lcd_info);
	
	return 0;
}

static void lcd_exit(void)
{
}

module_init(lcd_init);
module_exit(lcd_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Jerry.Gou");
MODULE_DESCRIPTION(TQ210 lcd driver);

添加LCD设备驱动至内核,点击下载代码

tq210_fb.h

#include <linux/module.h>
#include <linux/fb.h>
#include <linux/dma-mapping.h>
#include <linux/clk.h>
#include <linux/string.h>

static struct fb_info *lcd_info;
unsigned long pseudo_palette[16];

/*LCD GPIO Pins*/
volatile unsigned long* gpf0con;
volatile unsigned long* gpf1con;
volatile unsigned long* gpf2con;
volatile unsigned long* gpf3con;
volatile unsigned long* gpd0con;
volatile unsigned long* gpd0dat;
volatile unsigned long* display_control;

/*LCD Controler Pins*/
struct S5PV210_lcd_regs{
    volatile unsigned long vidcon0;
    volatile unsigned long vidcon1;
    volatile unsigned long vidcon2;
    volatile unsigned long vidcon3;
    
    volatile unsigned long vidtcon0;
    volatile unsigned long vidtcon1;
    volatile unsigned long vidtcon2;
    volatile unsigned long vidtcon3;
    
    volatile unsigned long wincon0;
    volatile unsigned long wincon1;
    volatile unsigned long wincon2;
    volatile unsigned long wincon3;
    volatile unsigned long wincon4;
    
    volatile unsigned long shadowcon;
    volatile unsigned long reserve1[2];
    
    volatile unsigned long vidosd0a;
    volatile unsigned long vidosd0b;
    volatile unsigned long vidosd0c;

	
};
volatile unsigned long* vidw00add0b0;
volatile unsigned long* vidw00add1b0;

struct clk *lcd_clk;
struct S5PV210_lcd_regs *tq210_lcd_regs;

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 tq210_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 struct fb_ops tq210_lcd_fbops = {
	.owner		= THIS_MODULE,
	.fb_setcolreg	= tq210_lcdfb_setcolreg,//设置color寄存器和调色板
	//下面这3个函数是通用的
	.fb_fillrect	= cfb_fillrect,//画一个矩形
	.fb_copyarea	= cfb_copyarea,//数据拷贝
	.fb_imageblit	= cfb_imageblit,//图像填充
};

static int __init lcd_init(void)
{
	int ret;
	//1. 分配fb_info
	lcd_info = framebuffer_alloc(0, NULL);
	if(lcd_info == NULL){
		printk(KERN_ERR "alloc framebuffer failed!\n");
		return -ENOMEM;
	}

	//2. 配置fb_info参数
	//2.1 fix固定参数
	strcpy(lcd_info->fix.id, "TQ210_lcd");
	lcd_info->fix.smem_len 			   =   800 * 480 * 32/8; //缓冲区大小(全屏字节数),采用32bpp表示32位表示一个像素点
	lcd_info->fix.type 				   =   FB_TYPE_PACKED_PIXELS;
	lcd_info->fix.visual 			   =   FB_VISUAL_TRUECOLOR; //TFT 真彩色
	lcd_info->fix.line_length 			   =   800 * 4; //行字节数,一行800个点

	//2.2 var可变参数
   	lcd_info->var.xres				   =   800;//X轴(行)的实际像素
   	lcd_info->var.yres				   =   480;//y轴(列)实际像素
   	lcd_info->var.xres_virtual		   =   800;//虚拟屏,设置虚拟像素和实际像素一样
   	lcd_info->var.yres_virtual		   =   480;
   	lcd_info->var.xoffset			   =   0;//实际像素和虚拟像素偏移值为0
   	lcd_info->var.yoffset			   =   0;
   	lcd_info->var.bits_per_pixel 	   =   32;//每个像素点有32个位组成(4个字节)
	/*RGB:888*/
	lcd_info->var.red.offset 		   =   16;//red在16位域中偏移值为11
   	lcd_info->var.red.length 		   =   8;
   	lcd_info->var.red.msb_right		   =   0;
   	lcd_info->var.green.offset		   =   8;//red在16位域中偏移值为11
   	lcd_info->var.green.length		   =   8;
   	lcd_info->var.green.msb_right	   =   0;
   	lcd_info->var.blue.offset		   =   0;//red在16位域中偏移值为11
   	lcd_info->var.blue.length		   =   8;
   	lcd_info->var.blue.msb_right 	   =   0;
	lcd_info->var.activate			   =   FB_ACTIVATE_NOW;

	//2.3 设置其他参数
	//2.3.1 显存大小
	lcd_info->screen_size 			   =   800 * 480 * 4; //和显存一样大小设置
	//2.3.2 调色板
	lcd_info->pseudo_palette 		   =   pseudo_palette;
	//2.3.3 显存操作函数
	lcd_info->fbops 				   =   &tq210_lcd_fbops;
	//2.3.4 设置显存的虚拟起始地址
	lcd_info->screen_base = dma_alloc_writecombine(NULL,  lcd_info->screen_size , (dma_addr_t *)&lcd_info->fix.smem_start, GFP_KERNEL); 

	//3. 硬件相关操作
	//3.1获取lcd时钟,使能时钟
	lcd_clk = clk_get(NULL, "lcd");
	if(!lcd_clk || IS_ERR(lcd_clk)){
		printk(KERN_INFO "failed to get clock source\n");
	}
	clk_enable(lcd_clk);
	
	//3.2 配置GPIO用于LCD
	gpf0con = ioremap(0xE0200120, 4);
    gpf1con = ioremap(0xE0200140, 4);
    gpf2con = ioremap(0xE0200160, 4);
    gpf3con	= ioremap(0xE0200180, 4);
    gpd0con = ioremap(0xE02000A0, 4);
    gpd0dat = ioremap(0xE02000A4, 4);
    
	
    tq210_lcd_regs = ioremap(0xF8000000, sizeof(struct S5PV210_lcd_regs)); 

	vidw00add0b0 = ioremap(0xF80000A0, 4);
	vidw00add1b0 = ioremap(0xF80000D0, 4);
	
	display_control = ioremap(0xe0107008, 4);

	//设置相关GPIO引脚用于LCD
	*gpf0con = 0x22222222;
	*gpf1con = 0x22222222;
	*gpf2con = 0x22222222;
	*gpf3con = 0x22222222;

	//使能LCD本身,LCD_PWM引脚 XpwmTOUT0 GPD0_0 背光
	*gpd0con &= ~0x0F;
	*gpd0con |= 0x01;
	*gpd0dat |= 1<<0; //0打开 1关闭
	
	//显示路径的选择, 0b10: RGB=FIMD I80=FIMD ITU=FIMD
	/*RGB接口和i80接口的区别:
	在嵌入式的主流 LCD屏中主要支持两大类的硬件接口,一种是常见的RGB接口,另外一种是MCU接口.
	MCU接口最早是针对单片机的领域在使用,因此得名.后在中低端手机大量使用,其主要特点是价格便宜的.
	MCU-LCD接口的标准术语是Interface 80,因此在很多文档中用I80 来指MCU-LCD屏。
	MCU-LCD屏它与RGB-LCD屏主要区别在于显存的位置.
	而MCU-LCD的设计之初只要考虑单片机的 内存较小,因此都是把显存内置在LCD模块内部.然后软件通过专
	门显示命令来更新显存,因此MCU屏往往不能做得很大.同时显示更新速度也比RGB-LCD慢.
	
	RGB屏只需显存组织好数据。启动显示后。LCD-DMA会自动把显存通过RGB接口送到LCM。
	而MCU屏则需要发送画点的命令来修改MCU内部RAM。(即不能直接MCU屏RAM)
	所以RGB显示速度明显比MCU快,而且播放视频方面,MCU-LCD也比较慢.
	ITU接口是给摄像头使用的。*/
	*display_control  = 2<<0;

	//3.3映射LCD控制器对应寄存器 
	tq210_lcd_regs->vidcon0 = (4<<6)|(1<<4);
	tq210_lcd_regs->vidcon1 = (1<<6)|(1<<5)|(1<<4);

	tq210_lcd_regs->vidtcon0 = (17<<16)|(26<<8)|(4<<0);
	tq210_lcd_regs->vidtcon1 = (40<<16)|(214<<8)|(4<<0);
	tq210_lcd_regs->vidtcon2 = (479<<11)|(799<<0);

	tq210_lcd_regs->wincon0 &= ~(0xf<<2);
	tq210_lcd_regs->wincon0 |= (0xb<<2);

	tq210_lcd_regs->vidosd0a = (0<<11)|(0<<0);
	tq210_lcd_regs->vidosd0b = (799<<11)|(479<<0);
	tq210_lcd_regs->vidosd0c = 480*800;
	
	*vidw00add0b0 = (volatile unsigned long)lcd_info->fix.smem_start;
	*vidw00add1b0 = (volatile unsigned long)lcd_info->fix.smem_start + lcd_info->fix.smem_len;

	tq210_lcd_regs->shadowcon = 0x01;//使能通道0
	tq210_lcd_regs->vidcon0  |= 0x3;  //开启总控制器   
    tq210_lcd_regs->wincon0 |= 1;     //开启窗口0

	//4. 注册fb_info
	ret = register_framebuffer(lcd_info);
	return ret;
}

static void __exit lcd_exit(void)
{
	unregister_framebuffer(lcd_info);
	dma_free_writecombine(NULL, lcd_info->fix.smem_len, 
		(void*)lcd_info->screen_base, (dma_addr_t)lcd_info->fix.smem_start);

	iounmap(vidw00add1b0);
	iounmap(vidw00add0b0);
	iounmap(tq210_lcd_regs);
	iounmap(gpd0dat);
	iounmap(gpd0con);
	iounmap(gpf3con);
	iounmap(gpf2con);
	iounmap(gpf1con);
	iounmap(gpf0con);
	framebuffer_release(lcd_info);
}

module_init(lcd_init);
module_exit(lcd_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Jerry.Gou");
MODULE_DESCRIPTION("TQ210 lcd driver");


将上面的代码在自己的内核环境下编译,然后下载到开发板上试运行即可。

在安装驱动程序前执行指令:

ls /dev/fb*

如果有fb0或者其他fb*存在,应该修改内和配置,取消其他fb的配置(是编译加载\drivers\video\s3c-fb.c导致)

修改\drivers\video\Makefile

obj-$(CONFIG_FB_S3C)		  += s3c-fb.o
改为
obj-$(CONFIG_FB_S3C)		  += tq210/
#obj-$(CONFIG_FB_S3C)		  += s3c-fb.o

在driver目录下添加tq210目录,存放lcd设备文件

tq210目录下添加Makefile文件和Kconfig文件

Makefile

#
# Makefile for the s3c framebuffer driver
#

ifeq ($(CONFIG_FB_S3C),y)

obj-$(CONFIG_FB_TQ210)        += tq210_fb.o

endif
Kconfig
#
# S3C Video configuration
#
config FB_TQ210
    tristate "TQ210 lcd support"
    depends on FB
    select FB_CFB_FILLRECT
    select FB_CFB_COPYAREA
    select FB_CFB_IMAGEBLIT
    ---help---
    Currently the suport is only for the TQ210

同时\drivers\video\Kconfig中config FB_CIRRUS前面添加

source "drivers/video/tq210/Kconfig"

如果看不到fb*设备,则可以按照如下步骤进行测试。

测试前还需要修改下内核配置,有两个原因:

(1) 内核默认配置下不支持Frame buffer

(2) 我们的驱动程序中用到了三个函数:

	.fb_fillrect	= cfb_fillrect,
	.fb_copyarea	= cfb_copyarea,
	.fb_imageblit	= cfb_imageblit,

这三个函数是引用的内核中的函数,不是我们自行实现的。

鉴于上面两个原因,我们需要配置内核支持Frame buffer和列举出的三个函数,另外,内核中并没有直接配置支持这三个函数的选项,权宜之计,修改下drivers/video目录下的Kconfig文件,在config FB项中添加

select FB_CFB_FILLRECT
select FB_CFB_COPYAREA
select FB_CFB_IMAGEBLIT

添加时一定保证格式正确,参考下该文件下的其他配置项即可。配置完成后执行make menuconfig作如下配置:

Device Drivers  --->
	Graphics support  --->
		<*> Support for frame buffer devices  --->
			<*> TQ210 lcd support 
测试方法:

(1) 在Linux主机上编译下面的C++程序

#include <iostream>

unsigned long buffer[480][800] = {0};

void put_long_hex(unsigned long v){ 
    for(int i = 0; i != 4; ++i){
        std::cout.put(static_cast<char>(0xff&(v>>(8*(3-i)))));
    }   
}

int main(){
    for(int i = 0; i != 480; ++i){
        buffer[i][0]   = 0x00ff0000;
        buffer[i][799] = 0x0000ff00;
    }   

    for(int i = 0; i != 800; ++i){
        buffer[0][i]   = 0xff000000;
        buffer[479][i] = 0x00ffff00;
    }   

    for(int i = 0; i != 480; ++i){
        for(int j = 0; j != 800; ++j){
            put_long_hex(buffer[i][j]); 
        }   
    }   
}

编译指令如下:

g++ -o main main.cpp

然后如下方式执行程序:

./main > /nfsroot/rootfs/test.img

我是直接将文件生成在NFS的根文件系统下了,你也可以用其他方式将生成的文件拷贝到开发板运行环境内,然后执行如下指令:

cat test.img > /dev/fb0

这时,就可以在屏幕上看到一个矩形且矩形的四条边颜色不相同。

如果想将驱动编译进内核,并在启动时可以看到小企鹅,可以将上面的驱动拷贝到内核的drivers/video/目录下,命名为tq210_fb.c,然后在该目录下做如下修改:

退回到内核根目录下,执行make menuconfig并按如下方式配置内核
Device Drivers  --->
	Graphics support  --->
		<*> Support for frame buffer devices  --->
			<*>   TQ210 lcd support
		[*] Bootup logo  --->
			[*]   Standard black and white Linux logo 
			[*]   Standard 16-color Linux logo
			[*]   Standard 224-color Linux logo

然后执行指令make zImage或者make uImage来编译内核,将编译好的内核烧写到开发板或者是放到NFS下即可正常运行。


阅读更多
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/JerryGou/article/details/79952355
文章标签: LCD
个人分类: LCD
上一篇static全局变量与普通全局变量的区别?static局部变量与普通局部变量的区别?static全局函数与普通全局函数的区别?
下一篇Lcd(一)显示原理
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭