35.【linux驱动】spi framebuffer驱动

1.framebuffer驱动
2.spi framebuffer驱动
3.spi framebuffer驱动(切换并显示虚拟终端)

spi framebuffer驱动

上一节实现了一个framebuffer驱动,但是没有具体操作硬件,这节做一个SPI屏驱动,整个代码作为一个spi驱动去写。spi相关内容可以看一文读懂linux设备驱动模型(常见总线bus)
首先是spi驱动,屏幕的操作由厂家C51代码改编而来。屏幕控制器是ILI9488,分辨率320 * 480

spi部分

spi.h

struct  Lcd_dev
{										    
	unsigned short width;			//LCD 宽度
	unsigned short height;			//LCD 高度
	unsigned short id;				//LCD ID
	unsigned char  dir;			//横屏还是竖屏控制:0,竖屏;1,横屏。	
	unsigned short	 wramcmd;		//开始写gram指令
	unsigned short  setxcmd;		//设置x坐标指令
	unsigned short  setycmd;		//设置y坐标指令	 
};

//画笔颜色
#define WHITE            0xFFFF
#define BLACK            0x0000   
#define BLUE           0x001F  
#define BRED             0XF81F
#define GRED       0XFFE0
#define GBLUE      0X07FF
#define RED              0xF800
#define MAGENTA          0xF81F
#define GREEN            0x07E0
#define CYAN             0x7FFF
#define YELLOW           0xFFE0
#define BROWN        0XBC40 //棕色
#define BRRED        0XFC07 //棕红色
#define GRAY         0X8430 //灰色
//GUI颜色

#define DARKBLUE         0X01CF //深蓝色
#define LIGHTBLUE        0X7D7C //浅蓝色  
#define GRAYBLUE         0X5458 //灰蓝色
//以上三色为PANEL的颜色 
 
#define LIGHTGREEN       0X841F //浅绿色
#define LGRAY        0XC618 //浅灰色(PANNEL),窗体背景色

#define LGRAYBLUE        0XA651 //浅灰蓝色(中间层颜色)
#define LBBLUE           0X2B12 //浅棕蓝色(选择条目的反色)

//LCD的画笔颜色和背景色	   
#define POINT_COLOR (0x0000)	//画笔颜色
#define BACK_COLOR (0xFFFF)		//背景色
//定义LCD的尺寸
#define LCD_W 320
#define LCD_H 480

#define USE_HORIZONTAL  	0	//定义液晶屏顺时针旋转方向 	0-0度旋转,1-90度旋转,2-180度旋转,3-270度旋转

spi.c


#include <linux/init.h>
#include <linux/module.h>
#include <linux/spi/spi.h>
#include <linux/vmalloc.h>
#include <linux/delay.h>
#include <linux/gpio.h>
#include <linux/fb.h>
#include "fb_dev.h"
#include "fb_spi.h"

struct spi_lcd_cmd {
    u8  d_or_r;
    u8  data; // command
    int delay_ms; //此命令发送数据完成后,需延时多久
} cmds[] = {
    {0, 0XF7, 0},
    {1, 0xA9, 0},
    {1, 0x51, 0},
    {1, 0x2C, 0},
    {1, 0x82, 0},
    {0, 0xC0, 0},
    {1, 0x11, 0},
    {1, 0x09, 0},
    {0, 0xC1, 0},
    {1, 0x41, 0},
    {0, 0XC5, 0},
    {1, 0x00, 0},
    {1, 0x0A, 0},
    {1, 0x80, 0},
    {0, 0xB1, 0},
    {1, 0xB0, 0},
    {1, 0x11, 0},
    {0, 0xB4, 0},
    {1, 0x02, 0},
    {0, 0xB6, 0},
    {1, 0x02, 0},
    {1, 0x42, 0},
    {0, 0xB7, 0},
    {1, 0xc6, 0},
    {0, 0xBE, 0},
    {1, 0x00, 0},
    {1, 0x04, 0},
    {0, 0xE9, 0},
    {1, 0x00, 0},
    {0, 0x36, 0},
    {1, (1 << 3) | (0 << 7) | (1 << 6) | (1 << 5), 0},
    {0, 0x3A, 0},
    {1, 0x66, 0},
    {0, 0xE0, 0},
    {1, 0x00, 0},
    {1, 0x07, 0},
    {1, 0x10, 0},
    {1, 0x09, 0},
    {1, 0x17, 0},
    {1, 0x0B, 0},
    {1, 0x41, 0},
    {1, 0x89, 0},
    {1, 0x4B, 0},
    {1, 0x0A, 0},
    {1, 0x0C, 0},
    {1, 0x0E, 0},
    {1, 0x18, 0},
    {1, 0x1B, 0},
    {1, 0x0F, 0},
    {0, 0XE1, 0},
    {1, 0x00, 0},
    {1, 0x17, 0},
    {1, 0x1A, 0},
    {1, 0x04, 0},
    {1, 0x0E, 0},
    {1, 0x06, 0},
    {1, 0x2F, 0},
    {1, 0x45, 0},
    {1, 0x43, 0},
    {1, 0x02, 0},
    {1, 0x0A, 0},
    {1, 0x09, 0},
    {1, 0x32, 0},
    {1, 0x36, 0},
    {1, 0x0F, 0},
    {0, 0x11, 0},
    {0, 0x29, 0},
};

