zynq linux驱动spi显示屏st7796s/st7796u,并使用framebuffer

st7796s芯片 linux内核中没有驱动,需要自己写。网上找了一下不知道是不是这个芯片冷门,只有几个单片机的例程。反正也不难就自己写一个。不过写的着急所以代码比较乱。需要的自己改一下。

st7796u是st7796s的升级版本,提高了分辨率。除了初始寄存器配置不同,没有区别,代码都贴出来。

首先配置vivado 程序,使用EMIO

设备树信息

&spi0 {
    st7796v@0 {
        status = "okay";
        compatible = "sitronix,st7796s";
       	reg = <0>;
       	spi-max-frequency =<25000000>;        //EMIO SPI时钟最大25MHz
        reset-gpios=<&gpio0 55 GPIO_ACTIVE_HIGH>;   //EMIO 55
       	dc-gpios  =<&gpio0 54 GPIO_ACTIVE_LOW>;   //EMIO 54
        };
};

驱动代码

#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/spi/spi.h>
#include <linux/uaccess.h>
#include <linux/delay.h>
#include <linux/gpio.h>
#include <linux/of.h>
#include <linux/of_gpio.h>

#define DEVICE_NAME		"spi_st7796s"	/* 名字 */

/*
 * 自定义结构体myspi_dev
 * 用于描述这个虚拟的spi从机设备信息
 */
struct myspi_dev {
	struct spi_device *spi;			/* spi从机设备对象指针 */
	dev_t devid;					/* 设备号 */
	struct cdev cdev;				/* cdev结构体 */
	struct class *class;			/* 类 */
	struct device *device;			/* 设备 */
	void   *private_data;      //私有数据
	
	int                 dc_gpio;
    int                 rest_gpio;
};

static struct myspi_dev myspi;	// 定义一个spi虚拟设备

#define RED  	0xf800
#define GREEN	0x07e0
#define BLUE 	0x001f
#define WHITE	0xffff
#define BLACK	0x0000
#define YELLOW  0xFFE0
#define GRAY0   0xEF7D   		//灰色0 3165 00110 001011 00101
#define GRAY1   0x8410      	//灰色1      00000 000000 00000
#define GRAY2   0x4208      	//灰色2  1111111111011111

#define LCD_W 320
#define LCD_H 480

#define USE_HORIZONTAL  	 0	//顺时针旋转屏幕角度0-3


static int LCD_WriteReg(struct spi_device *spi, unsigned char c)
{
	struct spi_message msg = {0};
	struct spi_transfer xfer = {0};
	unsigned char tx;
	int ret;

	gpio_set_value(myspi.dc_gpio, 0);/* 拉低发命令 */

	xfer.tx_buf = &tx;		/* 发送数据缓存区 */
	xfer.bits_per_word = 8;	/* 一次传输8个bit */
	xfer.len = 1;			/* 传输长度为2个字节:寄存器地址+写入值 */

	tx = c;			/* 要写入的从机设备寄存器地址(举个例子,假设寄存器地址是一个字节) */
	spi_message_init(&msg);	/* 初始化spi_message */
	spi_message_add_tail(&xfer, &msg);	/* 将spi_transfer添加到spi_message */
	ret = spi_sync(spi, &msg);		/* 执行同步数据传输 */
	if (ret)
		return ret;
		
	return 0;
}

static int LCD_WriteData(struct spi_device *spi, unsigned char c)
{
	struct spi_message msg = {0};
	struct spi_transfer xfer = {0};
	unsigned char tx;
	int ret;

    gpio_set_value(myspi.dc_gpio, 1);    /* 拉高发数据 */

	xfer.tx_buf = &tx;		/* 发送数据缓存区 */
	xfer.bits_per_word = 8;	/* 一次传输8个bit */
	xfer.len = 1;			/* 传输长度为2个字节:寄存器地址+写入值 */

	tx = c;			/* 要写入的从机设备寄存器地址(举个例子,假设寄存器地址是一个字节) */
	spi_message_init(&msg);	/* 初始化spi_message */
	spi_message_add_tail(&xfer, &msg);	/* 将spi_transfer添加到spi_message */
	ret = spi_sync(spi, &msg);		/* 执行同步数据传输 */
	if (ret)
		return ret;
		
	return 0;
}

static int LCD_WriteData_16bit(struct spi_device *spi, unsigned short dat)
{
	int ret;
	struct spi_message msg = {0};
	struct spi_transfer xfer = {0};
	unsigned char tx = dat>>8;;

    gpio_set_value(myspi.dc_gpio, 1);    /* 拉高发数据 */

	xfer.tx_buf = &tx;		/* 发送数据缓存区 */
	xfer.bits_per_word = 8;	/* 一次传输8个bit */
	xfer.len = 1;			/* 传输长度为2个字节:寄存器地址+写入值 */

    spi_message_init(&msg);   /* 初始化spi_message */
    spi_message_add_tail(&xfer, &msg);    /* 将spi_transfer添加到spi_message队列 */

    ret = spi_sync(spi, &msg);        /* 使用同步发送 */

    tx = dat&0xFF;
    xfer.tx_buf = &tx;
    ret = spi_sync(spi, &msg);        /* 使用同步发送 */
	if (ret)
		return ret;
	
    return 0;
}

static void LCD_Set_Window(struct spi_device *spi,u16 xstart,u16 ystart,u16 xend,u16 yend)
{
    LCD_WriteReg(spi, 0x2A);
	LCD_WriteData_16bit(spi, xstart);
	LCD_WriteData_16bit(spi, xend);

    LCD_WriteReg(spi, 0x2B); 
    LCD_WriteData_16bit(spi, ystart);
    LCD_WriteData_16bit(spi, yend);  

    LCD_WriteReg(spi, 0x2C); 
}

void LCD_Disp_Pic(struct spi_device *spi)
{
    int i,j;

    /* 设置屏幕和显示部分 */
    LCD_Set_Window(spi, 0, 0, LCD_W-1, LCD_H-1);
	
	for(i=0;i<LCD_H;i++)
	{
		for(j=0;j<LCD_W;j++)
		{	
			LCD_WriteData_16bit(spi, BLUE);
		}
	}
	
    mdelay(10);

    printk(KERN_ALERT "LCD display picture ok!\r\n");
}

