LCD驅動的硬件編寫

根據上一篇文章 分析框架列出大致編寫過程

框架分析
在這裏插入圖片描述

2設置

查看 fb_info怎麼定義
在這裏插入圖片描述
進入 fb.h查看定義在這裏插入圖片描述

2.1從中列出固定參數

在這裏插入圖片描述
設置fix結構體裏的參數
在這裏插入圖片描述
a. char id[16]; /* identification string eg “TT Builtin” / 名字
在這裏插入圖片描述
b. unsigned long smem_start; /
Start of frame buffer mem /
/
(physical address) / 顯存設置留到後面
c. __u32 smem_len; /
Length of frame buffer mem /顯存長度
查看lcd的手冊
在這裏插入圖片描述
寫出 顯存長度爲分辨率
rgb /字節 = 24032016/8
在這裏插入圖片描述
d. u32 type; /* see FB_TYPE* / 根據註釋查看這個宏
在這裏插入圖片描述
看不懂,用默認值,一般默認值支持大部分lcd
在這裏插入圖片描述
e. __u32 visual; /
see FB_VISUAL
* */ 可視圖像
查看宏
在這裏插入圖片描述
單色 和真彩色 選擇真彩色
在這裏插入圖片描述
e.
在這裏插入圖片描述
什麼搖動 看不懂設爲0

f.__u32 line_length; /* length of a line in bytes / 一行的固定長度大小(單位bytes)
一行是240個像素
16位的rgb(一個像素是8字節)
就是 240*2
在這裏插入圖片描述

2.2設置fb_info可變信息

從結構體中找到可變信息
在這裏插入圖片描述
進入進行配置
在這裏插入圖片描述
a. __u32 xres; /* visible resolution * x方向的分辨率 240
__u32 yres; y的分辨率320
_u32 xres_virtual; /* virtual resolution / 虛擬分辨率設置爲和硬件一樣就好
__u32 yres_virtual;
在這裏插入圖片描述
b. __u32 bits_per_pixel; /
guess what / 每個像素用多少位
爲什麼註釋是讓我猜猜 註釋這麼有意思的嘛
由於rgb爲16 就是16位
在這裏插入圖片描述
c. 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 */ 透明度不用了

由於rgb裏面是565 所以偏移值(offset)是11位 佔據位是5位
s3c_lcd->var.red.offset = 11;
s3c_lcd->var.red.length = 5;
在這裏插入圖片描述

2.3設置操作函數

在這裏插入圖片描述
很多驅動都有就拿過來用
在這裏插入圖片描述

2.4其他設置

a. screen_size = 24032416/8; //顯存的大小

調色板

在內存中 分配一塊 調色板 從顯存裏面取出數據 放進調色板調色 顏色加載進LCD中
在這裏插入圖片描述
直接引用別人的代碼吧
在這裏插入圖片描述
在這裏插入圖片描述

硬件相關的操作

在這裏插入圖片描述

配置GPIO用於LCD
3.1 配置GPIO用於LCD

查看原理圖
在這裏插入圖片描述
在這裏插入圖片描述
設置所用到的引腳
在這裏插入圖片描述
用 ioremap 映射 arm32位=4 字節 GPxCON佔用4字節,GPxDAT佔用4字節 映射8字節
gpbdat 這裏的+1 是4字節(加了一個指針長度)
在這裏插入圖片描述
設置引腳的功能
在這裏插入圖片描述

3.2 根據LCD手冊設置LCD控制器, 比如VCLK的頻率等

定義一個結構體 放控制lcd的寄存器
在這裏插入圖片描述
定義這個結構體的指針
在這裏插入圖片描述
ioremap(起始地址,大小)這個結構體
在這裏插入圖片描述
a.查看cpu的使用手冊 lcd屏幕的時鐘設置(CLKVAL)
在這裏插入圖片描述
從內核的輸出信息查看 HCLK 的參數
shell 裏面輸入 dmesg
在這裏插入圖片描述
哦 原來是100兆 = HCLK (100MHz)
根據lcd手冊上面寫 時鐘最小週期是100ns 納秒
在這裏插入圖片描述
所以 VCLK = 100ns = 10兆 = 10 MHz
根據LCD手冊上面的時鐘值設置 VCLK = HCLK / [(CLKVAL+1) x 2] 算出 CLKVLA =4
在這裏插入圖片描述
b.用 TFT LCD 屏幕
在這裏插入圖片描述
c.設置像素用16位來表示
在這裏插入圖片描述
d.設置LCD的使能
在這裏插入圖片描述
進行a,b,c ,d 的設置
在這裏插入圖片描述