struct  Lcd_dev lcddev;

static inline void write_u8(struct spi_device * spi, u8 d_or_r, u8 cmd)
{
    struct fb_data *pdata = spi->dev.platform_data;
    gpio_set_value(pdata->rs, d_or_r);
    spi_write(spi, &cmd, 1);
}

static void write_u24s(struct spi_device * spi, u8 * data, unsigned int len)
{
    struct fb_data *pdata = spi->dev.platform_data;
    gpio_set_value(pdata->rs, 1);
    spi_write(spi, data, len);
}

void lcd_set_window(struct spi_device * spi, unsigned short xStar, unsigned short yStar, unsigned short xEnd, unsigned short yEnd)
{
    write_u8(spi, 0, lcddev.setxcmd);
    write_u8(spi, 1, xStar >> 8);
    write_u8(spi, 1, 0x00FF & xStar);
    write_u8(spi, 1, xEnd >> 8);
    write_u8(spi, 1, 0x00FF & xEnd);

    write_u8(spi, 0, lcddev.setycmd);
    write_u8(spi, 1, yStar >> 8);
    write_u8(spi, 1, 0x00FF & yStar);
    write_u8(spi, 1, yEnd >> 8);
    write_u8(spi, 1, 0x00FF & yEnd);

    write_u8(spi, 0, lcddev.wramcmd);
}

void lcd_clear(struct spi_device * spi, struct fb_info * fbi, unsigned short Color)
{
    unsigned int i;
    unsigned char *p;
    lcd_set_window(spi, 0, 0, lcddev.width - 1, lcddev.height - 1);
    p = fbi->screen_base;
    for (i = 0; i < fbi->var.xres * fbi->var.yres; i++)
    {
        p[i * 3] = (Color >> 8) & 0xF8;
        p[i * 3 + 1] = (Color >> 3) & 0xFC;
        p[i * 3 + 2] = (Color << 3);
    }
}

void lcd_direction(struct spi_device * spi, unsigned char direction)
{
    lcddev.setxcmd = 0x2A;
    lcddev.setycmd = 0x2B;
    lcddev.wramcmd = 0x2C;
    switch (direction) {
    case 0:
        lcddev.width = LCD_W;
        lcddev.height = LCD_H;
        write_u8(spi, 0, 0x36);
        write_u8(spi, 1, (1 << 3) | (0 << 6) | (0 << 7)); //BGR==1,MY==0,MX==0,MV==0
        break;
    case 1:
        lcddev.width = LCD_H;
        lcddev.height = LCD_W;
        write_u8(spi, 0, 0x36);
        write_u8(spi, 1, (1 << 3) | (0 << 7) | (1 << 6) | (1 << 5)); //BGR==1,MY==1,MX==0,MV==1
        break;
    case 2:
        lcddev.width = LCD_W;
        lcddev.height = LCD_H;
        write_u8(spi, 0, 0x36);
        write_u8(spi, 1, (1 << 3) | (1 << 6) | (1 << 7)); //BGR==1,MY==0,MX==0,MV==0
        break;
    case 3:
        lcddev.width = LCD_H;
        lcddev.height = LCD_W;
        write_u8(spi, 0, 0x36);
        write_u8(spi, 1, (1 << 3) | (1 << 7) | (1 << 5)); //BGR==1,MY==1,MX==0,MV==1
        break;
    default: break;
    }
}