static void lcd_reg_init(struct spi_device *spi)
{
    printk(KERN_ALERT "This is lcd reg init func!\r\n");

    gpio_set_value(myspi.rest_gpio, 0);
    mdelay(100);
    gpio_set_value(myspi.rest_gpio, 1);
    mdelay(50);
    /******************************/
	LCD_WriteReg(spi,0xF0);
	LCD_WriteData(spi,0xC3);
	LCD_WriteReg(spi,0xF0);
	LCD_WriteData(spi,0x96);
	LCD_WriteReg(spi,0x36);
	LCD_WriteData(spi,0x68);	
	LCD_WriteReg(spi,0x3A);
	LCD_WriteData(spi,0x05);	
	LCD_WriteReg(spi,0xB0);
	LCD_WriteData(spi,0x80);	
	LCD_WriteReg(spi,0xB6);
	LCD_WriteData(spi,0x00);
	LCD_WriteData(spi,0x02);	
	LCD_WriteReg(spi,0xB5);
	LCD_WriteData(spi,0x02);
	LCD_WriteData(spi,0x03);
	LCD_WriteData(spi,0x00);
	LCD_WriteData(spi,0x04);
	LCD_WriteReg(spi,0xB1);
	LCD_WriteData(spi,0x80);	
	LCD_WriteData(spi,0x10);	
	LCD_WriteReg(spi,0xB4);
	LCD_WriteData(spi,0x00);
	LCD_WriteReg(spi,0xB7);
	LCD_WriteData(spi,0xC6);
	LCD_WriteReg(spi,0xC5);
	LCD_WriteData(spi,0x24);
	LCD_WriteReg(spi,0xE4);
	LCD_WriteData(spi,0x31);
	LCD_WriteReg(spi,0xE8);
	LCD_WriteData(spi,0x40);
	LCD_WriteData(spi,0x8A);
	LCD_WriteData(spi,0x00);
	LCD_WriteData(spi,0x00);
	LCD_WriteData(spi,0x29);
	LCD_WriteData(spi,0x19);
	LCD_WriteData(spi,0xA5);
	LCD_WriteData(spi,0x33);
	LCD_WriteReg(spi,0xC2);
	LCD_WriteReg(spi,0xA7);
	
	LCD_WriteReg(spi,0xE0);
	LCD_WriteData(spi,0xF0);
	LCD_WriteData(spi,0x09);
	LCD_WriteData(spi,0x13);
	LCD_WriteData(spi,0x12);
	LCD_WriteData(spi,0x12);
	LCD_WriteData(spi,0x2B);
	LCD_WriteData(spi,0x3C);
	LCD_WriteData(spi,0x44);
	LCD_WriteData(spi,0x4B);
	LCD_WriteData(spi,0x1B);
	LCD_WriteData(spi,0x18);
	LCD_WriteData(spi,0x17);
	LCD_WriteData(spi,0x1D);
	LCD_WriteData(spi,0x21);

	LCD_WriteReg(spi,0XE1);
	LCD_WriteData(spi,0xF0);
	LCD_WriteData(spi,0x09);
	LCD_WriteData(spi,0x13);
	LCD_WriteData(spi,0x0C);
	LCD_WriteData(spi,0x0D);
	LCD_WriteData(spi,0x27);
	LCD_WriteData(spi,0x3B);
	LCD_WriteData(spi,0x44);
	LCD_WriteData(spi,0x4D);
	LCD_WriteData(spi,0x0B);
	LCD_WriteData(spi,0x17);
	LCD_WriteData(spi,0x17);
	LCD_WriteData(spi,0x1D);
	LCD_WriteData(spi,0x21);

  	LCD_WriteReg(spi,0X36);
	LCD_WriteData(spi,0xEC);
	LCD_WriteReg(spi,0xF0);
	LCD_WriteData(spi,0xC3);
	LCD_WriteReg(spi,0xF0);
	LCD_WriteData(spi,0x69);
	LCD_WriteReg(spi,0X13);
	LCD_WriteReg(spi,0X11);
	LCD_WriteReg(spi,0X29);

	LCD_WriteReg(spi,0x36);  
	LCD_WriteData(spi,0x48);	  
	
    printk(KERN_ALERT "LCD init ok!\r\n");
    LCD_Disp_Pic(spi);
}

static int lcd_gpio_init(struct spi_device *spi)
{
    int ret;
    int flag1,flag2;

    /* 获取GPIO */
    myspi.dc_gpio = of_get_named_gpio_flags(spi->dev.of_node, "dc-gpios", 0, (enum of_gpio_flags *)&flag1);
    ret = gpio_is_valid(myspi.dc_gpio);
    if(ret < 0)
    {
        printk(KERN_ALERT "get dc gpio failed !\r\n");
        return -EINVAL;
    }
    ret = gpio_request(myspi.dc_gpio,"dc-gpios");
    if(ret < 0)
    {
        printk(KERN_ALERT "request gpio dc failed !\r\n");
        gpio_free(myspi.dc_gpio);
    }
    printk(KERN_ALERT "dc gpio num is %d !\r\n",myspi.dc_gpio);

    myspi.rest_gpio = of_get_named_gpio_flags(spi->dev.of_node, "reset-gpios", 0, (enum of_gpio_flags *)&flag2);
    ret = gpio_is_valid(myspi.rest_gpio);
    if(ret < 0)
    {
        printk(KERN_ALERT "get rest gpio failed !\r\n");
        return -EINVAL;
    }
    ret = gpio_request(myspi.rest_gpio,"reset-gpios");
    if(ret < 0)
    {
        printk(KERN_ALERT "request gpio rest failed !\r\n");
        gpio_free(myspi.rest_gpio);
    }
    printk(KERN_ALERT "rest gpio num is %d !\r\n",myspi.rest_gpio);

    /* 设置方向 */
	gpio_direction_output(myspi.dc_gpio,0);
	gpio_direction_output(myspi.rest_gpio,0);

    return ret;
}

