1. 程式人生 > >68 linux framebuffer裝置驅動之spi lcd屏驅動

68 linux framebuffer裝置驅動之spi lcd屏驅動

前面驅動的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上的效果:
這裡寫圖片描述