你好!这里是风筝的博客,
欢迎和我一起交流。
最近入手了一块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 <linux/init.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <sound/core.h>
#include <linux/spi/spi.h>
#include <asm/uaccess.h>
#include <linux/cdev.h>#include <linux/gpio.h>
#include <linux/delay.h>#include <linux/fb.h>#include <linux/dma-mapping.h>
#include <linux/sched.h>
#include <linux/wait.h>#include <asm/mach/map.h>//#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
#endifstatic 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);
#endifLcd_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位的像素数据转换成rgb565fbi->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 ; i<Y_MAX_PIXEL/2 ; i++){color = rgb(255, 0, 255); for(j=0; j<X_MAX_PIXEL/2; j++)Lcd_WriteData_16Bit(spi, color);//(u8 *)&color = rgb(255, 255, 0);for(j=X_MAX_PIXEL/2; j<X_MAX_PIXEL; j++)Lcd_WriteData_16Bit(spi, color);}for(i=Y_MAX_PIXEL/2 ; i<Y_MAX_PIXEL; i++){color = rgb(0, 255, 255);for(j=0; j<X_MAX_PIXEL/2; j++)Lcd_WriteData_16Bit(spi, color);color = rgb(255, 0,0);for(j=X_MAX_PIXEL/2; j<X_MAX_PIXEL; j++)Lcd_WriteData_16Bit(spi, color);}
}static int tft_lcd_probe(struct spi_device *spi)
{int ret;struct tft_lcd *lcd_data = devm_kzalloc(&spi->dev, 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屏幕(下)