/*
 * @description			: 打开设备
 * @param – inode		: 传递给驱动的inode
 * @param - filp		: 设备文件,file结构体有个叫做private_data的成员变量
 * 						  一般在open的时候将private_data指向设备结构体。
 * @return				: 0 成功;其他 失败
 */
static int myspi_open(struct inode *inode, struct file *filp)
{
	filp->private_data = &myspi;
	return 0;
}

/*
 * @description			: 从设备读取数据 
 * @param – filp		: 要打开的设备文件(文件描述符)
 * @param - buf			: 返回给用户空间的数据缓冲区
 * @param - cnt			: 要读取的数据长度
 * @param – off			: 相对于文件首地址的偏移
 * @return				: 读取的字节数,如果为负值,表示读取失败
 */
static ssize_t myspi_read(struct file *filp, char __user *buf,
			size_t cnt, loff_t *off)
{
	struct myspi_dev *dev = filp->private_data;
	struct spi_message msg = {0};
	struct spi_transfer xfers[2] = {0};
	unsigned char tx[5];
	unsigned char rx[5];
	int ret;

	/* 填充两次传输序列 */
	xfers[0].tx_buf = tx;			/* 发送数据缓存区 */
	xfers[0].bits_per_word = 8;		/* 一次传输8个bit */
	xfers[0].len = 1;				/* 传输长度为1个字节 */

	xfers[1].tx_buf = rx;			/* 接收数据缓存区 */
	xfers[1].bits_per_word = 8;		/* 一次传输8个bit */
	xfers[1].len = 1;				/* 传输长度为1个字节 */

	tx[0] = 0x45;			/* 要读取的从机设备寄存器地址(举个例子,假设寄存器地址是一个字节) */
	spi_message_init(&msg);	/* 初始化spi_message */
	spi_message_add_tail(&xfers[0], &msg);	/* 将发送序列添加到spi_message */
	spi_message_add_tail(&xfers[1], &msg);	/* 将读取序列添加到spi_message */
	ret = spi_sync(dev->spi, &msg);			/* 执行同步数据传输 */
	if (ret)
		return ret;

	/* 将数据拷贝到用户空间 */
	return copy_to_user(buf, rx, 1);
}

/*
 * @description			: 向设备写数据 
 * @param – filp		: 设备文件,表示打开的文件描述符
 * @param - buf			: 要写给设备写入的数据
 * @param - cnt			: 要写入的数据长度
 * @param - offt		: 相对于文件首地址的偏移
 * @return				: 写入的字节数,如果为负值,表示写入失败
 */
static ssize_t myspi_write(struct file *filp, const char __user *buf,
			size_t cnt, loff_t *offt)
{
	struct myspi_dev *dev = filp->private_data;
	struct spi_message msg = {0};
	struct spi_transfer xfer = {0};
	unsigned char tx[5];
	int ret;

	xfer.tx_buf = tx;		/* 发送数据缓存区 */
	xfer.bits_per_word = 8;	/* 一次传输8个bit */
	xfer.len = 2;			/* 传输长度为2个字节:寄存器地址+写入值 */

	tx[0] = 0x55;			/* 要写入的从机设备寄存器地址(举个例子,假设寄存器地址是一个字节) */
	tx[1] = 0xFF;			/* 要往该寄存器写入的数据 */
	spi_message_init(&msg);	/* 初始化spi_message */
	spi_message_add_tail(&xfer, &msg);	/* 将spi_transfer添加到spi_message */
	ret = spi_sync(dev->spi, &msg);		/* 执行同步数据传输 */
	if (ret)
		return ret;

	return cnt;
}


static ssize_t myspi_write_reg(struct spi_device *spi, unsigned char c)
{
	//struct myspi_dev *dev = spi_dev->private_data;
	struct spi_message msg = {0};
	struct spi_transfer xfer = {0};
	unsigned char tx;
	int ret;

	xfer.tx_buf = &tx;		/* 发送数据缓存区 */
	xfer.bits_per_word = 8;	/* 一次传输8个bit */
	xfer.len = 1;			/* 传输长度为2个字节:寄存器地址+写入值 */

	tx = c;			/* 要写入的从机设备寄存器地址(举个例子,假设寄存器地址是一个字节) */
	spi_message_init(&msg);	/* 初始化spi_message */
	spi_message_add_tail(&xfer, &msg);	/* 将spi_transfer添加到spi_message */
	//ret = spi_sync(dev->spi, &msg);		/* 执行同步数据传输 */
	ret = spi_sync(spi, &msg);		/* 执行同步数据传输 */
	if (ret)
		return ret;
	
	printk(KERN_ALERT "spi test\r\n");
		
	return 0;
}

/*
 * @description			: 关闭/释放设备
 * @param - filp		: 要关闭的设备文件(文件描述符)
 * @return				: 0 成功;其他 失败
 */
static int myspi_release(struct inode *inode, struct file *filp)
{
	return 0;
}

/*
 * file_operations结构体变量
 */
static const struct file_operations myspi_ops = {
	.owner = THIS_MODULE,
	.open = myspi_open,
	.read = myspi_read,
	.write = myspi_write,
	.release = myspi_release,
};

static int myspi_init(struct myspi_dev *dev)
{
	/* 对设备进行初始化操作 */
	/* 在这个函数中对SPI从机设备进行相关初始化操作 */
	/* ...... */
	return 0;
}