//初始化spi_lcd
static void spi_lcd_init(struct spi_device * spi)
{
    struct fb_data *pdata = spi->dev.platform_data;
    int i, n;

    // 屏复位
    gpio_set_value(pdata->reset, 0);
    mdelay(100);
    gpio_set_value(pdata->reset, 1);
    mdelay(100);

    n = 0; // n用于记录数据数组spi_lcd_datas的位置
    //发命令,并发出命令所需的数据
    for (i = 0; i < ARRAY_SIZE(cmds); i++) //命令
    {
        write_u8(spi, cmds[i].d_or_r, cmds[i].data);
        if (cmds[i].delay_ms)
            mdelay(cmds[i].delay_ms);
    }
}

void show_fb(struct fb_info * fbi, struct spi_device * spi)
{
    u8 *p = (u8 *)(fbi->screen_base);

    lcd_set_window(spi, 0, 0, lcddev.width - 1, lcddev.height - 1); //从屏的0,0坐标开始刷
    write_u24s(spi, p, fbi->screen_size/2);
    write_u24s(spi, p + fbi->screen_size/2, fbi->screen_size/2);
}

extern struct fb_info * fb_init(struct spi_device *);
static int spi_probe(struct spi_device * spi)
{
    struct fb_data *pdata = spi->dev.platform_data;
    int ret;
    struct fb_info * fbi;
    fbi = NULL;

    ret = gpio_request(pdata->reset, spi->modalias);
    if (ret < 0)
        goto err0;
    ret = gpio_request(pdata->rs, spi->modalias);
    if (ret < 0)
        goto err1;

    gpio_direction_output(pdata->rs, 0);
    gpio_direction_output(pdata->reset, 1);
    spi_lcd_init(spi); //初始化
    lcd_direction(spi, USE_HORIZONTAL); //设置LCD显示方向
    fbi = fb_init(spi); //fb设备初始化
    lcd_clear(spi, fbi, BLUE);

    printk("probe ...%s\n", spi->modalias);
    return 0;

err1:
    gpio_free(pdata->reset);
err0:
    return ret;
}

extern void fb_del(struct spi_device *);
static int spi_remove(struct spi_device * spi)
{
    struct fb_data *pdata = spi->dev.platform_data;

    fb_del(spi); //fb设备回收

    gpio_free(pdata->rs);
    gpio_free(pdata->reset);
    printk("%s remove\n", spi->modalias);
    return 0;
}

static struct spi_driver spi_drv = {
    .driver = {
        .owner = THIS_MODULE,
        .name = "hello_spi_fb",
    },
    .probe = spi_probe,
    .remove = spi_remove,
};

static int __init hello_init(void) {
    int ret;
    ret = spi_register_driver(&spi_drv);
    if (ret) {
        printk("hello driver fail\n");
    } else {
        printk("hello driver init\n");
    }
    return ret;
}

static void __exit hello_exit(void) {
    spi_unregister_driver(&spi_drv);
    printk("hello device exit\n");
}

module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

spi驱动里面主要是完成spi对屏幕的操作,初始化,刷图,等等。

framebuffer部分

framebuffer.c

#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>
#include "fb_spi.h"

typedef struct {
    struct spi_device *spi;
    struct task_struct *thread;
} 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;
}

struct fb_info * fb_init(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, LCD_W * LCD_H * 3, &p_addr, GFP_KERNEL);

    //额外分配lcd_data_t类型空间
    fbi = framebuffer_alloc(sizeof(lcd_data_t), NULL);
    data = fbi->par; //data指针指向额外分配的空间

    data->spi = spi;

    fbi->var.xres = LCD_W;
    fbi->var.yres = LCD_H;
    fbi->var.xres_virtual = LCD_W;
    fbi->var.yres_virtual = LCD_H;
    fbi->var.bits_per_pixel = 24;
    fbi->var.red.offset = 16;
    fbi->var.red.length = 6;
    fbi->var.green.offset = 8;
    fbi->var.green.length = 6;
    fbi->var.blue.offset = 0;
    fbi->var.blue.length = 6;

    strcpy(fbi->fix.id, "hello_fb");
    fbi->fix.smem_start = p_addr; //显存的物理地址
    fbi->fix.smem_len = LCD_W * LCD_H * 3;
    fbi->fix.type = FB_TYPE_PACKED_PIXELS;
    fbi->fix.visual = FB_VISUAL_TRUECOLOR;
    fbi->fix.line_length = LCD_W * 3;

    fbi->fbops = &fops;
    fbi->screen_base = v_addr; //显存虚拟地址
    fbi->screen_size = LCD_W * LCD_H * 3; //显存大小

    spi_set_drvdata(spi, fbi);
    register_framebuffer(fbi);
    data->thread = kthread_run(thread_func, fbi, spi->modalias);
    return fbi;
}

