最近入手了一块spi接口的tft彩屏,想着在我的h3板子上使用framebuffer驱动起来。
我们知道:
Linux抽象出FrameBuffer这个设备来供用户态进程实现直接写屏。Framebuffer机制模仿显卡的功能,将显卡硬件结构抽象掉,可以通过Framebuffer的读写直接对显存进行操作。用户可以将Framebuffer看成是显示内存的一个映像,将其映射到进程地址空间之后,就可以直接进行读写操作,而写操作可以立即反应在屏幕上。这种操作是抽象的,统一的。用户不必关心物理显存的位置、换页机制等等具体细节。这些都是由Framebuffer设备驱动来完成的。
帧缓存有个地址,是在内存里。我们通过不停的向frame buffer中写入数据,CPU指定显示控制器工作, 显示控制器就自动的从frame buffer中取数据并显示出来。全部的图形都共享内存中同一个帧缓存。
所以参考了这篇文章:
68 linux framebuffer设备驱动之spi lcd屏驱动
和
27 在H5上实现spi-tft屏的简单驱动
【吐槽】结果问题就来了。。。。。。。
我把代码编译成模块,insmod 时就出现:Segmentation fault
段错误一般都是指针指向或者引用了错误的地址,dmesg查看信息也确实发现是这样:
错误忘记复制下来了,懒得复现了,只有一些截图:
查看dmesg可以发现,确实是指向虚拟地址的问题,而且就是使用register_framebuffer
函数引起的Segmentation fault。
我当时就纳闷了,以前在2440板子上使用register_framebuffer函数也没见啥问题啊,就百度了一下,发现有的帖子说直接把fb_notifier_call_chain函数注释掉…
这不是扯淡吗,fb_notifier_call_chain函数最后会调用到notifier_call_chain函数,在我的错误堆栈信息也看到确实调用了这个函数,这函数路径在kernel/notifier.c里。
kernel路径下的文件那是我等渣渣能乱动的吗!!
我试着注释掉此函数,就发现板子在这里本卡住了。。。。。。
最后为了这个问题,我只能自力更生了。
根据错误信息,追踪到bit_clear_margins函数:
static void bit_clear_margins(struct vc_data *vc, struct fb_info *info,
int color, int bottom_only)
{
unsigned int cw = vc->vc_font.width;
unsigned int ch = vc->vc_font.height;
unsigned int rw = info->var.xres - (vc->vc_cols*cw);
unsigned int bh = info->var.yres - (vc->vc_rows*ch);
unsigned int rs = info->var.xres - rw;
unsigned int bs = info->var.yres - bh;
struct fb_fillrect region;
region.color = color;
region.rop = ROP_COPY;
if (rw && !bottom_only) {
region.dx = info->var.xoffset + rs;
region.dy = 0;
region.width = rw;
region.height = info->var.yres_virtual;
info->fbops->fb_fillrect(info, ®ion);
}
if (bh) {
region.dx = info->var.xoffset;
region.dy = info->var.yoffset + bs;
region.width = rs;
region.height = bh;
info->fbops->fb_fillrect(info, ®ion);
}
}
这函数哪里会使用非法地址导致Segmentation fault呢?显而易见,就是:info->fbops->fb_fillrect(info, ®ion)!!!!
我就是听信了那篇文章作者的话,fb_ops留了空,就悲剧了。
struct fb_ops fops = { //这里不实现操作函数,使用fbmem.c里实现的功能函数
//如果这里实现了功能函数,则会调用这里实现的函数
};
后面我参考以前写的文章:
嵌入式Linux驱动笔记(三)------LCD驱动程序
填充了fb_ops:
static struct fb_ops fops = {
.owner = THIS_MODULE,
.fb_fillrect = cfb_fillrect,
.fb_copyarea = cfb_copyarea,
.fb_imageblit = cfb_imageblit,
};
然后编译,发现还是出现Segmentation fault,dmesg查看错误信息的输出,发现错误出现在cfb_imageblit函数里,打开函数一看,确实是:
void cfb_imageblit(struct fb_info *p, const struct fb_image *image)
{
...
fgcolor = ((u32*)(p->pseudo_palette))[image->fg_color];
...
}
我一看,确实是使用register_framebuffer(fbi);函数注册时,fb_info结构体的pseudo_palette成员没有填充,填充之后顺利解决,可以在根文件系统里发现/dev/fb设备!
可以尝试一下:
echo hello > /dev/tty1
这样,在LCD就会出现hello字样了
摆放问题,屏幕倒着的,转一下方向即可看出正确输出了。
附上代码一份:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
//#define USE_HORIZONTAL
//#define __DEBUG__ 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 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 tft_lcd_fb{
struct spi_device *spi; //记录fb_info对象对应的spi设备对象
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},
};
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;
//addset(spi, 0, 0); //从屏的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);
}
}
}
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);
//
}
return 0;
}
static u32 pseudo_palette[16];
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 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;
}
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;
v_addr = dma_alloc_coherent(NULL, LCD_X_SIZE*LCD_Y_SIZE*4, &p_addr, GFP_KERNEL);
//额外分配lcd_data_t类型空间
fbi = framebuffer_alloc(sizeof(struct tft_lcd_fb), &spi->dev);
if(fbi == NULL)
DEBUG("[%s]:framebuffer_alloc failed\n", __FUNCTION__);
//lcd_fb = &fbi[1]; //data指针指向额外分配的空间
lcd_fb = fbi->par; //data指针指向额外分配的空间
lcd_fb->spi = spi;
fbi->pseudo_palette = pseudo_palette;
fbi->var.activate = FB_ACTIVATE_NOW;
fbi->var.xres = LCD_X_SIZE;
fbi->var.yres = LCD_Y_SIZE;
fbi->var.xres_virtual = LCD_X_SIZE;
fbi->var.yres_virtual = LCD_Y_SIZE;
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 = LCD_X_SIZE*LCD_Y_SIZE*4;
fbi->fix.type = FB_TYPE_PACKED_PIXELS;
fbi->fix.visual = FB_VISUAL_TRUECOLOR;
fbi->fix.line_length = LCD_X_SIZE*4;
fbi->fbops = &fops;
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; //显存大小
//spi_set_drvdata(spi, fbi);
register_framebuffer(fbi);
lcd_fb->thread = kthread_run(thread_func_fb, fbi, spi->modalias);
return 0;
}
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设备初始化
return 0;
err0:
devm_gpiod_put(&spi->dev, lcd_data->rs_gpio);
DEBUG("[%s]:failed\n", __FUNCTION__);
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);
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");
还是要感谢一下jklinux大佬的文章的~
但是这驱动只是简单刷一下屏幕,而且是在线程里全局刷新,即使界面无更改,也要刷新,利用率非常低,为了提高效率,每次刷新只要刷更改过的界面即可,也就是刷新重绘区。
可以参考这篇:嵌入式Linux驱动笔记(二十六)------framebuffer之使用spi-tft屏幕(下)