static int myspi_probe(struct spi_device *spi)
{
	int ret;

	/* 初始化虚拟设备 */
	myspi.spi = spi;
	ret = myspi_init(&myspi);
	if (ret)
		return ret;

	/* 申请设备号 */
	ret = alloc_chrdev_region(&myspi.devid, 0, 1, DEVICE_NAME);
	if (ret)
		return ret;

	/* 初始化字符设备cdev */
	myspi.cdev.owner = THIS_MODULE;
	cdev_init(&myspi.cdev, &myspi_ops);

	/* 添加cdev */
	ret = cdev_add(&myspi.cdev, myspi.devid, 1);
	if (ret)
		goto out1;

	/* 创建类class */
	myspi.class = class_create(THIS_MODULE, DEVICE_NAME);
	if (IS_ERR(myspi.class)) {
		ret = PTR_ERR(myspi.class);
		goto out2;
	}

	/* 创建设备 */
	myspi.device = device_create(myspi.class, &spi->dev,
				myspi.devid, NULL, DEVICE_NAME);
	if (IS_ERR(myspi.device)) {
		ret = PTR_ERR(myspi.device);
		goto out3;
	}

	spi_set_drvdata(spi, &myspi);	/* 将myspi添加到spi从机设备对象的私有数据区中 */

	/* 初始化lcd屏的dc rest管脚 */
    lcd_gpio_init(spi);
	
	/* 内部寄存器初始化 */
    lcd_reg_init(spi);

	
	
    printk(KERN_ALERT "spi_lcd_probe ok!\r\n");
	
	//myspi_write_reg(spi, 0x55);
	
	return 0;

out3:
	class_destroy(myspi.class);

out2:
	cdev_del(&myspi.cdev);

out1:
	unregister_chrdev_region(myspi.devid, 1);

	return ret;
}

static int myspi_remove(struct spi_device *spi)
{
	struct myspi_dev *dev = spi_get_drvdata(spi);

	/* 注销设备 */
	device_destroy(dev->class, dev->devid);

	/* 注销类 */
	class_destroy(dev->class);

	/* 删除cdev */
	cdev_del(&dev->cdev);

	/* 注销设备号 */
	unregister_chrdev_region(dev->devid, 1);

	return 0;
}

/* 匹配列表 */
static const struct of_device_id myspi_of_match[] = {
	{ .compatible = "sitronix,st7796s" },
	{ /* Sentinel */ }
};

/* SPI总线下的设备驱动结构体变量 */
static struct spi_driver myspi_driver = {
	.driver = {
		.name			= "spi_st7796s",
		.of_match_table	= myspi_of_match,
	},
	.probe		= myspi_probe,		// probe函数
	.remove		= myspi_remove,		// remove函数
};

module_spi_driver(myspi_driver);

MODULE_AUTHOR("WQH");
MODULE_DESCRIPTION("st7796s");
MODULE_LICENSE("GPL");

加载模块打印信息

测试结果

st7796u只有初始化不同,就贴出这一部分

static void lcd_reg_init(struct spi_device *spi)
{
    printk(KERN_ALERT "This is lcd reg init func!\r\n");

    gpio_set_value(myspi.rest_gpio, 0);
    mdelay(100);
    gpio_set_value(myspi.rest_gpio, 1);
    mdelay(50);
    /******************************/
	LCD_WriteReg(spi,0x11);     

	mdelay(120);                //Delay 120ms

	LCD_WriteReg(spi,0x36);     // Memory Data Access Control MY,MX~~
	LCD_WriteData(spi,0x48);   

	LCD_WriteReg(spi,0x3A);     
	LCD_WriteData(spi,0x55);   //LCD_WriteData(0x66);

	LCD_WriteReg(spi,0xF0);     // Command Set Control
	LCD_WriteData(spi,0xC3);   

	LCD_WriteReg(spi,0xF0);     
	LCD_WriteData(spi,0x96);   

	LCD_WriteReg(spi,0xB4);     
	LCD_WriteData(spi,0x01);   

	LCD_WriteReg(spi,0xB7);     
	LCD_WriteData(spi,0xC6);   


	LCD_WriteReg(spi,0xC0);     
	LCD_WriteData(spi,0x80);   
	LCD_WriteData(spi,0x45);   

	LCD_WriteReg(spi,0xC1);     
	LCD_WriteData(spi,0x13);   //18  //00

	LCD_WriteReg(spi,0xC2);     
	LCD_WriteData(spi,0xA7);   

	LCD_WriteReg(spi,0xC5);     
	LCD_WriteData(spi,0x0A);   

	LCD_WriteReg(spi,0xE8);     
	LCD_WriteData(spi,0x40);
	LCD_WriteData(spi,0x8A);
	LCD_WriteData(spi,0x00);
	LCD_WriteData(spi,0x00);
	LCD_WriteData(spi,0x29);
	LCD_WriteData(spi,0x19);
	LCD_WriteData(spi,0xA5);
	LCD_WriteData(spi,0x33);

	LCD_WriteReg(spi,0xE0);
	LCD_WriteData(spi,0xD0);
	LCD_WriteData(spi,0x08);
	LCD_WriteData(spi,0x0F);
	LCD_WriteData(spi,0x06);
	LCD_WriteData(spi,0x06);
	LCD_WriteData(spi,0x33);
	LCD_WriteData(spi,0x30);
	LCD_WriteData(spi,0x33);
	LCD_WriteData(spi,0x47);
	LCD_WriteData(spi,0x17);
	LCD_WriteData(spi,0x13);
	LCD_WriteData(spi,0x13);
	LCD_WriteData(spi,0x2B);
	LCD_WriteData(spi,0x31);

	LCD_WriteReg(spi,0xE1);
	LCD_WriteData(spi,0xD0);
	LCD_WriteData(spi,0x0A);
	LCD_WriteData(spi,0x11);
	LCD_WriteData(spi,0x0B);
	LCD_WriteData(spi,0x09);
	LCD_WriteData(spi,0x07);
	LCD_WriteData(spi,0x2F);
	LCD_WriteData(spi,0x33);
	LCD_WriteData(spi,0x47);
	LCD_WriteData(spi,0x38);
	LCD_WriteData(spi,0x15);
	LCD_WriteData(spi,0x16);
	LCD_WriteData(spi,0x2C);
	LCD_WriteData(spi,0x32);
	 

	LCD_WriteReg(spi,0xF0);     
	LCD_WriteData(spi,0x3C);   

	LCD_WriteReg(spi,0xF0);     
	LCD_WriteData(spi,0x69);   

	mdelay(120);                

	LCD_WriteReg(spi,0x21);     
	LCD_WriteReg(spi,0x29); 

	/* 横向显示 */
	LCD_WriteReg(spi,0x36);  
	LCD_WriteData(spi,0x48);	  
	
    printk(KERN_ALERT "LCD init ok!\r\n");
    LCD_Disp_Pic(spi);
}

