68 linux framebuffer裝置驅動之spi lcd屏驅動
阿新 • • 發佈:2019-01-02
前面驅動的spi lcd僅僅是刷了一下圖而已, 如果要讓QT圖形程式在此lcd上顯示的話,還需要實現標準的framebuffer裝置驅動才可以.
實現一個fb裝置驅動好, QT程式就可以在視訊記憶體裡顯示出來。 只需要在裝置驅動把視訊記憶體的資料通過spi控制器傳送到屏的驅動ic,就可以讓QT程式在spi lcd屏上顯示出來. 但視訊記憶體的資料有可能經常發生變化(介面切換), spi lcd屏也應跟著顯示出改變過的畫面。
在裝置驅動裡用一個核心執行緒, 迴圈把視訊記憶體的資料通過spi控制傳送到屏的驅動ic. 這樣應用程式只需改變視訊記憶體裡的資料就可以了,無需考慮螢幕的更新.
同時, QT程式不支援16位色的fb裝置裡顯示,所以只能在裝置驅動把32位色的視訊記憶體轉換成rgb565後,再把資料傳送到屏的驅動ic.
/////////////////////////////////////////////////////////////////
裝置驅動由兩個檔案組成:
fb_model.c主要實現fb裝置驅動模型.
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fb.h>
#include <linux/spi/spi.h>
#include <linux/dma-mapping.h>
#include <linux/sched.h>
#include <linux/wait.h>
#include <linux/delay.h>
#define X 240
#define Y 320
typedef struct {
struct spi_device * spi; //記錄fb_info物件對應的spi裝置物件
struct task_struct *thread; //記錄執行緒物件的地址,此執行緒專用於把視訊記憶體資料傳送到屏的驅動ic
}lcd_data_t;
struct fb_ops fops = {
};
extern void show_fb(struct fb_info *fbi, struct spi_device *spi);
int thread_func(void *data)
{
struct fb_info *fbi = (struct fb_info *)data;
lcd_data_t * ldata = fbi->par;
while (1)
{
if (kthread_should_stop())
break;
show_fb(fbi, ldata->spi);
//
}
return 0;
}
int myfb_new(struct spi_device *spi) //此函式在spi裝置驅動的probe函式裡被呼叫
{
struct fb_info *fbi;
u8 *v_addr;
u32 p_addr;
lcd_data_t *data;
v_addr = dma_alloc_coherent(NULL, X*Y*4, &p_addr, GFP_KERNEL);
//額外分配lcd_data_t型別空間
fbi = framebuffer_alloc(sizeof(lcd_data_t), NULL);
//data = &fbi[1]; //data指標指向額外分配的空間
data = fbi->par; //data指標指向額外分配的空間
data->spi = spi;
fbi->var.xres = X;
fbi->var.yres = Y;
fbi->var.xres_virtual = X;
fbi->var.yres_virtual = Y;
fbi->var.bits_per_pixel = 32; // 屏是rgb565, 但QT程式只能支援32位.還需要在刷圖時把32位的畫素資料轉換成rgb565
fbi->var.red.offset = 16;
fbi->var.red.length = 8;
fbi->var.green.offset = 8;
fbi->var.green.length = 8;
fbi->var.blue.offset = 0;
fbi->var.blue.length = 8;
strcpy(fbi->fix.id, "myfb");
fbi->fix.smem_start = p_addr; //視訊記憶體的實體地址
fbi->fix.smem_len = X*Y*4;
fbi->fix.type = FB_TYPE_PACKED_PIXELS;
fbi->fix.visual = FB_VISUAL_TRUECOLOR;
fbi->fix.line_length = X*4;
fbi->fbops = &fops;
fbi->screen_base = v_addr; //視訊記憶體虛擬地址
fbi->screen_size = X*Y*4; //視訊記憶體大小
spi_set_drvdata(spi, fbi);
register_framebuffer(fbi);
data->thread = kthread_run(thread_func, fbi, spi->modalias);
return 0;
}
void myfb_del(struct spi_device *spi) //此函式在spi裝置驅動remove時被呼叫
{
struct fb_info *fbi = spi_get_drvdata(spi);
lcd_data_t *data = fbi->par;
kthread_stop(data->thread); //讓刷圖執行緒退出
unregister_framebuffer(fbi);
dma_free_coherent(NULL, fbi->screen_size, fbi->screen_base, fbi->fix.smem_start);
framebuffer_release(fbi);
}
///////////////////
test.c 主要實現spi lcd的操作
#include <linux/init.h>
#include <linux/module.h>
#include <linux/spi/spi.h>
#include <linux/delay.h>
#include <linux/gpio.h>
#include <linux/fb.h>
struct myspi_lcd_pdata {
int dc_io;
int reset_io;
};
struct spi_lcd_cmd{
u8 reg_addr; // command
u8 len; //需要從spi_lcd_datas數組裡發出資料位元組數
int delay_ms; //此命令傳送資料完成後,需延時多久
}cmds[] = {
{0xCB, 5, 0},
{0xCF, 3, 0},
{0xEB, 3, 0},
{0xEA, 2, 0},
{0xED, 4, 0},
{0xF7, 1, 0},
{0xC0, 1, 0},
{0xC1, 1, 0},
{0xC5, 2, 0},
{0xC7, 1, 0},
{0x36, 1, 0},
{0x3A, 1, 0},
{0xB1, 2, 0},
{0xB6, 3, 0},
{0xF2, 1, 0},
{0x26, 1, 0},
{0xE0, 15, 0},
{0xE1, 15, 0},
{0x11, 0, 120},
{0x29, 0, 0},
{0x2c, 0, 0},
};
u8 spi_lcd_datas[] = {
0x39, 0x2c, 0x00, 0x34, 0x20, // command: 0xCB要發出的資料
0x00, 0xC1, 0x30, // command: 0xCF
0x85, 0x00, 0x78, // command: 0xEB
0x00, 0x00, // command: 0xEA
0x64, 0x03, 0x12, 0x81, // command: 0xED
0x20, // command: 0xF7
0x23, // command: 0xC0
0x10, // command: 0xC1
0x3e, 0x28, // command: 0xC5
0x86, // command: 0xC7
0x48, // command: 0x36
0x55, // command: 0x3A
0x00, 0x18, // command: 0xB1
0x08, 0x82, 0x27, // command: 0xB6
0x00, // command: 0xF2
0x01, // command: 0x26
0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08, 0x4E, 0xF1, 0x37, 0x07, 0x10, 0x03, 0x0E, 0x09, 0x00, //command: 0xE0
0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, 0x31, 0xC1, 0x48, 0x08, 0x0F, 0x0C, 0x31, 0x36, 0x0F, //command: 0xE1
};
extern int myfb_new(struct spi_device *);
extern void myfb_del(struct spi_device *);
void write_command(struct spi_device *spi, u8 cmd)
{
struct myspi_lcd_pdata *pdata = spi->dev.platform_data;
// dc , command:0
gpio_direction_output(pdata->dc_io, 0);
spi_write(spi, &cmd, 1);
}
void write_data(struct spi_device *spi, u8 data)
{
struct myspi_lcd_pdata *pdata = spi->dev.platform_data;
// dc , data:1
gpio_direction_output(pdata->dc_io, 1);
spi_write(spi, &data, 1);
}
//初始化spi_lcd
void spi_lcd_init(struct spi_device *spi)
{
struct myspi_lcd_pdata *pdata = spi->dev.platform_data;
int i, j, n;
// 屏復位
gpio_direction_output(pdata->reset_io, 0);
mdelay(100);
gpio_set_value(pdata->reset_io, 1);
mdelay(100);
n = 0; // n用於記錄資料陣列spi_lcd_datas的位置
//發命令,併發出命令所需的資料
for (i = 0; i < ARRAY_SIZE(cmds); i++) //命令
{
write_command(spi, cmds[i].reg_addr);
for (j = 0; j < cmds[i].len; j++) //發出命令後,需要發出的資料
write_data(spi, spi_lcd_datas[n++]);
if (cmds[i].delay_ms) //如有延時則延時
mdelay(cmds[i].delay_ms);
}
}
//設定要刷屏的開始座標
void addset(struct spi_device *spi, unsigned int x,unsigned int y)
{
write_command(spi, 0x2a); //發出x座標
write_data(spi, x>>8);
write_data(spi, x&0xff);
write_command(spi, 0x2b); //發出y座標
write_data(spi, y>>8);
write_data(spi, y&0xff);
write_command(spi, 0x2c);
}
void show_fb(struct fb_info *fbi, struct spi_device *spi)
{
int x, y;
u32 k;
u32 *p = (u32 *)(fbi->screen_base);
u16 c;
u8 *pp;
addset(spi, 0, 0); //從屏的0,0座標開始刷
// gpio_direction_output(pdata->dc_io, 1);
for (y = 0; y < fbi->var.yres; y++)
{
for (x = 0; x < fbi->var.xres; x++)
{
k = p[y*fbi->var.xres+x];//取出一個畫素點的32位資料
// rgb8888 --> rgb565
pp = (u8 *)&k;
c = pp[0] >> 3; //藍色
c |= (pp[1]>>2)<<5; //綠色
c |= (pp[2]>>3)<<11; //紅色
//發出畫素資料的rgb565
write_data(spi, c >> 8);
write_data(spi, c & 0xff);
}
}
}
int myprobe(struct spi_device *spi)
{
struct myspi_lcd_pdata *pdata = spi->dev.platform_data;
int ret;
int x, y;
u16 color0 = 0x001f; // RGB565, blue
u16 color1 = 0xf800; // red
u16 color2 = 0x07e0; // green
u16 color3 = 0xffff; // white
u16 color;
ret = gpio_request(pdata->reset_io, spi->modalias);
if (ret < 0)
goto err0;
ret = gpio_request(pdata->dc_io, spi->modalias);
if (ret < 0)
goto err1;
spi_lcd_init(spi); //初始化屏
printk("probe ...%s\n", spi->modalias);
return myfb_new(spi); //fb裝置初始化
err1:
gpio_free(pdata->reset_io);
err0:
return ret;
}
int myremove(struct spi_device *spi)
{
struct myspi_lcd_pdata *pdata = spi->dev.platform_data;
myfb_del(spi); //fb裝置回收
gpio_free(pdata->dc_io);
gpio_free(pdata->reset_io);
printk("%s remove\n", spi->modalias);
return 0;
}
struct spi_device_id ids[] = {
{"myspi_lcd"},
{},
};
struct spi_driver myspi_drv = {
.driver = {
.owner = THIS_MODULE,
.name = "myspi_drv",
},
.probe = myprobe,
.remove = myremove,
.id_table = ids,
};
module_spi_driver(myspi_drv);
MODULE_LICENSE("GPL");
///////
Makefile:
obj-m += test_fb.o
test_fb-objs := test.o fb_model.o
KSRC := /disk3/myown/h3/orangepi_sdk/source/linux-3.4.112/
export ARCH := arm
export CROSS_COMPILE := arm-linux-gnueabihf-
all:
make -C $(KSRC) modules M=`pwd`
.PHONY : clean
clean:
make -C $(KSRC) modules clean M=`pwd`
//////////////////////////////////////////
編譯模組後,載入模組。/dev/目錄下多生成一個fb8裝置檔案.
設定QT環境變數: export QT_QPA_PLATFORM=linuxfb:fb=/dev/fb8
然後執行交叉編譯過的QT程式, QT程式就會在屏上顯示出來(重新整理比較慢), 效果如圖:
QT程式在pc上的效果: