上一篇文章,描述的是如何驱动spi的屏幕,嵌入式Linux驱动笔记(二十四)------framebuffer之使用spi-tft屏幕(上)
但是是使用的是在内核里开一个线程来不停的绘制图形,CPU占用率非常高,效率低。
有种较为方便的办法,就是局部刷新,每次只重绘“脏区”即可。
参考了github里几个9225芯片的驱动,对本驱动进行了一些改进,主要是加入fb_deferred_io_init函数,建立延定时工作队列。
标准frambuffer操作api->mkdirty->fbtft_mkdirty()->schedule_delayed_work(&info->deferred_work, fbdefio->delay);->deferred_io()->fbtft_deferred_io()
故只要定期执行deferred_io()即可完成FPS
参考:ili9225.c
和
ili9225fb.c
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
//#define USE_HORIZONTAL
//#define __DEBUG__ 1
//#define OPEN_FPS 1
#ifdef __DEBUG__
#define DEBUG(format,...) \
printk("DEBUG::"format, ##__VA_ARGS__)
#else
#define DEBUG(format,...)
#endif
#define LCD_X_SIZE 176
#define LCD_Y_SIZE 220
#ifdef USE_HORIZONTAL//如果定义了横屏
#define X_MAX_PIXEL LCD_Y_SIZE
#define Y_MAX_PIXEL LCD_X_SIZE
#else//竖屏
#define X_MAX_PIXEL LCD_X_SIZE
#define Y_MAX_PIXEL LCD_Y_SIZE
#endif
static struct timer_list frame_timer;
static int tft_lcdfb_setcolreg(unsigned int regno, unsigned int red,
unsigned int green, unsigned int blue,
unsigned int transp, struct fb_info *info);
struct tft_lcd{
struct gpio_desc *reset_gpio;
struct gpio_desc *rs_gpio;
};
struct fb_page {
unsigned short x;
unsigned short y;
u32 *buffer;//unsigned short
unsigned short len;
};
struct tft_lcd_fb{
struct spi_device *spi; //记录fb_info对象对应的spi设备对象
struct fb_info *fbi;
unsigned int pages_count;
struct fb_page *pages;
u32 *last_buffer_start;//unsigned short
struct task_struct *thread; //记录线程对象的地址,此线程专用于把显存数据发送到屏的驱动ic
};
static struct fb_ops fops = {
.owner = THIS_MODULE,
.fb_setcolreg = tft_lcdfb_setcolreg, /*设置颜色寄存器*/
.fb_fillrect = cfb_fillrect, /*用像素行填充矩形框,通用库函数*/
.fb_copyarea = cfb_copyarea, /*将屏幕的一个矩形区域复制到另一个区域,通用库函数*/
.fb_imageblit = cfb_imageblit, /*显示一副图像,通用库函数*/
};
struct regdata_t{
u8 reg;
u16 data;
int delay_ms;
}regdatas[] = {
{0x10, 0x0000, 0}, {0x11, 0x0000, 0}, {0x12, 0x0000, 0},
{0x13, 0x0000, 0}, {0x14, 0x0000, 40},
{0x11, 0x0018, 0}, {0x12, 0x1121, 0}, {0x13, 0x0063, 0},
{0x14, 0x3961, 0}, {0x10, 0x0800, 10}, {0x11, 0x1038, 30},
{0x02, 0x0100, 0},
#ifdef USE_HORIZONTAL//如果定义了横屏
{0x01, 0x001c, 0}, {0x03, 0x1038, 0},
#else//竖屏
{0x01, 0x011c, 0}, {0x03, 0x1030, 0},
#endif
{0x07, 0x0000, 0}, {0x08, 0x0808, 0}, {0x0b, 0x1100, 0},
{0x0c, 0x0000, 0}, {0x0f, 0x0501, 0}, {0x15, 0x0020, 0},
{0x20, 0x0000, 0}, {0x21, 0x0000, 0},
{0x30, 0x0000}, {0x31, 0x00db}, {0x32, 0x0000}, {0x33, 0x0000},
{0x34, 0x00db}, {0x35, 0x0000}, {0x36, 0x00af}, {0x37, 0x0000},
{0x38, 0x00db}, {0x39, 0x0000},
{0x50, 0x0603}, {0x51, 0x080d}, {0x52, 0x0d0c}, {0x53, 0x0205},
{0x54, 0x040a}, {0x55, 0x0703}, {0x56, 0x0300}, {0x57, 0x0400},
{0x58, 0x0b00}, {0x59, 0x0017},
{0x0f, 0x0701}, {0x07, 0x0012, 50}, {0x07, 0x1017},
};
int frames = 0;
#ifdef OPEN_FPS
static void frames_timer_function(unsigned long data)
{
printk("frames is %d fps\n",frames);
frames = 0;
//mod_timer(&frame_timer, jiffies+HZ/1000);
mod_timer(&frame_timer, jiffies + (1*HZ));
}
#endif
static void Lcd_WriteIndex(struct spi_device *spi, u8 Index)
{
struct tft_lcd *pdata = spi_get_drvdata(spi);
gpiod_set_value(pdata->rs_gpio, 0); //高电平
spi_write(spi, &Index, 1);
}
static void Lcd_WriteData_16Bit(struct spi_device *spi, u16 Data)
{
u8 buf[2];
struct tft_lcd *pdata = spi_get_drvdata(spi);
buf[0] = ((u8)(Data>>8));
buf[1] = ((u8)(Data&0x00ff));
gpiod_set_value(pdata->rs_gpio, 1); //高电平
spi_write(spi, &buf[0], 1);
spi_write(spi, &buf[1], 1);
}
static void LCD_WriteReg(struct spi_device *spi, u8 Index, u16 Data)
{
int addr;
addr = Index;
Lcd_WriteIndex(spi, addr);
Lcd_WriteData_16Bit(spi, Data);
}
static void Lcd_SetRegion(struct spi_device *spi, u8 xStar, u8 yStar,u8 xEnd,u8 yEnd)
{
#ifdef USE_HORIZONTAL//如果定义了横屏
LCD_WriteReg(spi,0x38,xEnd);
LCD_WriteReg(spi,0x39,xStar);
LCD_WriteReg(spi,0x36,yEnd);
LCD_WriteReg(spi,0x37,yStar);
LCD_WriteReg(spi,0x21,xStar);
LCD_WriteReg(spi,0x20,yStar);
#else//竖屏
LCD_WriteReg(spi,0x36,xEnd);
LCD_WriteReg(spi,0x37,xStar);
LCD_WriteReg(spi,0x38,yEnd);
LCD_WriteReg(spi,0x39,yStar);
LCD_WriteReg(spi,0x20,xStar);
LCD_WriteReg(spi,0x21,yStar);
#endif
Lcd_WriteIndex(spi,0x22);
}
static int lcd_dt_parse(struct spi_device *spi, struct tft_lcd *lcd_data)
{
lcd_data->reset_gpio = devm_gpiod_get(&spi->dev, "rest", GPIOD_OUT_HIGH);
if (IS_ERR(lcd_data->reset_gpio))
goto err0;
gpio_direction_output(desc_to_gpio(lcd_data->reset_gpio), 1);
lcd_data->rs_gpio = devm_gpiod_get(&spi->dev, "rs", GPIOD_OUT_HIGH);
if (IS_ERR(lcd_data->rs_gpio))
goto err1;
gpio_direction_output(desc_to_gpio(lcd_data->rs_gpio), 1);
return 0;
err1:
devm_gpiod_put(&spi->dev, lcd_data->reset_gpio);
err0:
DEBUG("[%s]:failed\n", __FUNCTION__);
return -1;
}
static void lcd_init(struct spi_device *spi, struct tft_lcd *pdata)
{
int i =0;
gpiod_set_value(pdata->reset_gpio, 0); //设低电平
msleep(100);
gpiod_set_value(pdata->reset_gpio, 1); //设高电平
msleep(50);
for (i = 0; i < ARRAY_SIZE(regdatas); i++)
{
LCD_WriteReg(spi, regdatas[i].reg, regdatas[i].data);
if (regdatas[i].delay_ms)
msleep(regdatas[i].delay_ms);
}
}
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;
//从屏的0,0坐标开始刷
Lcd_SetRegion(spi, 0,0,X_MAX_PIXEL-1,Y_MAX_PIXEL-1);
DEBUG("[%s] \n",__FUNCTION__);
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);
Lcd_WriteData_16Bit(spi, c);
}
}
}
static void fb_update(struct fb_info *fbi, struct list_head *pagelist);
int thread_func_fb(void *data)
{
struct fb_info *fbi = (struct fb_info *)data;
struct tft_lcd_fb *lcd_fb = fbi->par;
while (1)
{
if (kthread_should_stop())
break;
show_fb(fbi, lcd_fb->spi);
//fb_update(fbi, &fbi->fbdefio->pagelist);
frames++;
}
return 0;
}
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 u32 pseudo_palette[16];
static int tft_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)
{
DEBUG("[%s] the regno is %d !!\n",__FUNCTION__, regno);
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 void fb_copy(struct tft_lcd_fb *item, unsigned int index, struct spi_device *spi)
{
unsigned short x;
unsigned short y;
u32 *buffer;
unsigned int len;
int i = 0;
u32 k;
u16 c;
u8 *pp;
// unsigned int count;
//unsigned int diff;
x = item->pages[index].x;
y = item->pages[index].y;
buffer = item->pages[index].buffer;
len = item->pages[index].len;
// check if we should change the chip framebuffer pointer
if (item->last_buffer_start!=buffer)
{
/*
if (x!=0) // The ssd1963 needs to start from X=0 to keep drawing in next lines
{
diff = x*(item->info->var.bits_per_pixel / 16);
buffer-=diff;
len+=diff;
}
*/
//dev_dbg(item->dev, "%s: page[%u]: x=%3hu y=%3hu buffer=0x%p len=%3hu diff=%3hu\n",
// __func__, index, x, y, buffer, len,diff);
Lcd_SetRegion(spi, x,y,X_MAX_PIXEL-1,Y_MAX_PIXEL-1);
}
for (i = 0; i < len; i++)
{
k = buffer[i];//取出一个像素点的32位数据
// rgb8888 --> rgb565
pp = (u8 *)&k;
c = pp[0] >> 3; //蓝色
c |= (pp[1]>>2)<<5; //绿色
c |= (pp[2]>>3)<<11; //红色
//发出像素数据的rgb565
Lcd_WriteData_16Bit(spi, c);
}
item->last_buffer_start=&buffer[len];
}
static void fb_update(struct fb_info *fbi, struct list_head *pagelist)
{
struct tft_lcd_fb *lcd_fb = (struct tft_lcd_fb *)fbi->par;
struct page *page;
DEBUG("[%s]:fb_update\n", __FUNCTION__);
list_for_each_entry(page, pagelist, lru) {
DEBUG("[%s]:the pages x is %d, y is %d \n",__func__, lcd_fb->pages[page->index].x, lcd_fb->pages[page->index].y);
fb_copy(lcd_fb, page->index, lcd_fb->spi);
}
frames++;
}
static struct fb_deferred_io fb_defio = {
.delay = HZ/20,
.deferred_io = &fb_update,
};
static struct fb_var_screeninfo fb_var __initdata = {
.xres = LCD_X_SIZE,
.yres = LCD_Y_SIZE,
.xres_virtual = LCD_X_SIZE,
.yres_virtual = LCD_Y_SIZE,
.bits_per_pixel = 32, // 屏是rgb565, 但QT程序只能支持32位.还需要在刷图时把32位的像素数据转换成rgb565
.red.offset = 16,
.red.length = 8,
.green.offset = 8,
.green.length = 8,
.blue.offset = 0,
.blue.length = 8,
.activate = FB_ACTIVATE_NOW,
.vmode = FB_VMODE_NONINTERLACED,
};
static struct fb_fix_screeninfo fb_fix __initdata= {
.type = FB_TYPE_PACKED_PIXELS,
.visual = FB_VISUAL_TRUECOLOR,
.line_length = LCD_X_SIZE*4,//32 bit = 4 byte
.accel = FB_ACCEL_NONE,
.id = "myfb",
};
static int fb_video_alloc(struct tft_lcd_fb *item)
{
unsigned int frame_size;
frame_size = item->fbi->fix.line_length * item->fbi->var.yres;
item->pages_count = frame_size / PAGE_SIZE;
if ((item->pages_count * PAGE_SIZE) < frame_size) {
item->pages_count++;
}
item->fbi->fix.smem_len = item->pages_count * PAGE_SIZE;
item->fbi->fix.smem_start =
(u32)vmalloc(item->fbi->fix.smem_len);
if (!item->fbi->fix.smem_start) {
dev_err(&item->spi->dev, "[%s]: unable to vmalloc\n", __func__);
return -ENOMEM;
}
memset((void *)item->fbi->fix.smem_start, 0, item->fbi->fix.smem_len);
return 0;
}
static int fb_pages_alloc(struct tft_lcd_fb *item)
{
u32 pixels_per_page;
u32 yoffset_per_page;
u32 xoffset_per_page;
u32 index;
unsigned short x = 0;
unsigned short y = 0;
u32 *buffer;
unsigned int len;
item->pages = kmalloc(item->pages_count * sizeof(struct fb_page), GFP_KERNEL);
if (!item->pages) {
dev_err(&item->spi->dev, "[%s]: unable to kmalloc for fb_page\n", __func__);
return -ENOMEM;
}
pixels_per_page = PAGE_SIZE / (item->fbi->var.bits_per_pixel / 8);
yoffset_per_page = pixels_per_page / item->fbi->var.xres;
xoffset_per_page = pixels_per_page -
(yoffset_per_page * item->fbi->var.xres);
DEBUG("[%s]: pixels_per_page=%hu "
"yoffset_per_page=%hu xoffset_per_page=%hu\n",
__func__, pixels_per_page, yoffset_per_page, xoffset_per_page);//5 144
buffer = (u32 *)item->fbi->fix.smem_start;
for (index = 0; index < item->pages_count; index++) {
len = (item->fbi->var.xres * item->fbi->var.yres) -
(index * pixels_per_page);
if (len > pixels_per_page) {
len = pixels_per_page;
}
printk("%s: page[%d]: x=%3hu y=%3hu buffer=0x%p len=%3hu\n",
__func__, index, x, y, buffer, len);
item->pages[index].x = x;
item->pages[index].y = y;
item->pages[index].buffer = buffer;
item->pages[index].len = len;
x += xoffset_per_page;
if (x >= item->fbi->var.xres) {
y++;
x -= item->fbi->var.xres;
}
y += yoffset_per_page;
buffer += pixels_per_page;
}
/* tell the copy routines that the LCD controller is ready to receive data
from the start of the buffer X,Y = (0,0) */
item->last_buffer_start=(u32 *)item->fbi->fix.smem_start;
return 0;
}
int tft_lcd_fb_register(struct spi_device *spi) //此函数在probe函数里被调用
{
struct fb_info *fbi;
//u8 *v_addr;
//u32 p_addr;
struct tft_lcd_fb *lcd_fb;
int ret = 0;
//v_addr = dma_alloc_coherent(NULL, LCD_X_SIZE*LCD_Y_SIZE*4, &p_addr, GFP_KERNEL);
//额外分配tft_lcd_fb类型空间
fbi = framebuffer_alloc(sizeof(struct tft_lcd_fb), &spi->dev);
if(fbi == NULL){
dev_err(&spi->dev, "[%s]: framebuffer_alloc failed\n", __func__);
goto out;
}
lcd_fb = fbi->par; //data指针指向额外分配的空间
lcd_fb->spi = spi;
lcd_fb->fbi = fbi;
fbi->pseudo_palette = pseudo_palette;
fbi->var = fb_var;
fbi->fix = fb_fix;
fbi->fbops = &fops;
fbi->flags = FBINFO_FLAG_DEFAULT | FBINFO_VIRTFB;
//fbi->fix.smem_start = p_addr; //显存的物理地址
//fbi->fix.smem_len = LCD_X_SIZE*LCD_Y_SIZE*4;
//fbi->screen_base = v_addr; //显存虚拟地址
//fbi->screen_base = dma_alloc_writecombine(NULL, fbi->fix.smem_len, &fbi->fix.smem_start, GFP_KERNEL);
//fbi->screen_size = LCD_X_SIZE*LCD_Y_SIZE*4; //显存大小
ret = fb_video_alloc(lcd_fb);
if (ret) {
dev_err(&spi->dev, "[%s]: unable to fb_video_alloc\n", __func__);
goto out_info;
}
fbi->screen_base = (char __iomem *)lcd_fb->fbi->fix.smem_start;
ret = fb_pages_alloc(lcd_fb);
if (ret) {
dev_err(&spi->dev, "[%s]: unable to fb_pages_init\n", __func__);
goto out_info;
}
fbi->fbdefio = &fb_defio;
fb_deferred_io_init(fbi);
ret = register_framebuffer(fbi);
if (ret < 0) {
dev_err(&spi->dev, "[%s]: unable to register_frambuffer\n", __func__);
goto out_info;
}
//lcd_fb->thread = kthread_run(thread_func_fb, fbi, spi->modalias);
return 0;
out_info:
framebuffer_release(fbi);
out:
return ret;
}
static void tft_fb_test(struct spi_device *spi)
{
int i,j;
u16 color = 0x001f; /* rgb565, 蓝色 */
Lcd_SetRegion(spi, 0,0,X_MAX_PIXEL-1,Y_MAX_PIXEL-1); //设置从屏哪个坐标开始显示,到哪个坐标结束
#define rgb(r, g, b) ((((r>>3)&0x1f)<<11) | (((g>>2)&0x3f)<<5) | ((b>>3)&0x1f))
for(i=0 ; idev, sizeof(struct tft_lcd), GFP_KERNEL);
ret = lcd_dt_parse(spi, lcd_data);
if(ret !=0)
goto err0;
DEBUG("[%s]:success\n", __FUNCTION__);
spi_set_drvdata(spi, lcd_data);
lcd_init(spi, lcd_data);
tft_fb_test(spi);
ret = tft_lcd_fb_register(spi); //fb设备初始化
#ifdef OPEN_FPS
init_timer(&frame_timer);
frame_timer.expires = jiffies+(1*HZ);
frame_timer.function = frames_timer_function;
add_timer(&frame_timer);
#endif
return 0;
err0:
devm_gpiod_put(&spi->dev, lcd_data->rs_gpio);
dev_err(&spi->dev, "[%s]:failed\n", __func__);
return ret;
}
int tft_lcd_remove(struct spi_device *spi)
{
struct tft_lcd *pdata = spi_get_drvdata(spi);
DEBUG("[%s]:success\n", __FUNCTION__);
devm_gpiod_put(&spi->dev, pdata->rs_gpio);
devm_gpiod_put(&spi->dev, pdata->reset_gpio);
//kthread_stop(data->thread); //让刷图线程退出container_of(&temp.j,struct test,j);
//unregister_framebuffer(fbi);
//dma_free_coherent(NULL, fbi->screen_size, fbi->screen_base, fbi->fix.smem_start);
return 0;
}
struct of_device_id tft_lcd_ids[] = {
{.compatible = "nanopi,tft_lcd_spi"},
{},
};
struct spi_driver tft_lcd_drv = {
.probe = tft_lcd_probe,
.remove = tft_lcd_remove,
.driver = {
.owner = THIS_MODULE,
.name = "tft_lcd_drv",
.of_match_table = tft_lcd_ids,
},
};
module_spi_driver(tft_lcd_drv);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("TFT LCD SPI driver");
这样即可做到局部刷新,哪里改变就刷新它的page即可。
其实把,最好还是使用内核里的fbtft驱动,如果内核没有fbtft驱动,可以去github下载fbtft的驱动,地址:https://github.com/notro/fbtft
Linux内核里也是有这个fbtft这个驱动的,比如我的Linux4.14就在drivers/staging/fbtft/目录下。
在make menuconfig里选择对应的芯片驱动为m,作为模块,最后:
make ARCH=arm CROSS_COMPILE=arm-linux- modules SUBDIRS=/work/linux/drivers/staging/fbtft/
即可编译出ko文件,insmod即可使用。
测试用例可以使用fbtft里的应用测试一下:https://github.com/notro/fbtft/wiki/Testing
或者网上找一个参考试一试:一个简单的framebuffer的显示使用例子
当然这个例子我没使用过,网上随便找的。
最后,给个framebuffer驱动分析