测试结果

使用 framebuffer 程序如下

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/spi/spi.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/fb.h>
#include <linux/dma-mapping.h>
#include <linux/sched.h>
#include <linux/wait.h>
#include <linux/fs.h> 
#include <linux/module.h> 
#include <linux/uaccess.h> 
#include <asm/mach/map.h>

#define DEVICE_NAME		"spi_st7796u"	/* 名字 */

/*
 * 自定义结构体spi_st7796u_lcd_dev
 */
struct spi_st7796u_lcd_dev {
	dev_t  devid;					/* 设备号 */
	struct cdev cdev;				/* cdev结构体 */
	struct class *class;			/* 类 */
	struct device *device;			/* 设备 */
	void   *private_data;     
	
	int    dc_gpio;
    int    rest_gpio;
};

static struct spi_st7796u_lcd_dev spi_st7796u_lcd;	

typedef struct{
    struct spi_device *spi;     //记录fb_info对象对应的spi设备对象
    struct task_struct *thread; //记录线程对象的地址,此线程专用于把显存数据发送到屏的驱动ic
}lcd_data_t;

#define SPI_SPEED 25000000

#define RED  	0xf800
#define GREEN	0x07e0
#define BLUE 	0x001f
#define WHITE	0xffff
#define BLACK	0x0000
#define YELLOW  0xFFE0
#define GRAY0   0xEF7D   		//灰色0 3165 00110 001011 00101
#define GRAY1   0x8410      	//灰色1      00000 000000 00000
#define GRAY2   0x4208      	//灰色2  1111111111011111

#define LCD_W 480
#define LCD_H 320

#define USE_HORIZONTAL  	 0	//顺时针旋转屏幕角度0-3

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 fb_ops fops = {
    .owner = THIS_MODULE,
    .fb_setcolreg = tft_lcdfb_setcolreg,
    .fb_fillrect = cfb_fillrect,
    .fb_copyarea = cfb_copyarea,
    .fb_imageblit = cfb_imageblit,
};

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;
}

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)
	{
		printk(KERN_ALERT "%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);
	
	pseudo_palette[regno] = val;
	return 0;
}

/* 此函数在spi设备驱动的probe函数里被调用 */
struct fb_info * fb_init(struct spi_device *spi)
{
    struct fb_info *fbi;
    u8 *v_addr;
    dma_addr_t p_addr;
    lcd_data_t *ldata;

	spi_st7796u_lcd.device->coherent_dma_mask = ~0;
	v_addr = dma_alloc_coherent(spi_st7796u_lcd.device, LCD_W * LCD_H * 4, &p_addr, GFP_KERNEL);
    if(v_addr)
	{
		printk(KERN_ALERT "allocate CM at virtual address: 0x%p"
			"address: 0x%p size:%dKiB\r\n", v_addr,
			(void *)p_addr, LCD_W * LCD_H * 4 / SZ_1K);
	}
	else
	{
		printk(KERN_ALERT "no mem in CMA area\r\n");
	}
	fbi = framebuffer_alloc(sizeof(lcd_data_t), NULL);//额外分配lcd_data_t类型空间
    ldata = fbi->par;       //datal指针指向额外分配的空间
    ldata->spi = spi;

   	fbi->pseudo_palette = pseudo_palette;
	fbi->var.activate = FB_ACTIVATE_NOW;

    /* 设置fbi的变量 RGB这里需要注意长度和偏移*/
    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 = 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;

    /* 设置fbi的常量 */
    strcpy(fbi->fix.id, "spi_st7796u");
    fbi->fix.smem_start = p_addr; //显存的物理地址
    fbi->fix.smem_len = LCD_W * LCD_H * 4;
    fbi->fix.type = FB_TYPE_PACKED_PIXELS;
    fbi->fix.visual = FB_VISUAL_TRUECOLOR;
    fbi->fix.line_length = LCD_W * 4;

    /* 其他重要的成员 */
    fbi->fbops = &fops;
    fbi->screen_base = v_addr;//显存虚拟地址
    fbi->screen_size = LCD_W * LCD_H * 4;//显存大小

    spi_set_drvdata(spi, fbi);  //spi成员关联了fbi

    register_framebuffer(fbi);  //注册初始化完成的fbi
    ldata->thread = kthread_run(thread_func, fbi, spi->modalias);

    return fbi;
}

/* 此函数在spi设备驱动remove时被调用 */
void fb_del(struct spi_device *spi)
{
    struct fb_info *fbi = spi_get_drvdata(spi); //得到之前存在spi设备中的fbi
    lcd_data_t *ldata = fbi->par;

    kthread_stop(ldata->thread); //让刷图线程退出
    unregister_framebuffer(fbi);
    dma_free_coherent(NULL, fbi->screen_size, fbi->screen_base, fbi->fix.smem_start);//把物理地址和虚拟地址放进去
    framebuffer_release(fbi);
}

static int LCD_WriteReg(struct spi_device *spi, unsigned char c)
{
	struct spi_message msg = {0};
	struct spi_transfer xfer = {0};
	unsigned char tx;
	int ret;

	gpio_set_value(spi_st7796u_lcd.dc_gpio, 0);/* 拉低发命令 */

	xfer.speed_hz = SPI_SPEED,
	xfer.tx_buf = &tx;		/* 发送数据缓存区 */
	xfer.bits_per_word = 8;	/* 一次传输8个bit */
	xfer.len = 1;			/* 传输长度为2个字节:寄存器地址+写入值 */

	tx = c;			/* 要写入的从机设备寄存器地址(举个例子,假设寄存器地址是一个字节) */
	spi_message_init(&msg);	/* 初始化spi_message */
	spi_message_add_tail(&xfer, &msg);	/* 将spi_transfer添加到spi_message */
	ret = spi_sync(spi, &msg);		/* 执行同步数据传输 */
	if (ret)
		return ret;
		
	return 0;
}

static int LCD_WriteReg_len(struct spi_device *spi, u8 *buf, unsigned int len)
{
	int ret;

    struct spi_transfer *t;
	struct spi_message m;

	t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);	/* 申请内存 */
	
	gpio_set_value(spi_st7796u_lcd.dc_gpio, 0);/* 拉低发命令 */

	t->speed_hz = SPI_SPEED,
	t->tx_buf = buf;			/* 要发送的数据 */
	t->len = len;				/* 字节 */
	spi_message_init(&m);		/* 初始化spi_message */
	spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message队列 */
	ret = spi_sync(spi, &m);	/* 同步发送 */

	kfree(t);					/* 释放内存 */

	return ret;
}