void fb_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);
}

framebuffer部分主要是开启一个刷图线程不断的把图像刷到屏幕上。图像数据在应用层会使用/dev/fbx写道对应设备缓冲区中,我们只需要刷图即可。

device设备

fb_dev.h

#ifndef __FB_DEV__
#define __FB_DEV__

struct fb_data{
	int reset;
	int rs;	
};
#endif

fb_dev.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <mach/gpio.h>
#include <mach/platform.h>
#include <linux/spi/spi.h>
#include <linux/amba/pl022.h>
#include "fb_dev.h"

static struct fb_data fb1_plat_data = {
	.reset  = PAD_GPIO_D + 21,
	.rs    = PAD_GPIO_D + 17,
};

struct pl022_config_chip spi0_info = {
	/* available POLLING_TRANSFER, INTERRUPT_TRANSFER, DMA_TRANSFER */
	.com_mode = CFG_SPI0_COM_MODE,
	.iface = SSP_INTERFACE_MOTOROLA_SPI,
	/* We can only act as master but SSP_SLAVE is possible in theory */
	.hierarchy = SSP_MASTER,
	/* 0 = drive TX even as slave, 1 = do not drive TX as slave */
	.slave_tx_disable = 1,
	.rx_lev_trig = SSP_RX_4_OR_MORE_ELEM,
	.tx_lev_trig = SSP_TX_4_OR_MORE_EMPTY_LOC,
	.ctrl_len = SSP_BITS_8,
	.wait_state = SSP_MWIRE_WAIT_ZERO,
	.duplex = SSP_MICROWIRE_CHANNEL_FULL_DUPLEX,
	.clkdelay = SSP_FEEDBACK_CLK_DELAY_1T,
};

static struct spi_board_info spi_plat_board = {
	.modalias        = "hello_spi_fb",    /* fixup */
	.max_speed_hz    = 25000000,     /* max spi clock (SCK) speed in HZ */
	.bus_num         = 0,           /* Note> set bus num, must be smaller than ARRAY_SIZE(spi_plat_device) */
	.chip_select     = 1,           /* Note> set chip select num, must be smaller than spi cs_num */
	.controller_data = &spi0_info,
	.mode            = SPI_MODE_3 | SPI_CPOL | SPI_CPHA,
	.platform_data   = &fb1_plat_data,
};

__attribute__ ((unused)) static void device_spi_delete(struct spi_master *master, unsigned cs)
{
	struct device *dev;
	char str[32];

	snprintf(str, sizeof(str), "%s.%u", dev_name(&master->dev), cs);

	dev = bus_find_device_by_name(&spi_bus_type, NULL, str);
	if (dev) {
		printk(": Deleting %s\n", str);
		device_del(dev);
	}
}

static struct spi_device *spi_device;
static int __init hello_init(void) {
	struct spi_master *master;

	master = spi_busnum_to_master(spi_plat_board.bus_num);
	if (!master) {
		printk(":  spi_busnum_to_master(%d) returned NULL\n",
		       spi_plat_board.bus_num);
		return -EINVAL;
	}
	/* make sure it's available */
	// device_spi_delete(master, spi_plat_board.chip_select);
	spi_device = spi_new_device(master, &spi_plat_board);
	put_device(&master->dev);
	if (!spi_device) {
		printk(":    spi_new_device() returned NULL\n");
		return -EPERM;
	}
	return 0;
	printk("hello device init\n");
	return 0;
}

static void __exit hello_exit(void) {
	if (spi_device) {
		if (spi_device->master->cleanup) {
			spi_device->master->cleanup(spi_device);
		}
		device_del(&spi_device->dev);
		kfree(spi_device);
	}
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");

在dev中定义好spi相关参数,这里我已经吧spi频率调到最高25Mhz了,这是nanopi spi频率的极限。以及屏幕相关的引脚复位脚:reset = GPIO_D_21,数据指令切换脚:rs = GPIO_D_17。

Makefile

最后吧所有驱动相关的文件编译成一个模块

KERNEL_DIR := /home/minicoco/disk1/Dev/nanopi/kernel/linux-3.4.y

hello:
	make -C ${KERNEL_DIR} M=`pwd` modules

.PHONY:
clean:
	make -C ${KERNEL_DIR} M=`pwd` clean

obj-m += fb_dev.o
obj-m += hello_framebuffer.o
hello_framebuffer-objs := fb_spi.o framebuffer.o

猜你喜欢

转载自blog.csdn.net/qq_16054639/article/details/107620007