目录
参考博客
- 嵌入式Linux驱动笔记(二十四)------framebuffer之使用spi-tft屏幕(上)
- 68 linux framebuffer设备驱动之spi lcd屏驱动
- 65 linux spi设备驱动之spi LCD屏驱动
- 正点原子Linux开发板 spi内核驱动 0.96寸ips屏教程
- imx6ull Linux 开发板spi驱动st7789
- linux spi
- imx6ull Linux spi + frambuffer 实现st7789运行显示QT程序 且可实现大小屏双屏同显
一、配置设备树
//功能引脚节点
pinctrl_ipsRes: ipsRes { //屏幕复位u引脚
fsl,pins = <
MX6UL_PAD_GPIO1_IO01__GPIO1_IO01 0x10B0 /* LED0 */
>;
};
pinctrl_ipsDc: ipsDc { //屏幕dc(data or command)u引脚
fsl,pins = <
MX6UL_PAD_GPIO1_IO04__GPIO1_IO04 0x10B0
>;
};
..............//附加节点
&ecspi3 {
fsl,spi-num-chipselects = <1>;
cs-gpio = <&gpio1 20 GPIO_ACTIVE_LOW>; /* cant't use cs-gpios! */
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_ecspi3>;
status = "okay";
spidev: ipsTft@0 {
compatible = "alientek,ipsTft";
spi-max-frequency = <1000000000>;
reg = <0>;
};
};
二、编写驱动程序
1.驱动程序
- (1)0.96寸 st7735驱动芯片
- (2)1.3寸 st7735驱动芯片
(1)0.96寸显示屏驱动(spi_driver.c)
#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/i2c.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/spi/spi.h>
#define ipsTft_CNT 1
#define ipsTft_NAME "ipsTft"
#define LCD_W 240
#define LCD_H 240
#define X_MAX_PIXEL 160
#define Y_MAX_PIXEL 80
#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
u8 buf[9] = {
RED, GREEN, BLUE,WHITE, BLACK, YELLOW,GRAY0 ,GRAY1 ,GRAY2
};
struct ipsTft_dev {
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
struct device_node *nd; /* 设备节点 */
int major; /* 主设备号 */
void *private_data; /* 私有数据 */
int dc_gpio; /* 片选所使用的GPIO编号 */
int res_gpio; /*ips屏幕复位引脚*/
int cs_gpio; /*磁力计的cs*/
};
static struct ipsTft_dev ipsTftdev;
void ipsTft_reginit(struct ipsTft_dev *dev);
struct spi_lcd_cmd{
u8 reg_addr; // command
u8 len; //需要从spi_lcd_datas数组里发出数据字节数
int delay_ms; //此命令发送数据完成后,需延时多久
}cmds[] = {
{0x11, 0, 120},
{0x21, 0, 0},
{0x21, 0, 0},
{0xB1, 3, 0},
{0xB2, 3, 0},
{0xB3, 6, 0},
{0xB4, 1, 0},
{0xC0, 3, 0},
{0xC1, 1, 0},
{0xC2, 2, 0},
{0xC3, 2, 0},
{0xC4, 2, 0},
{0xC5, 1, 0},
{0xE0, 16, 0},
{0xE1, 16, 0},
{0x3A, 1, 0},
{0x36, 1, 0},
{0x29, 0, 0},
};
u8 spi_lcd_datas[] = {
0x05,0x3A,0x3A,
0x05,0x3A,0x3A,
0x05,0x3A,0x3A,0x05,0x3A,0x3A,
0x03,
0x62,0x02,0x04,
0xC0,
0x0D,0x00,
0x8D,0x6A,
0x8D,0xEE,
0x0E,
0x10,0x0E,0x02,0x03,0x0E,0x07,0x02,0x07,0x0A,0x12,0x27,0x37,0x00,0x0D,0x0E,0x10,
0x10,0x0E,0x03,0x03,0x0F,0x06,0x02,0x08,0x0A,0x13,0x26,0x36,0x00,0x0D,0x0E,0x10,
0x05,
0xA8,
};
/*
* @description : 向ipsTft多个寄存器写入数据
* @param - dev: ipsTft设备
* @param - reg: 要写入的寄存器首地址
* @param - val: 要写入的数据缓冲区
* @param - len: 要写入的数据长度
* @return : 操作结果
*/
static s32 ipsTft_write_regs(struct ipsTft_dev *dev,u8 *buf, u8 len)
{
int ret;
struct spi_message m;
struct spi_transfer *t;
struct spi_device *spi = (struct spi_device *)dev->private_data;
t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL); /* 申请内存 */
/* 发送要写入的数据 */
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;
}
/*
* @description : 向ipsTft指定寄存器写入指定的值,写一个寄存器
* @param - dev: ipsTft设备
* @param - reg: 要写的寄存器
* @param - data: 要写入的值
* @return : 无
*/
static void ipsTft_write_onereg(struct ipsTft_dev *dev, u8 buf)
{
ipsTft_write_regs(dev,&buf, 1);
//spi_write(dev,&buf, 1);
}
/*
funciton: 写一个命令
*/
void write_command(struct ipsTft_dev *dev, u8 cmd)
{
// dc , command:0
gpio_set_value(dev->dc_gpio, 0);
ipsTft_write_onereg(dev,cmd);
}
/*
funciton: 写一个数据
*/
void write_data(struct ipsTft_dev *dev, u8 data)
{
gpio_set_value(dev->dc_gpio, 1);
ipsTft_write_onereg(dev,data);
}
/*
funciton: 写一个数据
*/
static void write_datas(struct ipsTft_dev *dev, int data,int len)
{
gpio_set_value(dev->dc_gpio, 1);
ipsTft_write_regs(dev,(u8 *)&data,len);
}
/*
* @description : 打开设备
* @param - inode : 传递给驱动的inode
* @param - filp : 设备文件,file结构体有个叫做pr似有ate_data的成员变量
* 一般在open的时候将private_data似有向设备结构体。
* @return : 0 成功;其他 失败
*/
static int ipsTft_open(struct inode *inode, struct file *filp)
{
filp->private_data = &ipsTftdev; /* 设置私有数据 */
ipsTft_reginit(&ipsTftdev);
return 0;
}
/*
* @description : 关闭/释放设备
* @param - filp : 要关闭的设备文件(文件描述符)
* @return : 0 成功;其他 失败
*/
static int ipsTft_release(struct inode *inode, struct file *filp)
{
return 0;
}
/* ipsTft操作函数 */
static const struct file_operations ipsTft_ops = {
.owner = THIS_MODULE,
.open = ipsTft_open,
.release = ipsTft_release,
};
void Address_set(struct ipsTft_dev *dev,unsigned int x1,unsigned int y1,unsigned int x2,unsigned int y2)
{
write_command(dev,0x2a);
write_data(dev,x1>>8);
write_data(dev,x1);
write_data(dev,x2>>8);
write_data(dev,x2);
write_command(dev,0x2b);
write_data(dev,y1>>8);
write_data(dev,y1);
write_data(dev,y2>>8);
write_data(dev,y2);
write_command(dev,0x2C);
}
/*
刷屏函数
*/
void LCD_Clear(struct ipsTft_dev *dev)
{
u16 i,j;
Address_set(dev,0,0,LCD_W-1,LCD_H-1);
for(i=0;i<LCD_W;i++)
{
for (j=0;j<LCD_H;j++)
{
//write_datas(dev,0xF800,2); //全红
write_data(dev,0xF800>>8);
write_data(dev,0xF800&0xFF);
}
}
}
/*************************************************
0.96'
*************************************************/
void Lcd_SetRegion(struct ipsTft_dev *dev,u16 x_start,u16 y_start,u16 x_end,u16 y_end)
{
write_command(dev,0x2a);
write_data(dev,0x00);
write_data(dev,x_start+1);
write_data(dev,0x00);
write_data(dev,x_end+1);
write_command(dev,0x2b);
write_data(dev,0x00);
write_data(dev,y_start+0x1A);
write_data(dev,0x00);
write_data(dev,y_end+0x1A);
write_command(dev,0x2c);
}
/*************************************************
0.96'显示屏填充函数
*************************************************/
void Lcd_Clear_small(struct ipsTft_dev *dev,u16 Color)
{
unsigned int i,m;
Lcd_SetRegion(dev,0,0,X_MAX_PIXEL-1,Y_MAX_PIXEL-1);
write_command(dev,0x2C);
for(i=0;i<X_MAX_PIXEL;i++)
for(m=0;m<Y_MAX_PIXEL;m++)
{
//LCD_WriteData_16Bit(Color);
write_data(dev,Color>>8);
write_data(dev,Color&0xFF);
}
}
/*
* ipsTft内部寄存器初始化函数
* @param : 无
* @return : 无
*/
void ipsTft_reginit(struct ipsTft_dev *dev)
{
int i, j, n;
gpio_set_value(ipsTftdev.res_gpio, 0);
mdelay(500);
gpio_set_value(ipsTftdev.res_gpio, 1);
mdelay(500);
n = 0; // n用于记录数据数组spi_lcd_datas的位置
//发命令,并发出命令所需的数据
for (i = 0; i < ARRAY_SIZE(cmds); i++) //命令
{
write_command(dev, cmds[i].reg_addr);
for (j = 0; j < cmds[i].len; j++) //发出命令后,需要发出的数据
if(cmds[i].len!=0)
write_data(dev, spi_lcd_datas[n++]);
printk("the n is %d\n",n);
if (cmds[i].delay_ms) //如有延时则延时
mdelay(cmds[i].delay_ms);
}
n=0;
while(n<9){
Lcd_Clear_small(dev,*(buf+n));
n++;
mdelay(1000);
}
printk("ips init finish!\n");
}
/*
* @description : spi驱动的probe函数,当驱动与
* 设备匹配以后此函数就会执行
* @param - client : spi设备
* @param - id : spi设备ID
*
*/
static int ipsTft_probe(struct spi_device *spi)
{
int ret = 0;
/* 1、构建设备号 */
if (ipsTftdev.major) {
ipsTftdev.devid = MKDEV(ipsTftdev.major, 0);
register_chrdev_region(ipsTftdev.devid, ipsTft_CNT, ipsTft_NAME);
} else {
alloc_chrdev_region(&ipsTftdev.devid, 0, ipsTft_CNT, ipsTft_NAME);
ipsTftdev.major = MAJOR(ipsTftdev.devid);
}
/* 2、注册设备 */
cdev_init(&ipsTftdev.cdev, &ipsTft_ops);
cdev_add(&ipsTftdev.cdev, ipsTftdev.devid, ipsTft_CNT);
/* 3、创建类 */
ipsTftdev.class = class_create(THIS_MODULE, ipsTft_NAME);
if (IS_ERR(ipsTftdev.class)) {
return PTR_ERR(ipsTftdev.class);
}
/* 4、创建设备 */
ipsTftdev.device = device_create(ipsTftdev.class, NULL, ipsTftdev.devid, NULL, ipsTft_NAME);
if (IS_ERR(ipsTftdev.device)) {
return PTR_ERR(ipsTftdev.device);
}
/* 获取设备树中cs片选信号 */
ipsTftdev.nd = of_find_node_by_path("/soc/aips-bus@02000000/spba-bus@02000000/ecspi@02010000");
if(ipsTftdev.nd == NULL) {
printk("ecspi3 node not find!\r\n");
return -EINVAL;
}
/* 2、 获取设备树中的gpio属性,得到BEEP所使用的BEEP编号 */
ipsTftdev.cs_gpio = of_get_named_gpio(ipsTftdev.nd, "cs-gpio", 0);
if(ipsTftdev.cs_gpio < 0) {
printk("can't get cs-gpio");
return -EINVAL;
}
ipsTftdev.nd = of_find_node_by_path("/ipsRes");
if(ipsTftdev.nd == NULL) {
printk("res-gpio node not find!\r\n");
return -EINVAL;
}
ipsTftdev.res_gpio = of_get_named_gpio(ipsTftdev.nd, "res-gpio", 0);
if(ipsTftdev.res_gpio < 0) {
printk("can't get res-gpio");
return -EINVAL;
}
ipsTftdev.nd = of_find_node_by_path("/ipsDc");
if(ipsTftdev.nd == NULL) {
printk("ipsDcgpio node not find!\r\n");
return -EINVAL;
}
ipsTftdev.dc_gpio = of_get_named_gpio(ipsTftdev.nd, "dc-gpio", 0);
if(ipsTftdev.dc_gpio < 0) {
printk("can't get ipsDc-gpio");
return -EINVAL;
}
/* 3、设置GPIO1_IO20为输出,并且输出高电平 */
ret = gpio_direction_output(ipsTftdev.cs_gpio, 1);
if(ret < 0) {
printk("can't set cs gpio!\r\n");
}
ret = gpio_direction_output(ipsTftdev.res_gpio, 1);
if(ret < 0) {
printk("can't set res gpio!\r\n");
}
ret = gpio_direction_output(ipsTftdev.dc_gpio, 1);
if(ret < 0) {
printk("can't set dc gpio!\r\n");
}
/*初始化spi_device */
spi->mode = SPI_MODE_0; /*MODE0,CPOL=0,CPHA=0*/
spi_setup(spi);
ipsTftdev.private_data = spi; /* 设置私有数据 */
/* 初始化ipsTft内部寄存器 */
ipsTft_reginit(&ipsTftdev);
return 0;
}
/*
* @description : spi驱动的remove函数,移除spi驱动的时候此函数会执行
* @param - client : spi设备
* @return : 0,成功;其他负值,失败
*/
static int ipsTft_remove(struct spi_device *spi)
{
/* 删除设备 */
cdev_del(&ipsTftdev.cdev);
unregister_chrdev_region(ipsTftdev.devid, ipsTft_CNT);
/* 注销掉类和设备 */
device_destroy(ipsTftdev.class, ipsTftdev.devid);
class_destroy(ipsTftdev.class);
return 0;
}
/* 传统匹配方式ID列表 */
static const struct spi_device_id ipsTft_id[] = {
{"alientek,ipsTft", 0},
{}
};
/* 设备树匹配列表 */
static const struct of_device_id ipsTft_of_match[] = {
{ .compatible = "alientek,ipsTft" },
{ /* Sentinel */ }
};
/* SPI驱动结构体 */
static struct spi_driver ipsTft_driver = {
.probe = ipsTft_probe,
.remove = ipsTft_remove,
.driver = {
.owner = THIS_MODULE,
.name = "ipsTft",
.of_match_table = ipsTft_of_match,
},
.id_table = ipsTft_id,
};
/*
* @description : 驱动入口函数
* @param : 无
* @return : 无
*/
static int __init ipsTft_init(void)
{
return spi_register_driver(&ipsTft_driver);
}
/*
* @description : 驱动出口函数
* @param : 无
* @return : 无
*/
static void __exit ipsTft_exit(void)
{
spi_unregister_driver(&ipsTft_driver);
}
module_init(ipsTft_init);
module_exit(ipsTft_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("raymond");
(2)1.3寸显示屏驱动 240*240(spi_driver.c)
需注意点:
spi->mode = SPI_MODE_2; /MODE2,CPOL=1,CPHA=0 //出问题的地方!!!/
spi模式设置为模式二
#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/i2c.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/spi/spi.h>
#define ipsTft_CNT 1
#define ipsTft_NAME "ipsTft"
#define LCD_W 240
#define LCD_H 240
#define WHITE 0xFFFF
#define BLACK 0x0000
#define BLUE 0x001F
#define BRED 0XF81F
#define GRED 0XFFE0
#define GBLUE 0X07FF
#define RED 0xF800
#define MAGENTA 0xF81F
#define GREEN 0x07E0
#define CYAN 0x7FFF
#define YELLOW 0xFFE0
#define BROWN 0XBC40 //×ØÉ«
#define BRRED 0XFC07 //×غìÉ«
#define GRAY 0X8430 //»ÒÉ«
u8 buf[9] = {
RED, GREEN, BLUE,WHITE, BLACK, YELLOW,GRAY ,BRRED ,CYAN
};
struct ipsTft_dev {
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
struct device_node *nd; /* 设备节点 */
int major; /* 主设备号 */
void *private_data; /* 私有数据 */
int dc_gpio; /* 片选所使用的GPIO编号 */
int res_gpio; /*ips屏幕复位引脚*/
int cs_gpio; /*磁力计的cs*/
};
static struct ipsTft_dev ipsTftdev;
void ipsTft_reginit(struct ipsTft_dev *dev);
//1.3寸屏幕
struct spi_lcd_cmd{
u8 reg_addr; // command
u8 len; //需要从spi_lcd_datas数组里发出数据字节数
int delay_ms; //此命令发送数据完成后,需延时多久
} cmds[] = {
{0x36, 1, 30},
{0x3A, 1, 30},
{0xB2, 5, 30},
{0xB7, 1, 30},
{0xBB, 1, 30},
{0xC0, 1, 30},
{0xC2, 1, 30},
{0xC3, 1, 30},
{0xC4, 1, 30},
{0xC6, 1, 30},
{0xD0, 2, 30},
{0xE0, 14, 30},
{0xE1, 14, 30},
{0x21, 0, 30},
{0x11, 0, 120},
{0x29, 0, 30},
};
u8 spi_lcd_datas[] = {
0x00,
0x05,
0x0c,0x0c,0x00,0x33,0x33,
0x35,
0x19,
0x2c,
0x01,
0x12,
0x20,
0x0F,
0xA4,0xA1,
0xD0,0x04,0x0D,0x11,0x13,0x2B,0x3F,0x54,0x4C,0x18,0x0D,0x0B,0x1F,0x23,
0xD0,0x04,0x0C,0x11,0x13,0x2C,0x3F,0x44,0x51,0x2F,0x1F,0x1F,0x20,0x23
};
/*
* @description : 向ipsTft多个寄存器写入数据
* @param - dev: ipsTft设备
* @param - reg: 要写入的寄存器首地址
* @param - val: 要写入的数据缓冲区
* @param - len: 要写入的数据长度
* @return : 操作结果
*/
static s32 ipsTft_write_regs(struct ipsTft_dev *dev,u8 *buf, u8 len)
{
int ret;
struct spi_message m;
struct spi_transfer *t;
struct spi_device *spi = (struct spi_device *)dev->private_data;
t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL); /* 申请内存 */
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;
}
/*
* @description : 向ipsTft指定寄存器写入指定的值,写一个寄存器
* @param - dev: ipsTft设备
* @param - reg: 要写的寄存器
* @param - data: 要写入的值
* @return : 无
*/
static void ipsTft_write_onereg(struct ipsTft_dev *dev, u8 buf)
{
ipsTft_write_regs(dev,&buf, 1);
//spi_write(dev,&buf, 1);
}
/*
funciton: 写一个命令
*/
void write_command(struct ipsTft_dev *dev, u8 cmd)
{
// dc , command:0
gpio_set_value(dev->dc_gpio, 0);
ipsTft_write_onereg(dev,cmd);
}
/*
funciton: 写一个数据
*/
void write_data(struct ipsTft_dev *dev, u8 data)
{
gpio_set_value(dev->dc_gpio, 1);
ipsTft_write_onereg(dev,data);
}
/*
funciton: 写一个数据
*/
static void write_datas(struct ipsTft_dev *dev, int data,int len)
{
gpio_set_value(dev->dc_gpio, 1);
ipsTft_write_regs(dev,(u8 *)&data,len);
}
/*
* @description : 打开设备
* @param - inode : 传递给驱动的inode
* @param - filp : 设备文件,file结构体有个叫做pr似有ate_data的成员变量
* 一般在open的时候将private_data似有向设备结构体。
* @return : 0 成功;其他 失败
*/
static int ipsTft_open(struct inode *inode, struct file *filp)
{
filp->private_data = &ipsTftdev; /* 设置私有数据 */
return 0;
}
/*
* @description : 关闭/释放设备
* @param - filp : 要关闭的设备文件(文件描述符)
* @return : 0 成功;其他 失败
*/
static int ipsTft_release(struct inode *inode, struct file *filp)
{
return 0;
}
/* ipsTft操作函数 */
static const struct file_operations ipsTft_ops = {
.owner = THIS_MODULE,
.open = ipsTft_open,
.release = ipsTft_release,
};
void Address_set(struct ipsTft_dev *dev,unsigned int x1,unsigned int y1,unsigned int x2,unsigned int y2)
{
write_command(dev,0x2a);
write_data(dev,x1>>8);
write_data(dev,x1);
write_data(dev,x2>>8);
write_data(dev,x2);
write_command(dev,0x2b);
write_data(dev,y1>>8);
write_data(dev,y1);
write_data(dev,y2>>8);
write_data(dev,y2);
write_command(dev,0x2C);
}
/*
刷屏函数
*/
void LCD_Clear(struct ipsTft_dev *dev,u16 Color)
{
u16 i,j;
Address_set(dev,0,0,LCD_W-1,LCD_H-1);
write_command(dev,0x2C);
for(i=0;i<LCD_W;i++)
{
for (j=0;j<LCD_H;j++)
{
//write_datas(dev,0xF800,2); //全红
write_data(dev,Color>>8);
write_data(dev,Color);
}
}
}
/*
* ipsTft内部寄存器初始化函数
* @param : 无
* @return : 无
*/
void ipsTft_reginit(struct ipsTft_dev *dev)
{
int i, j, n;
gpio_set_value(ipsTftdev.res_gpio, 0);
mdelay(20);
gpio_set_value(ipsTftdev.res_gpio, 1);
mdelay(20);
n = 0; // n用于记录数据数组spi_lcd_datas的位置
//发命令,并发出命令所需的数据
for (i = 0; i < ARRAY_SIZE(cmds); i++) //命令
{
write_command(dev, cmds[i].reg_addr);
for (j = 0; j < cmds[i].len; j++) //发出命令后,需要发出的数据
if(cmds[i].len!=0)
write_data(dev, spi_lcd_datas[n++]);
if (cmds[i].delay_ms) //如有延时则延时
mdelay(cmds[i].delay_ms);
}
n=0;
LCD_Clear(dev,WHITE);
printk("ips init finish!\n");
}
/*
* @description : spi驱动的probe函数,当驱动与
* 设备匹配以后此函数就会执行
* @param - client : spi设备
* @param - id : spi设备ID
*
*/
static int ipsTft_probe(struct spi_device *spi)
{
int ret = 0;
/* 1、构建设备号 */
if (ipsTftdev.major) {
ipsTftdev.devid = MKDEV(ipsTftdev.major, 0);
register_chrdev_region(ipsTftdev.devid, ipsTft_CNT, ipsTft_NAME);
} else {
alloc_chrdev_region(&ipsTftdev.devid, 0, ipsTft_CNT, ipsTft_NAME);
ipsTftdev.major = MAJOR(ipsTftdev.devid);
}
/* 2、注册设备 */
cdev_init(&ipsTftdev.cdev, &ipsTft_ops);
cdev_add(&ipsTftdev.cdev, ipsTftdev.devid, ipsTft_CNT);
/* 3、创建类 */
ipsTftdev.class = class_create(THIS_MODULE, ipsTft_NAME);
if (IS_ERR(ipsTftdev.class)) {
return PTR_ERR(ipsTftdev.class);
}
/* 4、创建设备 */
ipsTftdev.device = device_create(ipsTftdev.class, NULL, ipsTftdev.devid, NULL, ipsTft_NAME);
if (IS_ERR(ipsTftdev.device)) {
return PTR_ERR(ipsTftdev.device);
}
/* 获取设备树中cs片选信号 */
ipsTftdev.nd = of_find_node_by_path("/soc/aips-bus@02000000/spba-bus@02000000/ecspi@02010000");
if(ipsTftdev.nd == NULL) {
printk("ecspi3 node not find!\r\n");
return -EINVAL;
}
/* 2、 获取设备树中的gpio属性,得到BEEP所使用的BEEP编号 */
ipsTftdev.cs_gpio = of_get_named_gpio(ipsTftdev.nd, "cs-gpio", 0);
if(ipsTftdev.cs_gpio < 0) {
printk("can't get cs-gpio");
return -EINVAL;
}
ipsTftdev.nd = of_find_node_by_path("/ipsRes");
if(ipsTftdev.nd == NULL) {
printk("res-gpio node not find!\r\n");
return -EINVAL;
}
ipsTftdev.res_gpio = of_get_named_gpio(ipsTftdev.nd, "res-gpio", 0);
if(ipsTftdev.res_gpio < 0) {
printk("can't get res-gpio");
return -EINVAL;
}
ipsTftdev.nd = of_find_node_by_path("/ipsDc");
if(ipsTftdev.nd == NULL) {
printk("ipsDcgpio node not find!\r\n");
return -EINVAL;
}
ipsTftdev.dc_gpio = of_get_named_gpio(ipsTftdev.nd, "dc-gpio", 0);
if(ipsTftdev.dc_gpio < 0) {
printk("can't get ipsDc-gpio");
return -EINVAL;
}
/* 3、设置GPIO1_IO20为输出,并且输出高电平 */
ret = gpio_direction_output(ipsTftdev.cs_gpio, 1);
if(ret < 0) {
printk("can't set cs gpio!\r\n");
}
ret = gpio_direction_output(ipsTftdev.res_gpio, 1);
if(ret < 0) {
printk("can't set res gpio!\r\n");
}
ret = gpio_direction_output(ipsTftdev.dc_gpio, 1);
if(ret < 0) {
printk("can't set dc gpio!\r\n");
}
/*初始化spi_device */
spi->mode = SPI_MODE_2; /*MODE0,CPOL=0,CPHA=0 //出问题的地方!!!*/
spi_setup(spi);
ipsTftdev.private_data = spi; /* 设置私有数据 */
/* 初始化ipsTft内部寄存器 */
ipsTft_reginit(&ipsTftdev);
return 0;
}
/*
* @description : spi驱动的remove函数,移除spi驱动的时候此函数会执行
* @param - client : spi设备
* @return : 0,成功;其他负值,失败
*/
static int ipsTft_remove(struct spi_device *spi)
{
/* 删除设备 */
cdev_del(&ipsTftdev.cdev);
unregister_chrdev_region(ipsTftdev.devid, ipsTft_CNT);
/* 注销掉类和设备 */
device_destroy(ipsTftdev.class, ipsTftdev.devid);
class_destroy(ipsTftdev.class);
return 0;
}
/* 传统匹配方式ID列表 */
static const struct spi_device_id ipsTft_id[] = {
{"alientek,ipsTft", 0},
{}
};
/* 设备树匹配列表 */
static const struct of_device_id ipsTft_of_match[] = {
{ .compatible = "alientek,ipsTft" },
{ /* Sentinel */ }
};
/* SPI驱动结构体 */
static struct spi_driver ipsTft_driver = {
.probe = ipsTft_probe,
.remove = ipsTft_remove,
.driver = {
.owner = THIS_MODULE,
.name = "ipsTft",
.of_match_table = ipsTft_of_match,
},
.id_table = ipsTft_id,
};
/*
* @description : 驱动入口函数
* @param : 无
* @return : 无
*/
static int __init ipsTft_init(void)
{
return spi_register_driver(&ipsTft_driver);
}
/*
* @description : 驱动出口函数
* @param : 无
* @return : 无
*/
static void __exit ipsTft_exit(void)
{
spi_unregister_driver(&ipsTft_driver);
}
module_init(ipsTft_init);
module_exit(ipsTft_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("raymond");
(3)驱动测试程序
- 使用insmode命令安装模块
- probe函数调用后会触发屏幕初始化配置函数,配置完后屏幕会刷新几种颜色(纯用于测试,加载自动测试,岂不美哉),APP文件只需要open一下就会重复刷新操作。
app.c
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "sys/ioctl.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include <poll.h>
#include <sys/select.h>
#include <sys/time.h>
#include <signal.h>
#include <fcntl.h>
/*
* @description : main主程序
* @param - argc : argv数组元素个数
* @param - argv : 具体参数
* @return : 0 成功;其他 失败
*/
int main(int argc, char *argv[])
{
int fd;
char *filename;
int ret = 0;
if (argc != 2) {
printf("Error Usage!\r\n");
return -1;
}
filename = argv[1];
fd = open(filename, O_RDWR);
if(fd < 0) {
printf("can't open file %s\r\n", filename);
return -1;
}else{
printf("open the ips!\n");
}
close(fd); /* 关闭文件 */
return 0;
}
通过shell命令进行测试:
./ipsApp /dev/ipsTft
2.驱动应用程序
st7735_tft_app.c
#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/i2c.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 "ipsDriver.h"
#include "image.h"
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/uaccess.h>
typedef struct {
struct spi_device *spi; //记录fb_info对象对应的spi设备对象
struct task_struct *thread; //记录线程对象的地址,此线程专用于把显存数据发送到屏的驱动ic
}lcd_data_t;
struct fb_info *fbi;
void show_fb(struct fb_info *fbi, struct spi_device *spi);
static int tft_lcdfb_setcolreg(unsigned int regno, unsigned int red,
unsigned int green, unsigned int blue,
unsigned int transp, struct fb_info *info);
void LCD_Clear(struct ipsTft_dev *dev);
struct fb_ops fops = {
.owner = THIS_MODULE,
.fb_setcolreg = tft_lcdfb_setcolreg,
.fb_fillrect = cfb_fillrect,
.fb_copyarea = cfb_copyarea,
.fb_imageblit = cfb_imageblit,
};
int thread_func(void *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[240*240];
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)
{
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;
}
int myfb_new(struct spi_device *spi) //此函数在spi设备驱动的probe函数里被调用
{
u8 *v_addr;
u32 p_addr;
lcd_data_t *data;
/*
coherent:连贯的
分配连贯的物理内存
*/
v_addr = dma_alloc_coherent(NULL, X*Y*4, &p_addr, GFP_KERNEL);
//额外分配lcd_data_t类型空间
fbi = framebuffer_alloc(sizeof(lcd_data_t), NULL);
if(fbi == NULL){
printk("fbi allow error!\n");
return -1;
}
data = fbi->par; //data指针指向额外分配的空间
data->spi = spi;
fbi->pseudo_palette = pseudo_palette;
fbi->var.activate = FB_ACTIVATE_NOW;
fbi->var.xres = X;
fbi->var.yres = Y;
fbi->var.xres_virtual = X;
fbi->var.yres_virtual = Y;
fbi->var.bits_per_pixel = 32; // 屏是rgb565, 但QT程序只能支持32位.还需要在刷图时把32位的像素数据转换成rgb565
// fbi->var.red.offset = 11;
// fbi->var.red.length = 5;
// fbi->var.green.offset = 5;
// fbi->var.green.length = 6;
// fbi->var.blue.offset = 0;
// fbi->var.blue.length = 5;
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 = X*Y*4;
fbi->fix.type = FB_TYPE_PACKED_PIXELS;
fbi->fix.visual = FB_VISUAL_TRUECOLOR;
fbi->fix.line_length = X*4;
fbi->fbops = &fops;
fbi->screen_base = v_addr; //显存虚拟地址
fbi->screen_size = X*Y*4; //显存大小
register_framebuffer(fbi);
data->thread = kthread_run(thread_func, fbi, spi->modalias);
return 0;
}
void myfb_del(void) //此函数在spi设备驱动remove时被调用
{
lcd_data_t *data = fbi->par;
kthread_stop(data->thread); //让刷图线程退出
unregister_framebuffer(fbi);
dma_free_coherent(NULL, fbi->screen_size, fbi->screen_base, fbi->fix.smem_start);
framebuffer_release(fbi);
}
/*
* @description : 向ipsTft多个寄存器写入数据
* @param - dev: ipsTft设备
* @param - reg: 要写入的寄存器首地址
* @param - val: 要写入的数据缓冲区
* @param - len: 要写入的数据长度
* @return : 操作结果
*/
static s32 ipsTft_write_regs(struct spi_device *spi,u8 *buf, u32 len)
{
int ret;
struct spi_message m;
struct spi_transfer *t;
t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL); /* 申请内存 */
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;
}
/*
* @description : 向ipsTft指定寄存器写入指定的值,写一个寄存器
* @param - dev: ipsTft设备
* @param - reg: 要写的寄存器
* @param - data: 要写入的值
* @return : 无
*/
static void ipsTft_write_onereg(struct spi_device *spi, u8 buf)
{
ipsTft_write_regs(spi,&buf, 1);
}
/*
funciton: 写一个命令
*/
void write_command(struct spi_device *spi, u8 cmd)
{
// dc , command:0
gpio_set_value(ipsTftdev.dc_gpio, 0);
ipsTft_write_onereg(spi,cmd);
}
/*
funciton: 写一个数据
*/
void write_data(struct spi_device *spi, u8 data)
{
gpio_set_value(ipsTftdev.dc_gpio, 1);
ipsTft_write_onereg(spi,data);
}
/*
funciton: 写多个数据
*/
static void write_datas(struct spi_device *spi, u8 *data,u32 len)
{
gpio_set_value(ipsTftdev.dc_gpio, 1);
ipsTft_write_regs(spi,data,len);
}
/*
* @description : 打开设备
* @param - inode : 传递给驱动的inode
* @param - filp : 设备文件,file结构体有个叫做pr似有ate_data的成员变量
* 一般在open的时候将private_data似有向设备结构体。
* @return : 0 成功;其他 失败
*/
static int ipsTft_open(struct inode *inode, struct file *filp)
{
filp->private_data = &ipsTftdev; /* 设置私有数据 */
return 0;
}
/*
* @description : 关闭/释放设备
* @param - filp : 要关闭的设备文件(文件描述符)
* @return : 0 成功;其他 失败
*/
static int ipsTft_release(struct inode *inode, struct file *filp)
{
return 0;
}
/* ipsTft操作函数 */
static const struct file_operations ipsTft_ops = {
.owner = THIS_MODULE,
.open = ipsTft_open,
.release = ipsTft_release,
};
void Address_set(struct spi_device *spi,unsigned int x1,unsigned int y1,unsigned int x2,unsigned int y2)
{
write_command(spi,0x2a);
write_data(spi,x1>>8);
write_data(spi,x1);
write_data(spi,x2>>8);
write_data(spi,x2);
write_command(spi,0x2b);
write_data(spi,y1>>8);
write_data(spi,y1);
write_data(spi,y2>>8);
write_data(spi,y2);
write_command(spi,0x2C);
}
/*
刷屏函数
*/
void LCD_Clear(struct ipsTft_dev *dev)
{
u16 i,j;
u16 *memory;
memory = (u16 *)kzalloc(240*240*2, GFP_KERNEL); /* 申请内存 */
Address_set(dev->private_data,0,0,LCD_W-1,LCD_H-1);
write_command(dev->private_data,0x2C);
msleep(3000);//延迟显示,显示完上面钢铁侠图片
memcpy(memory,gImage_q,240*240*2);
int count = 0,split=10;
while (count < split)//split 用于设置分批发送数据,有时候st7789spi速率跟不上容易丢数据,偶尔会提示spi2 send ....类似的错误
{
write_datas(dev->private_data,(u8 *)memory+count*240*240*2/split,240*240*2/split);
count++;
}
kfree(memory);
}
/*
* ipsTft内部寄存器初始化函数
* @param : 无
* @return : 无
*/
void ipsTft_reginit(struct ipsTft_dev *dev)
{
int i, j, n;
gpio_set_value(ipsTftdev.res_gpio, 0);
mdelay(20);
gpio_set_value(ipsTftdev.res_gpio, 1);
mdelay(20);
n = 0; // n用于记录数据数组spi_lcd_datas的位置
//发命令,并发出命令所需的数据
for (i = 0; i < ARRAY_SIZE(cmds); i++) //命令
{
write_command(dev->private_data, cmds[i].reg_addr);
for (j = 0; j < cmds[i].len; j++) //发出命令后,需要发出的数据
if(cmds[i].len!=0)
write_data(dev->private_data, spi_lcd_datas[n++]);
if (cmds[i].delay_ms) //如有延时则延时
mdelay(cmds[i].delay_ms);
}
n=0;
LCD_Clear(dev);
msleep(5000);
printk("ips init finish!\n");
}
//show frambuffer
//framebuffer线程刷屏函数
void show_fb(struct fb_info *fbi, struct spi_device *spi)
{
int x, y;
u32 k;
u32 *p = (u16 *)(fbi->screen_base);
u16 c;
u8 *pp;
u16 *memory;
memory = kzalloc(240*2*240, GFP_KERNEL); /* 申请内存 */
Address_set(spi,0,0,LCD_W-1,LCD_H-1);
write_command(spi,0x2C);
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
*((u16 *)memory+y*fbi->var.yres+x) = ((c&0xff)<<8)|((c&0xff00)>>8);
}
}
//split 用于设置分批发送数据,有时候st7789spi速率跟不上容易丢数据,偶尔会提示spi2 send ....类似的错误
int count = 0,split=1;
while (count < split)
{
write_datas(spi,(u8 *)memory+count*fbi->var.yres*fbi->var.xres*2/split,fbi->var.yres*fbi->var.xres*2/split);
count++;
}
kfree(memory);
}
/*
* @description : spi驱动的probe函数,当驱动与
* 设备匹配以后此函数就会执行
* @param - client : spi设备
* @param - id : spi设备ID
*
*/
static int ipsTft_probe(struct spi_device *spi)
{
int ret = 0;
/* 1、构建设备号 */
if (ipsTftdev.major) {
ipsTftdev.devid = MKDEV(ipsTftdev.major, 0);
register_chrdev_region(ipsTftdev.devid, ipsTft_CNT, ipsTft_NAME);
} else {
alloc_chrdev_region(&ipsTftdev.devid, 0, ipsTft_CNT, ipsTft_NAME);
ipsTftdev.major = MAJOR(ipsTftdev.devid);
}
/* 2、注册设备 */
cdev_init(&ipsTftdev.cdev, &ipsTft_ops);
cdev_add(&ipsTftdev.cdev, ipsTftdev.devid, ipsTft_CNT);
/* 3、创建类 */
ipsTftdev.class = class_create(THIS_MODULE, ipsTft_NAME);
if (IS_ERR(ipsTftdev.class)) {
return PTR_ERR(ipsTftdev.class);
}
/* 4、创建设备 */
ipsTftdev.device = device_create(ipsTftdev.class, NULL, ipsTftdev.devid, NULL, ipsTft_NAME);
if (IS_ERR(ipsTftdev.device)) {
return PTR_ERR(ipsTftdev.device);
}
/* 获取设备树中cs片选信号 */
ipsTftdev.nd = of_find_node_by_path("/soc/aips-bus@02000000/spba-bus@02000000/ecspi@02010000");
if(ipsTftdev.nd == NULL) {
printk("ecspi3 node not find!\r\n");
return -EINVAL;
}
/* 2、 获取设备树中的gpio属性,得到BEEP所使用的BEEP编号 */
ipsTftdev.cs_gpio = of_get_named_gpio(ipsTftdev.nd, "cs-gpio", 0);
if(ipsTftdev.cs_gpio < 0) {
printk("can't get cs-gpio");
return -EINVAL;
}
ipsTftdev.nd = of_find_node_by_path("/ipsRes");
if(ipsTftdev.nd == NULL) {
printk("res-gpio node not find!\r\n");
return -EINVAL;
}
ipsTftdev.res_gpio = of_get_named_gpio(ipsTftdev.nd, "res-gpio", 0);
if(ipsTftdev.res_gpio < 0) {
printk("can't get res-gpio");
return -EINVAL;
}
ipsTftdev.nd = of_find_node_by_path("/ipsDc");
if(ipsTftdev.nd == NULL) {
printk("ipsDcgpio node not find!\r\n");
return -EINVAL;
}
ipsTftdev.dc_gpio = of_get_named_gpio(ipsTftdev.nd, "dc-gpio", 0);
if(ipsTftdev.dc_gpio < 0) {
printk("can't get ipsDc-gpio");
return -EINVAL;
}
/* 3、设置GPIO1_IO20为输出,并且输出高电平 */
ret = gpio_direction_output(ipsTftdev.cs_gpio, 1);//失能板子上的磁力计
if(ret < 0) {
printk("can't set cs gpio!\r\n");
}
gpio_set_value(ipsTftdev.cs_gpio,1);
ret = gpio_direction_output(ipsTftdev.res_gpio, 1);
if(ret < 0) {
printk("can't set res gpio!\r\n");
}
ret = gpio_direction_output(ipsTftdev.dc_gpio, 1);
if(ret < 0) {
printk("can't set dc gpio!\r\n");
}
/*初始化spi_device */
spi->mode = SPI_MODE_2; /*MODE0,CPOL=0,CPHA=0 //出问题的地方!!!*/
spi_setup(spi);
ipsTftdev.private_data = spi; /* 设置私有数据 */
/* 初始化ipsTft内部寄存器 */
ipsTft_reginit(&ipsTftdev);
myfb_new(spi); //fb设备初始化
return 0;
}
/*
* @description : spi驱动的remove函数,移除spi驱动的时候此函数会执行
* @param - client : spi设备
* @return : 0,成功;其他负值,失败
*/
static int ipsTft_remove(struct spi_device *spi)
{
myfb_del();//注销fb
/* 删除设备 */
cdev_del(&ipsTftdev.cdev);
unregister_chrdev_region(ipsTftdev.devid, ipsTft_CNT);
/* 注销掉类和设备 */
device_destroy(ipsTftdev.class, ipsTftdev.devid);
class_destroy(ipsTftdev.class);
return 0;
}
/* 传统匹配方式ID列表 */
static const struct spi_device_id ipsTft_id[] = {
{"alientek,ipsTft", 0},
{}
};
/* 设备树匹配列表 */
static const struct of_device_id ipsTft_of_match[] = {
{ .compatible = "alientek,ipsTft" },
{ /* Sentinel */ }
};
/* SPI驱动结构体 */
static struct spi_driver ipsTft_driver = {
.probe = ipsTft_probe,
.remove = ipsTft_remove,
.driver = {
.owner = THIS_MODULE,
.name = "ipsTft",
.of_match_table = ipsTft_of_match,
},
.id_table = ipsTft_id,
};
/*
* @description : 驱动入口函数
* @param : 无
* @return : 无
*/
static int __init ipsTft_init(void)
{
return spi_register_driver(&ipsTft_driver);
}
/*
* @description : 驱动出口函数
* @param : 无
* @return : 无
*/
static void __exit ipsTft_exit(void)
{
spi_unregister_driver(&ipsTft_driver);
}
module_init(ipsTft_init);
module_exit(ipsTft_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("raymond");
2. makefile文件
KERNELDIR := /home/raymond/linux/iMX6ULL/MyArmLinuxProgram/linuxKernal
CURRENT_PATH := $(shell pwd)
obj-m := ipsDriver.o
build: kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
三、更改开机图片
在image.h里面含有一张240*240的图片,用于开机显示图片,可自定义数组(用图像转换器转换)。
按照屏幕配置(St7789按图片配置即可),来选择进行转换。输出的数组替换image.h即可。
四、驱动测试
加载内核之后输入命令:ls /dev/
可以看到在/dev下已经有了一个fb设备节点。
还需要设置环境变量:
export QT_QPA_PLATFORM=linuxfb:fb=/dev/fb1
目的是指定Qt显示在新创建的fb设备上。