static int LCD_WriteData(struct spi_device *spi, unsigned char c)
{
	struct spi_message msg = {0};
	struct spi_transfer xfer = {0};
	unsigned char tx;
	int ret;

    gpio_set_value(spi_st7796u_lcd.dc_gpio, 1);    /* 拉高发数据 */

	xfer.speed_hz = SPI_SPEED,
	xfer.tx_buf = &tx;		/* 发送数据缓存区 */
	xfer.bits_per_word = 8;	/* 一次传输8个bit */
	xfer.len = 1;			/* 传输长度为2个字节:寄存器地址+写入值 */

	tx = c;			/* 要写入的从机设备寄存器地址(举个例子,假设寄存器地址是一个字节) */
	spi_message_init(&msg);	/* 初始化spi_message */
	spi_message_add_tail(&xfer, &msg);	/* 将spi_transfer添加到spi_message */
	ret = spi_sync(spi, &msg);		/* 执行同步数据传输 */
	if (ret)
		return ret;
		
	return 0;
}

static int LCD_WriteData_16bit(struct spi_device *spi, unsigned short dat)
{
	int ret;
	struct spi_message msg = {0};
	struct spi_transfer xfer = {0};
	unsigned char tx = dat>>8;;

    gpio_set_value(spi_st7796u_lcd.dc_gpio, 1);    /* 拉高发数据 */

	xfer.speed_hz = SPI_SPEED,
	xfer.tx_buf = &tx;		/* 发送数据缓存区 */
	xfer.bits_per_word = 8;	/* 一次传输8个bit */
	xfer.len = 1;			/* 传输长度为2个字节:寄存器地址+写入值 */

    spi_message_init(&msg);   /* 初始化spi_message */
    spi_message_add_tail(&xfer, &msg);    /* 将spi_transfer添加到spi_message队列 */

    ret = spi_sync(spi, &msg);        /* 使用同步发送 */

    tx = dat&0xFF;
    xfer.tx_buf = &tx;
    ret = spi_sync(spi, &msg);        /* 使用同步发送 */
	if (ret)
		return ret;
	
    return 0;
}

static int LCD_WriteData_len(struct spi_device *spi, u8 *buf, unsigned int len)
{
	int ret;

    struct spi_transfer *t;
	struct spi_message m;

	t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);	/* 申请内存 */
	
    gpio_set_value(spi_st7796u_lcd.dc_gpio, 1);    /* 拉高发数据 */

	t->speed_hz = SPI_SPEED,
	t->tx_buf = buf;			/* 要发送的数据 */
	t->len = len;				/* 字节 */
	spi_message_init(&m);		/* 初始化spi_message */
	spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message队列 */
	ret = spi_sync(spi, &m);	/* 同步发送 */

	kfree(t);					/* 释放内存 */

	return ret;
}


static void LCD_Set_Window(struct spi_device *spi,u16 xstart,u16 ystart,u16 xend,u16 yend)
{
    LCD_WriteReg(spi, 0x2A);
	LCD_WriteData_16bit(spi, xstart);
	LCD_WriteData_16bit(spi, xend);

    LCD_WriteReg(spi, 0x2B); 
    LCD_WriteData_16bit(spi, ystart);
    LCD_WriteData_16bit(spi, yend);  

    LCD_WriteReg(spi, 0x2C); //开始写入GRAM
}

/* framebuffer线程刷屏函数 fbi显存的数据给spi发送*/
void show_fb(struct fb_info *fbi, struct spi_device *spi)
{
	int x, y, i = 0;
    u32 k;
    u32 *p = (u32 *)(fbi->screen_base);
    u16 c;
    u8 *pp;

	/* 申请内存 */
	u8 *lcd_buffer = kzalloc(LCD_W * LCD_H * sizeof(u16), GFP_KERNEL);

    LCD_Set_Window(spi, 0,0,LCD_W-1,LCD_H-1);
    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; //红色

			lcd_buffer[i++] = (c>>8)&0xff;
			lcd_buffer[i++] = c&0xff;
        }
    }
	LCD_WriteData_len(spi, lcd_buffer, LCD_W * LCD_H * sizeof(u16));

	/* 释放内存 */
    kfree(lcd_buffer);
}

void LCD_Disp_Pic(struct spi_device *spi)
{
	/* 申请内存 */
	u8 *lcd_buffer = kzalloc(LCD_W * LCD_H * sizeof(u16), GFP_KERNEL);

    /* 设置屏幕和显示部分 */
    LCD_Set_Window(spi, 0, 0, LCD_W-1, LCD_H-1);
	/* 对内存赋值 */
	memset(lcd_buffer, BLUE, LCD_W * LCD_H * sizeof(u16));
	/* 连续写 */
	LCD_WriteData_len(spi, lcd_buffer, LCD_W * LCD_H * sizeof(u16));

	mdelay(500);

	/* 释放内存 */
    kfree(lcd_buffer);

    printk(KERN_ALERT "LCD display picture ok!\r\n");
}