時間時序設置

從cpu的手冊找出LCD的控制器2(LCDCON2)

在這裏插入圖片描述
有幾個參數需要設置 搜索裏面的參數 如VSPW 一幀的圖片
在這裏插入圖片描述
在這裏插入圖片描述
有四個參數需要設置 VSPW VPBD LINEVAL VFPD 打開LCD 手冊 查看如何設置
找到LCD的時序圖
在這裏插入圖片描述
從時序圖裏面的 時間參數 在LCD手冊裏面找到典型值
在這裏插入圖片描述
在時鐘圖上面標出 脈衝寬度平時是高電平 有效的時候是低電平
在這裏插入圖片描述
另一張圖片的是反的 要區分出來

垂直方向時間參數設置

a. VSYNC設置
VSYNC 一個週期是327+1 而有脈衝週期是1 從兩張圖對比 VSWP+1 = 1 VSWP=0
在這裏插入圖片描述
b. LINEVAL設置
一個垂直信號的週期 取決於有多少行 2440 有320行 = LINEVAL+1 LINEVAL = 319
在這裏插入圖片描述
c.VFPD設置
當到最後一行時候 過多長時間才重新開始到第一行
VFPD+1 = T2 - T5 =2 ** VFPD=1**
在這裏插入圖片描述
d.VSPW 設置
當垂直脈衝消失 過多長時間 才發出第一行的信號
VSPW+1 = T0 - T2 = 4 VSPW=3
在這裏插入圖片描述
整理完後 配置寄存器
在這裏插入圖片描述

水平方向的時間參數設置

先在 cpu手冊中找到 時間參數
在這裏插入圖片描述
找到LCDCON3 控制寄存器3
在這裏插入圖片描述
LCDCON4
在這裏插入圖片描述
啊 又有這麼多參數要設置

再從LCD上面找到 時間參數
在這裏插入圖片描述
通過LCD的手冊 把時間的典型值標註
在這裏插入圖片描述
a.HSPW設置
T7是脈衝寬度 = HSPW+1 HSPW = 4
在這裏插入圖片描述
b.HOZVAL設置
有240列 240 = HOZAL+1 HOZAL = 239
在這裏插入圖片描述
c.HFPD設置
當發出最後一行的信號過多長時間纔有新的信號
HFPD +1 = T8-T11 = 11 HFPD = 10
在這裏插入圖片描述
d.HBPD 設置
當發出最後一行最後一個像素過多久發出
HBPD +1 = T6 - T8 - T11 = 17 HBPD = 16
在這裏插入圖片描述
由此設置寄存器的參數
在這裏插入圖片描述
在這裏插入圖片描述

LCD的引腳設置 控制極性

在這裏插入圖片描述

LCD在下降沿 取數據
在這裏插入圖片描述
這裏設置爲0 INVVCLK =0

在這裏插入圖片描述
INVVLINE設置
由於之前的 cpu和LCD圖片的有效值是相反的
在這裏插入圖片描述
在這裏插入圖片描述
於此同時 幀同步信號 也要反轉 所以INVVFRAME = 1
在這裏插入圖片描述
設置數據使能信號INVVDEN
在這裏插入圖片描述
兩個信號是相同的不用反轉
在這裏插入圖片描述
INVVDEN = 0

INVPWREN電源使能設置

判斷出是高電平的有效
在這裏插入圖片描述
在這裏插入圖片描述
INVPWREN = 0

選擇顯存的方式
在這裏插入圖片描述

在這裏插入圖片描述

LCD分配顯存

參考2440自帶的代碼
在這裏插入圖片描述
在這裏插入圖片描述
用dma_free_writecombine(設備,大小,物理地址,標記flag) 分配內存 之前的虛擬地址沒有設備 由於返回值是虛擬地址現在設置

s3c_lcd->screen_base = dma_alloc_writecombine(NULL, s3c_lcd->fix.smem_len, &s3c_lcd->fix.smem_start, GFP_KERNEL);
在這裏插入圖片描述

設置完虛擬地址,把虛擬地址告訴寄存器

