1.framebuffer驱动
2.spi framebuffer驱动
3.spi framebuffer驱动(切换并显示虚拟终端)
上一节实现了一个framebuffer驱动,但是没有具体操作硬件,这节做一个SPI屏驱动,整个代码作为一个spi驱动去写。spi相关内容可以看一文读懂linux设备驱动模型(常见总线bus)。
首先是spi驱动,屏幕的操作由厂家C51代码改编而来。屏幕控制器是ILI9488,分辨率320 * 480
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
#include
#include
#include
#include
#include
#include
#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.c
#include
#include
#include
#include
#include
#include
#include
#include
#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写道对应设备缓冲区中,我们只需要刷图即可。
fb_dev.h
#ifndef __FB_DEV__
#define __FB_DEV__
struct fb_data{
int reset;
int rs;
};
#endif
fb_dev.c
#include
#include
#include
#include
#include
#include
#include
#include
#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。
最后吧所有驱动相关的文件编译成一个模块
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