static void lcd_reg_init(struct spi_device *spi)
{
    printk(KERN_ALERT "This is lcd reg init func!\r\n");

    gpio_set_value(spi_st7796u_lcd.rest_gpio, 0);
    mdelay(100);
    gpio_set_value(spi_st7796u_lcd.rest_gpio, 1);
    mdelay(50);
    /******************************/
	LCD_WriteReg(spi,0x11);     

	mdelay(120);                //Delay 120ms

	LCD_WriteReg(spi,0x36);     // Memory Data Access Control MY,MX~~
	LCD_WriteData(spi,0x48);   

	LCD_WriteReg(spi,0x3A);     
	LCD_WriteData(spi,0x55);   //LCD_WriteData(0x66);

	LCD_WriteReg(spi,0xF0);     // Command Set Control
	LCD_WriteData(spi,0xC3);   

	LCD_WriteReg(spi,0xF0);     
	LCD_WriteData(spi,0x96);   

	LCD_WriteReg(spi,0xB4);     
	LCD_WriteData(spi,0x01);   

	LCD_WriteReg(spi,0xB7);     
	LCD_WriteData(spi,0xC6);   


	LCD_WriteReg(spi,0xC0);     
	LCD_WriteData(spi,0x80);   
	LCD_WriteData(spi,0x45);   

	LCD_WriteReg(spi,0xC1);     
	LCD_WriteData(spi,0x13);   //18  //00

	LCD_WriteReg(spi,0xC2);     
	LCD_WriteData(spi,0xA7);   

	LCD_WriteReg(spi,0xC5);     
	LCD_WriteData(spi,0x0A);   

	LCD_WriteReg(spi,0xE8);     
	LCD_WriteData(spi,0x40);
	LCD_WriteData(spi,0x8A);
	LCD_WriteData(spi,0x00);
	LCD_WriteData(spi,0x00);
	LCD_WriteData(spi,0x29);
	LCD_WriteData(spi,0x19);
	LCD_WriteData(spi,0xA5);
	LCD_WriteData(spi,0x33);

	LCD_WriteReg(spi,0xE0);
	LCD_WriteData(spi,0xD0);
	LCD_WriteData(spi,0x08);
	LCD_WriteData(spi,0x0F);
	LCD_WriteData(spi,0x06);
	LCD_WriteData(spi,0x06);
	LCD_WriteData(spi,0x33);
	LCD_WriteData(spi,0x30);
	LCD_WriteData(spi,0x33);
	LCD_WriteData(spi,0x47);
	LCD_WriteData(spi,0x17);
	LCD_WriteData(spi,0x13);
	LCD_WriteData(spi,0x13);
	LCD_WriteData(spi,0x2B);
	LCD_WriteData(spi,0x31);

	LCD_WriteReg(spi,0xE1);
	LCD_WriteData(spi,0xD0);
	LCD_WriteData(spi,0x0A);
	LCD_WriteData(spi,0x11);
	LCD_WriteData(spi,0x0B);
	LCD_WriteData(spi,0x09);
	LCD_WriteData(spi,0x07);
	LCD_WriteData(spi,0x2F);
	LCD_WriteData(spi,0x33);
	LCD_WriteData(spi,0x47);
	LCD_WriteData(spi,0x38);
	LCD_WriteData(spi,0x15);
	LCD_WriteData(spi,0x16);
	LCD_WriteData(spi,0x2C);
	LCD_WriteData(spi,0x32);
	 

	LCD_WriteReg(spi,0xF0);     
	LCD_WriteData(spi,0x3C);   

	LCD_WriteReg(spi,0xF0);     
	LCD_WriteData(spi,0x69);   

	mdelay(120);                

	LCD_WriteReg(spi,0x21);     
	LCD_WriteReg(spi,0x29); 

	/* 横向显示 */
	LCD_WriteReg(spi,0x36);  
	LCD_WriteData(spi,0x28);	  
	
    printk(KERN_ALERT "LCD init ok!\r\n");

    LCD_Disp_Pic(spi);
}

static int lcd_gpio_init(struct spi_device *spi)
{
    int ret;
    int flag1,flag2;

    /* 获取GPIO */
    spi_st7796u_lcd.dc_gpio = of_get_named_gpio_flags(spi->dev.of_node, "dc-gpios", 0, (enum of_gpio_flags *)&flag1);
    ret = gpio_is_valid(spi_st7796u_lcd.dc_gpio);
    if(ret < 0)
    {
        printk(KERN_ALERT "get dc gpio failed !\r\n");
        return -EINVAL;
    }
    ret = gpio_request(spi_st7796u_lcd.dc_gpio,"dc-gpios");
    if(ret < 0)
    {
        printk(KERN_ALERT "request gpio dc failed !\r\n");
        gpio_free(spi_st7796u_lcd.dc_gpio);
    }
    printk(KERN_ALERT "dc gpio num is %d !\r\n",spi_st7796u_lcd.dc_gpio);

    spi_st7796u_lcd.rest_gpio = of_get_named_gpio_flags(spi->dev.of_node, "reset-gpios", 0, (enum of_gpio_flags *)&flag2);
    ret = gpio_is_valid(spi_st7796u_lcd.rest_gpio);
    if(ret < 0)
    {
        printk(KERN_ALERT "get rest gpio failed !\r\n");
        return -EINVAL;
    }
    ret = gpio_request(spi_st7796u_lcd.rest_gpio,"reset-gpios");
    if(ret < 0)
    {
        printk(KERN_ALERT "request gpio rest failed !\r\n");
        gpio_free(spi_st7796u_lcd.rest_gpio);
    }
    printk(KERN_ALERT "rest gpio num is %d !\r\n",spi_st7796u_lcd.rest_gpio);

    /* 设置方向 */
	gpio_direction_output(spi_st7796u_lcd.dc_gpio,0);
	gpio_direction_output(spi_st7796u_lcd.rest_gpio,0);

    return ret;
}

/*
 * @description			: 打开设备
 * @param – inode		: 传递给驱动的inode
 * @param - filp		: 设备文件,file结构体有个叫做private_data的成员变量
 * 						  一般在open的时候将private_data指向设备结构体。
 * @return				: 0 成功;其他 失败
 */
static int spi_st7796u_lcd_open(struct inode *inode, struct file *filp)
{
	filp->private_data = &spi_st7796u_lcd;
	return 0;
}

/*
 * @description			: 从设备读取数据 
 * @param – filp		: 要打开的设备文件(文件描述符)
 * @param - buf			: 返回给用户空间的数据缓冲区
 * @param - cnt			: 要读取的数据长度
 * @param – off			: 相对于文件首地址的偏移
 * @return				: 读取的字节数,如果为负值,表示读取失败
 */