LCDSADDR1
在這裏插入圖片描述
寄存器的29 ~ 0 位 放 顯存物理地址(s3c_lcd->fix.smem_start)的30 ~ 1 位
顯存地址右移一位 再與上 取反的(3左移30位 )
在這裏插入圖片描述
LCDSADDR2
在這裏插入圖片描述
對於單列掃描 寄存器20 ~ 0 位的值等於結束地址的 21 ~ 1 位
結束地址 = 開始地址+顯存大小
lcd_regs->lcdsaddr2 = ((s3c_lcd->fix.smem_start + s3c_lcd->fix.smem_len) >> 1) & 0x1fffff;
在這裏插入圖片描述
LCDSADDR3
在這裏插入圖片描述
PAGEWIDTH設置
一行佔據的大小(單位半字節)
一行240 像素16位 半字節16位 所以是 240*16/16

最後啓動 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/delay.h>
#include <linux/fb.h>
#include <linux/init.h>
#include <linux/dma-mapping.h>
#include <linux/interrupt.h>
#include <linux/workqueue.h>
#include <linux/wait.h>
#include <linux/platform_device.h>
#include <linux/clk.h>

#include <asm/io.h>
#include <asm/uaccess.h>
#include <asm/div64.h>

#include <asm/mach/map.h>
#include <asm/arch/regs-lcd.h>
#include <asm/arch/regs-gpio.h>
#include <asm/arch/fb.h>

static int s3c_lcdfb_setcolreg(unsigned int regno, unsigned int red,
			     unsigned int green, unsigned int blue,
			     unsigned int transp, struct fb_info *info);


struct lcd_regs {
	unsigned long	lcdcon1;
	unsigned long	lcdcon2;
	unsigned long	lcdcon3;
	unsigned long	lcdcon4;
	unsigned long	lcdcon5;
    unsigned long	lcdsaddr1;
    unsigned long	lcdsaddr2;
    unsigned long	lcdsaddr3;
    unsigned long	redlut;
    unsigned long	greenlut;
    unsigned long	bluelut;
    unsigned long	reserved[9];
    unsigned long	dithmode;
    unsigned long	tpal;
    unsigned long	lcdintpnd;
    unsigned long	lcdsrcpnd;
    unsigned long	lcdintmsk;
    unsigned long	lpcsel;
};

static struct fb_ops s3c_lcdfb_ops = {
	.owner		= THIS_MODULE,
	.fb_setcolreg	= s3c_lcdfb_setcolreg,
	.fb_fillrect	= cfb_fillrect,
	.fb_copyarea	= cfb_copyarea,
	.fb_imageblit	= cfb_imageblit,
};


static struct fb_info *s3c_lcd;
static volatile unsigned long *gpbcon;
static volatile unsigned long *gpbdat;
static volatile unsigned long *gpccon;
static volatile unsigned long *gpdcon;
static volatile unsigned long *gpgcon;
static volatile struct lcd_regs* lcd_regs;
static u32 pseudo_palette[16];


