嵌入式Linux驱动笔记(二十四)------framebuffer之使用spi-tft屏幕(上)

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/Guet_Kite/article/details/88766022

你好!这里是风筝的博客,

欢迎和我一起交流。


最近入手了一块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查看信息也确实发现是这样:
错误忘记复制下来了,懒得复现了,只有一些截图:
error
register
查看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, &region);
	}

	if (bh) {
		region.dx = info->var.xoffset;
		region.dy = info->var.yoffset + bs;
		region.width = rs;
		region.height = bh;
		info->fbops->fb_fillrect(info, &region);
	}
}

这函数哪里会使用非法地址导致Segmentation fault呢?显而易见,就是:info->fbops->fb_fillrect(info, &region)!!!!
我就是听信了那篇文章作者的话,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字样了
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
#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 ; 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屏幕(下)

展开阅读全文

没有更多推荐了,返回首页