static ssize_t spi_st7796u_lcd_read(struct file *filp, char __user *buf,
			size_t cnt, loff_t *off)
{
	return 0;
}

/*
 * @description			: 向设备写数据 
 * @param – filp		: 设备文件,表示打开的文件描述符
 * @param - buf			: 要写给设备写入的数据
 * @param - cnt			: 要写入的数据长度
 * @param - offt		: 相对于文件首地址的偏移
 * @return				: 写入的字节数,如果为负值,表示写入失败
 */
static ssize_t spi_st7796u_lcd_write(struct file *filp, const char __user *buf,
			size_t cnt, loff_t *offt)
{
	return 0;
}


static ssize_t spi_st7796u_lcd_write_reg(struct spi_device *spi, unsigned char c)
{
	//struct spi_st7796u_lcd_dev *dev = spi_dev->private_data;
	struct spi_message msg = {0};
	struct spi_transfer xfer = {0};
	unsigned char tx;
	int ret;

	xfer.tx_buf = &tx;		/* 发送数据缓存区 */
	xfer.bits_per_word = 8;	/* 一次传输8个bit */
	xfer.len = 1;			/* 传输长度为2个字节:寄存器地址+写入值 */

	tx = c;			/* 要写入的从机设备寄存器地址(举个例子,假设寄存器地址是一个字节) */
	spi_message_init(&msg);	/* 初始化spi_message */
	spi_message_add_tail(&xfer, &msg);	/* 将spi_transfer添加到spi_message */
	//ret = spi_sync(dev->spi, &msg);		/* 执行同步数据传输 */
	ret = spi_sync(spi, &msg);		/* 执行同步数据传输 */
	if (ret)
		return ret;
	
	printk(KERN_ALERT "spi test\r\n");
		
	return 0;
}

/*
 * @description			: 关闭/释放设备
 * @param - filp		: 要关闭的设备文件(文件描述符)
 * @return				: 0 成功;其他 失败
 */
static int spi_st7796u_lcd_release(struct inode *inode, struct file *filp)
{
	return 0;
}

/*
 * file_operations结构体变量
 */
static const struct file_operations spi_st7796u_lcd_ops = {
	.owner = THIS_MODULE,
	.open = spi_st7796u_lcd_open,
	.read = spi_st7796u_lcd_read,
	.write = spi_st7796u_lcd_write,
	.release = spi_st7796u_lcd_release,
};

static int spi_st7796u_lcd_init(struct spi_st7796u_lcd_dev *dev)
{
	/* 对设备进行初始化操作 */
	/* 在这个函数中对SPI从机设备进行相关初始化操作 */
	/* ...... */
	return 0;
}

static int spi_st7796u_lcd_probe(struct spi_device *spi)
{
	int ret;
	struct fb_info *fbi;

    printk(KERN_ALERT "spi_lcd_probe ok!\r\n");

	/* 初始化虚拟设备 */
	ret = spi_st7796u_lcd_init(&spi_st7796u_lcd);
	if (ret)
		return ret;

	/* 申请设备号 */
	ret = alloc_chrdev_region(&spi_st7796u_lcd.devid, 0, 1, DEVICE_NAME);
	if (ret)
		return ret;

	/* 初始化字符设备cdev */
	spi_st7796u_lcd.cdev.owner = THIS_MODULE;
	cdev_init(&spi_st7796u_lcd.cdev, &spi_st7796u_lcd_ops);

	/* 添加cdev */
	ret = cdev_add(&spi_st7796u_lcd.cdev, spi_st7796u_lcd.devid, 1);
	if (ret)
		goto out1;

	/* 创建类class */
	spi_st7796u_lcd.class = class_create(THIS_MODULE, DEVICE_NAME);
	if (IS_ERR(spi_st7796u_lcd.class)) {
		ret = PTR_ERR(spi_st7796u_lcd.class);
		goto out2;
	}

	/* 创建设备 */
	spi_st7796u_lcd.device = device_create(spi_st7796u_lcd.class, &spi->dev,
				spi_st7796u_lcd.devid, NULL, DEVICE_NAME);
	if (IS_ERR(spi_st7796u_lcd.device)) {
		ret = PTR_ERR(spi_st7796u_lcd.device);
		goto out3;
	}

    /*初始化spi_device */
    spi_st7796u_lcd.private_data = spi;

	/* 初始化lcd屏的dc rest管脚 */
    lcd_gpio_init(spi);
	
	/* 内部寄存器初始化 */
    lcd_reg_init(spi);

	/* 初始化fb设备 */
    fbi = fb_init(spi);
	
	return 0;

out3:
	class_destroy(spi_st7796u_lcd.class);

out2:
	cdev_del(&spi_st7796u_lcd.cdev);

out1:
	unregister_chrdev_region(spi_st7796u_lcd.devid, 1);

	return ret;
}

static int spi_st7796u_lcd_remove(struct spi_device *spi)
{
	struct spi_st7796u_lcd_dev *dev = spi_get_drvdata(spi);

    /* fb设备回收 */
    fb_del(spi);

	/* 注销设备 */
	device_destroy(dev->class, dev->devid);

	/* 注销类 */
	class_destroy(dev->class);

	/* 删除cdev */
	cdev_del(&dev->cdev);

	/* 注销设备号 */
	unregister_chrdev_region(dev->devid, 1);

	return 0;
}

/* 匹配列表 */
static const struct of_device_id spi_st7796u_lcd_of_match[] = {
	{ .compatible = "sitronix,st7796" },
	{ /* Sentinel */ }
};

/* SPI总线下的设备驱动结构体变量 */
static struct spi_driver spi_st7796u_lcd_driver = {
	.driver = {
		.name			= "st7796u",
		.of_match_table	= spi_st7796u_lcd_of_match,
	},
	.probe		= spi_st7796u_lcd_probe,		// probe函数
	.remove		= spi_st7796u_lcd_remove,		// remove函数
};

module_spi_driver(spi_st7796u_lcd_driver);

MODULE_AUTHOR("WQH");
MODULE_DESCRIPTION("st7796u_spi_lcd");
MODULE_LICENSE("GPL");

测试结果

  • 17
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值