/* from pxafb.c */
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 s3c_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 int lcd_init(void)
{
	/* 1. 分配一個fb_info */
	s3c_lcd = framebuffer_alloc(0, NULL);

	/* 2. 設置 */
	/* 2.1 設置固定的參數 */
	strcpy(s3c_lcd->fix.id, "mylcd");
	s3c_lcd->fix.smem_len = 240*320*16/8;
	s3c_lcd->fix.type     = FB_TYPE_PACKED_PIXELS;
	s3c_lcd->fix.visual   = FB_VISUAL_TRUECOLOR; /* TFT */
	s3c_lcd->fix.line_length = 240*2;
	
	/* 2.2 設置可變的參數 */
	s3c_lcd->var.xres           = 240;
	s3c_lcd->var.yres           = 320;
	s3c_lcd->var.xres_virtual   = 240;
	s3c_lcd->var.yres_virtual   = 320;
	s3c_lcd->var.bits_per_pixel = 16;

	/* RGB:565 */
	s3c_lcd->var.red.offset     = 11;
	s3c_lcd->var.red.length     = 5;
	
	s3c_lcd->var.green.offset   = 5;
	s3c_lcd->var.green.length   = 6;

	s3c_lcd->var.blue.offset    = 0;
	s3c_lcd->var.blue.length    = 5;

	s3c_lcd->var.activate       = FB_ACTIVATE_NOW;
	
	
	/* 2.3 設置操作函數 */
	s3c_lcd->fbops              = &s3c_lcdfb_ops;
	
	/* 2.4 其他的設置 */
	s3c_lcd->pseudo_palette = pseudo_palette;
	//s3c_lcd->screen_base  = ;  /* 顯存的虛擬地址 */ 
	s3c_lcd->screen_size   = 240*324*16/8;

	/* 3. 硬件相關的操作 */
	/* 3.1 配置GPIO用於LCD */
	gpbcon = ioremap(0x56000010, 8);
	gpbdat = gpbcon+1;
	gpccon = ioremap(0x56000020, 4);
	gpdcon = ioremap(0x56000030, 4);
	gpgcon = ioremap(0x56000060, 4);

    *gpccon  = 0xaaaaaaaa;   /* GPIO管腳用於VD[7:0],LCDVF[2:0],VM,VFRAME,VLINE,VCLK,LEND */
	*gpdcon  = 0xaaaaaaaa;   /* GPIO管腳用於VD[23:8] */
	
	*gpbcon &= ~(3);  /* GPB0設置爲輸出引腳 */
	*gpbcon |= 1;
	*gpbdat &= ~1;     /* 輸出低電平 */

	*gpgcon |= (3<<8); /* GPG4用作LCD_PWREN */
	
	/* 3.2 根據LCD手冊設置LCD控制器, 比如VCLK的頻率等 */
	lcd_regs = ioremap(0x4D000000, sizeof(struct lcd_regs));

	/* bit[17:8]: VCLK = HCLK / [(CLKVAL+1) x 2], LCD手冊P14
	 *            10MHz(100ns) = 100MHz / [(CLKVAL+1) x 2]
	 *            CLKVAL = 4
	 * bit[6:5]: 0b11, TFT LCD
	 * bit[4:1]: 0b1100, 16 bpp for TFT
	 * bit[0]  : 0 = Disable the video output and the LCD control signal.
	 */
	lcd_regs->lcdcon1  = (4<<8) | (3<<5) | (0x0c<<1);

#if 1
	/* 垂直方向的時間參數
	 * bit[31:24]: VBPD, VSYNC之後再過多長時間才能發出第1行數據
	 *             LCD手冊 T0-T2-T1=4
	 *             VBPD=3
	 * bit[23:14]: 多少行, 320, 所以LINEVAL=320-1=319
	 * bit[13:6] : VFPD, 發出最後一行數據之後,再過多長時間才發出VSYNC
	 *             LCD手冊T2-T5=322-320=2, 所以VFPD=2-1=1
	 * bit[5:0]  : VSPW, VSYNC信號的脈衝寬度, LCD手冊T1=1, 所以VSPW=1-1=0
	 */
	lcd_regs->lcdcon2  = (3<<24) | (319<<14) | (1<<6) | (0<<0);


	/* 水平方向的時間參數
	 * bit[25:19]: HBPD, VSYNC之後再過多長時間才能發出第1行數據
	 *             LCD手冊 T6-T7-T8=17
	 *             HBPD=16
	 * bit[18:8]: 多少列, 240, 所以HOZVAL=240-1=239
	 * bit[7:0] : HFPD, 發出最後一行裏最後一個象素數據之後,再過多長時間才發出HSYNC
	 *             LCD手冊T8-T11=251-240=11, 所以HFPD=11-1=10
	 */
	lcd_regs->lcdcon3 = (16<<19) | (239<<8) | (10<<0);

	/* 水平方向的同步信號
	 * bit[7:0]	: HSPW, HSYNC信號的脈衝寬度, LCD手冊T7=5, 所以HSPW=5-1=4
	 */	
	lcd_regs->lcdcon4 = 4;

#else
lcd_regs->lcdcon2 =	S3C2410_LCDCON2_VBPD(5) | \
		S3C2410_LCDCON2_LINEVAL(319) | \
		S3C2410_LCDCON2_VFPD(3) | \
		S3C2410_LCDCON2_VSPW(1);

lcd_regs->lcdcon3 =	S3C2410_LCDCON3_HBPD(10) | \
		S3C2410_LCDCON3_HOZVAL(239) | \
		S3C2410_LCDCON3_HFPD(1);

lcd_regs->lcdcon4 =	S3C2410_LCDCON4_MVAL(13) | \
		S3C2410_LCDCON4_HSPW(0);

#endif
	/* 信號的極性 
	 * bit[11]: 1=565 format
	 * bit[10]: 0 = The video data is fetched at VCLK falling edge
	 * bit[9] : 1 = HSYNC信號要反轉,即低電平有效 
	 * bit[8] : 1 = VSYNC信號要反轉,即低電平有效 
	 * bit[6] : 0 = VDEN不用反轉
	 * bit[3] : 0 = PWREN輸出0
	 * bit[1] : 0 = BSWP
	 * bit[0] : 1 = HWSWP 2440手冊P413
	 */
	lcd_regs->lcdcon5 = (1<<11) | (0<<10) | (1<<9) | (1<<8) | (1<<0);
	
	/* 3.3 分配顯存(framebuffer), 並把地址告訴LCD控制器 */
	s3c_lcd->screen_base = dma_alloc_writecombine(NULL, s3c_lcd->fix.smem_len, &s3c_lcd->fix.smem_start, GFP_KERNEL);
	
	lcd_regs->lcdsaddr1  = (s3c_lcd->fix.smem_start >> 1) & ~(3<<30);
	lcd_regs->lcdsaddr2  = ((s3c_lcd->fix.smem_start + s3c_lcd->fix.smem_len) >> 1) & 0x1fffff;
	lcd_regs->lcdsaddr3  = (240*16/16);  /* 一行的長度(單位: 2字節) */	
	
	//s3c_lcd->fix.smem_start = xxx;  /* 顯存的物理地址 */
	/* 啓動LCD */
	lcd_regs->lcdcon1 |= (1<<0); /* 使能LCD控制器 */
	lcd_regs->lcdcon5 |= (1<<3); /* 使能LCD本身 */
	*gpbdat |= 1;     /* 輸出高電平, 使能背光 */		

	/* 4. 註冊 */
	register_framebuffer(s3c_lcd);
	
	return 0;
}

static void lcd_exit(void)
{
	unregister_framebuffer(s3c_lcd);
	lcd_regs->lcdcon1 &= ~(1<<0); /* 關閉LCD本身 */
	*gpbdat &= ~1;     /* 關閉背光 */
	dma_free_writecombine(NULL, s3c_lcd->fix.smem_len, s3c_lcd->screen_base, s3c_lcd->fix.smem_start);
	iounmap(lcd_regs);
	iounmap(gpbcon);
	iounmap(gpccon);
	iounmap(gpdcon);
	iounmap(gpgcon);
	framebuffer_release(s3c_lcd);
}

module_init(lcd_init);
module_exit(lcd_exit);

MODULE_LICENSE("GPL");



makefile 源碼

KERN_DIR = /work/system/linux-2.6.22.6

all:
	make -C $(KERN_DIR) M=`pwd` modules 

clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order

obj-m	+= lcd.o

進行編譯

1mak emenuconfig 進入內核去掉原來的驅動程序

設備驅動
在這裏插入圖片描述
圖形支持
在這裏插入圖片描述
把原來的LCD驅動設置爲M 因爲我們自己的驅動程序裏面 有三個函數 用到原來的驅動文件
在這裏插入圖片描述

編譯模塊 把上面的三個函數對應的文件編譯爲.ko 加載進去

make uImage

在這裏插入圖片描述
這時候 uImage 已經去掉了原來的驅動 可以拷貝到 windos裏面
在這裏插入圖片描述
進行燒寫 這時候 發現 控制檯能正確輸入輸出 但是 LCD沒有亮
由於要得到 三個.ko文件 make modules

編譯自己的 lcd.ko

用nfs 掛載開發版
mount -t nfs -o intr,nolock,rsize=1024,wsize=1024 192.168.31.51:/work/nfs_root /mnt
裝載自己的 lcd.ko文件
在這裏插入圖片描述
這時候 發現 之前的三個驅動文件 沒有拿過來
進入 內核的driver裏面
在這裏插入圖片描述
拷貝出來
在這裏插入圖片描述
在開發版上面裝載這三個驅動
在這裏插入圖片描述
再裝載 LCD的驅動
在這裏插入圖片描述
這時候 開發板也亮了

測試

echo hello > /dev/tty1
輸出文字
在這裏插入圖片描述
隨便把文件放進顯存中 所以會花屏
看見lcd花屏

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章