1 前言
lcd显示驱动移植完后,以为跳出深坑了;没想到触摸屏驱动来了个大的。看产品手册和百度的教程发现整体框架很简单,就是中断加spi子系统两个模块而已;触摸芯片xpt2046的初始化命令也很简单,就是单纯的输入0x90、0xd0即可获取xy轴数据了;但就是这么简单的驱动,却卡了我好久的时间。
首先就遇到了spi子系统与中断冲突的问题。这个问题困扰我很久,因为无论是从代码逻辑还是时序上都找不出来错误,但就是导致spi无法获取有效数据。后来也是读官方驱动和其他大佬的驱动代码,对比内容找差异,才发现问题的关键点在于我没加中断触发锁导致的。
分析原因应该是中断不断地触发导致写函数不停地给触摸屏发送读取指令,而我的传输数据又没传输完成,二者冲突导致我无法获取有效数据。我的初始版本驱动代码是仿照官方spi驱动编写的,官方驱动中断处理部分是通过sysfs方式在应用层控制,所以驱动不存在中断触发锁的问题;且可能是在应用层处理中断逻辑,有了操作延时,所以避免了此问题。而官方的ads7846驱动和其他触摸驱动中均有中断触发锁的存在。
自己编写的简单版触摸代码没问题后,适配官方的触摸驱动。查阅资料发现xpt2046被ads7846驱动所兼容。照葫芦画瓢适配设备树,匹配设备树无误后,又是卡在了中断无法触发的问题上。
分析代码,最终发现还是卡在spi子系统和中断的配合上,查阅资料各种适配修改,但结果是要么无法触发驱动,要么无法获取有效数据。又在网上找了专门的xpt2046驱动,也是无法直接使用,它的spi处理数据部分也是会导致中断无法触发。到这想省事直接使用官方驱动的路是堵死了,直接重写了spi传输数据的逻辑解决了这个问题。
最后以xpt2046驱动为基础,修改了2个模块的逻辑:修改了spi子系统处理数据的逻辑、把定时器中断改为工作队列中断完成了触摸驱动的适配工作。
2 触摸驱动函数介绍
首先触摸屏硬件部分已经在硬件的那篇笔记中讲解过了,其次中断部分在中断的笔记中也讲解过了,这里就不对这两个模块进行重复分析了。主要分析了一下数据传输以及input子系统。
2.1 触摸驱动结构体
因为是在官方驱动的基础上改写的,只用到了一些基础的变量(剩余的变量没删除,抄的时候注意一下)。
struct xpt2046
{
struct input_dev *input; // 输入设备的核心结构体,保存各种信息属性等
char phys[32];
char name[32];
char pendown_iomux_name[IOMUX_NAME_SIZE];
struct spi_device *spi; //spi指针
u16 model; //模式
u16 x_min, x_max;
u16 y_min, y_max;
struct xpt2046_packet *packet; // 避免缓存行共享的问题,单独设置的变量
spinlock_t lock;
struct hrtimer timer; //定时器的变量
void (*wait_for_sync)(void);
};
2.2 input子系统
这个子系统其实和frammebuffer子系统有些类似,就是会在/dev/input/注册一个event0/1的设备节点;然后在驱动中使用input子系统,当触摸屏被按下时event节点输出坐标信息,应用层可以获取输出的数据进而确定按下的坐标值。
(关于应用层与驱动层传递坐标值的方法我看有好几种,这个xpt2046的驱动就没像ads7846驱动一样使用input子系统,而是利用了ioctl方法向应用层传输数据。说不上来哪个方法更好,看实际情况自己使用即可。)
ts->wait_for_sync = null_wait_for_sync; // 空函数,作用不明
ts->x_min = 0; // pdata->x_min; //屏幕x的最小值
ts->x_max = SCREEN_MAX_X; // pdata->x_max; //屏幕x的最大值
ts->y_min = 0; // pdata->y_min; //屏幕y的最小值
ts->y_max = SCREEN_MAX_Y; // pdata->y_max; //屏幕y的最大值
配置input子系统的事件;
// 设置输入设备支持的事件类型和具体按键或触摸事件
input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); // 设置输入设备支持的事件类型,按键事件,绝对坐标事件
// 计算 BTN_TOUCH的值,设置到keybit数组中的位置,一般为0
input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); // 设置输入设备支持的具体事件,触摸事件,
// 设置绝对坐标轴(X 轴和 Y 轴)的参数,确定屏幕的坐标范围
input_set_abs_params(input_dev, ABS_X,
ts->x_min ?: 0,
ts->x_max ?: MAX_12BIT, // MAX_12BIT最大值是4095
0, 0);
input_set_abs_params(input_dev, ABS_Y,
ts->y_min ?: 0,
ts->y_max ?: MAX_12BIT,
0, 0);
注册event节点;
err = input_register_device(input_dev); // 注册event节点
触发中断向终端打印信息。
input_report_abs(input_dev, ABS_X, x);
input_report_abs(input_dev, ABS_Y, y);
input_report_key(input_dev, BTN_TOUCH, 1);
input_sync(input_dev);
2.3 spi子系统
spi子系统初始化;
if (spi->max_speed_hz > (125000 * SAMPLE_BITS))
{
xpt2046printDebug("f(sample) %d KHz?\n",
(spi->max_speed_hz / SAMPLE_BITS) / 1000);
return -EINVAL;
}
// 将tx和rx都设置为8位
spi->bits_per_word = 8;
spi->mode = SPI_MODE_0;
err = spi_setup(spi);
if (err < 0)
return err;
spi读取数据的函数;
uint16_t touch_buf = 0;
uint16_t value_re;
volatile int num;
static int read_spi(uint8_t tcmd)
{
int ret;
unsigned char txdata;
struct spi_message m;
struct spi_transfer *t;
struct spi_device *spi = ts_tmp->spi;
t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL); /* 申请内存 */
/* 第1次,发送要读取的寄存地址 */
txdata = tcmd;
t->tx_buf = &txdata; /* 要发送的数据 */
t->len = 1; /* 1个字节 */
spi_message_init(&m); /* 初始化spi_message */
spi_message_add_tail(t, &m); /* 将spi_transfer添加到spi_message队列 */
ret = spi_sync(spi, &m); /* 同步发送 */
mdelay(1);
/* 第2次,读取数据 */
txdata = 0x00; /* 随便一个值,此处无意义 */
t->rx_buf = &touch_buf; /* 读取到的数据 */
t->len = 1; /* 要读取的数据长度 */
spi_message_init(&m); /* 初始化spi_message */
spi_message_add_tail(t, &m); /* 将spi_transfer添加到spi_message队列 */
ret = spi_sync(spi, &m); /* 同步发送 */
value_re = touch_buf << 8;
/* 第3次,读取数据 */
txdata = 0x00; /* 随便一个值,此处无意义 */
t->rx_buf = &touch_buf; /* 读取到的数据 */
t->len = 1; /* 要读取的数据长度 */
spi_message_init(&m); /* 初始化spi_message */
spi_message_add_tail(t, &m); /* 将spi_transfer添加到spi_message队列 */
ret = spi_sync(spi, &m); /* 同步发送 */
value_re |= touch_buf;
value_re >>= 3;
value_re -= 4096;
if (value_re == 4095 || value_re == 0)
{
value_re = 0;
}
/*
else
{
num++;
printk("333333333x-write = [%d]-[%x]\n", tcmd, tcmd);
printk("333333333x-value = [%d]-[%x]\n", value_re, value_re);
printk("333333333---num = [%d]\n", num);
}
*/
udelay(200);
kfree(t); /* 释放内存 */
return value_re;
}
2.4 消抖逻辑
触摸屏最重要的就是消抖逻辑。坐标不准也就是多按几次的事,要是消抖没做好,屏幕直接就是乱跳转,各种bug频出。如下是编写的消抖逻辑:
// Request GPIO // 请求使用 GPIO
if (!gpio_is_valid(GPIO_BUTTON)) {
printk(KERN_INFO "Invalid GPIO\n");
return -ENODEV;
}
gpio_request(GPIO_BUTTON, "tsc2046"); // 请求使用指定的GPIO引脚。如果请求成功,该引脚将被保留,以防止其他内核代码使用它。
gpio_direction_input(GPIO_BUTTON); // 将指定的GPIO引脚设置为输入方向。这意味着它将读取外部设备的信号,例如按键的状态。
gpio_set_debounce(GPIO_BUTTON, 200); // 设置GPIO引脚的去抖动时间。去抖动是为了过滤掉按键按下时可能产生的抖动信号,200表示去抖动时间为200毫秒。
gpio_export(GPIO_BUTTON, false); // 将指定的GPIO引脚导出到用户空间,使得用户空间的应用程序可以通过sysfs文件系统访问和控制这个引脚。第二个参数false表示不允许通过sysfs修改引脚的方向(具体如何使用sysfs文件系统控制引脚查看我的上一篇显示驱动博客)
至此编写触摸驱动需要使用的主要函数都介绍完了。
3 触摸驱动
3.1 编写简单版驱动
如前言所示,为了精益求精在使用线程方式初步实现触摸功能后,就研究起了中断触发实现触摸功能;所以我编写的触摸驱动功能使用了线程和中断两种方式实现。
3.1.1 线程死循环读取触摸数据
如下是我使用线程方式来实现触摸驱动功能的代码,没啥值得说的,就是创建、添加、初始化、启动线程函数、最后调用spi子系统传输数据。
static struct task_struct *oled_kthread;//定义线程指针
static int oled_thread(void *data){//创建线程函数
while(1){
//把数据刷新到OLED:
//if(gpio_get_value(mylcddev.dc_gpio) == 0)
read_spi(data, 0x90);
set_current_state(TASK_INTERRUPTIBLE);//将线程中配置为可中断的睡眠状态。在这种状态下,进程会暂停执行,直到被唤醒
schedule_timeout(HZ/10);//让当前线程进入睡眠状态,时长为100ms。
if(kthread_should_stop()){//检查是否有停止请求发送给当前线程。
set_current_state(TASK_RUNNING);//准备退出线程之前,将线程状态设置为可运行状态。
break;//收到停止线程信号后,会跳出死循环
}
}
return 0;
}
oled_kthread = kthread_create(oled_thread, NULL, "my_touch_thread");//创建线程
wake_up_process(oled_kthread);//唤起线程
3.1.2 中断读取触摸数据
如下所示是我使用中断触发来实现触摸功能的代码。初始化中断、使用中断底半部调用函数等操作在中断篇都已经写过了,尤其3.5.3章节是我总结的经验,一定要注意看,这里我就不多解释了。
struct device_node *node;
unsigned int irqno;
#define my_irq_name "my_irq"
int tp_cs_pin;
// 底半部处理函数
void irq_work_func(struct work_struct *mwork)
{
printk("进入软中断,工作队列\n");
gpio_set_value(tp_cs_pin, 0);
disable_irq_nosync(irqno);//禁止指定终端号的中断
read_spi(data, 0xd0);//调用spi处理数据的函数
enable_irq(irqno);//启动指定终端号的中断
disable_irq_nosync(irqno);
read_spi(data, 0x90);
enable_irq(irqno);
gpio_set_value(tp_cs_pin, 1);
}
// 中断顶半部
irqreturn_t key_irq_handle(int irq, void *dev)
{
printk("进入中断上半部\n");
if (schedule_work(&data->work))
{
printk("调用中断下半部完成\n");
}else{
printk("调用中断下半部失败\n");
}
return IRQ_HANDLED;
}
//初始化中断底半部
INIT_WORK(&data->work, irq_work_func);
//触摸片选引脚初始化
tp_cs_pin = of_get_named_gpio(node, "tpcs", 0);
if (tp_cs_pin < 0)
{
ret = tp_cs_pin;
return tp_cs_pin;
}
//初始化cs引脚为高电平状态
if ((ret = gpio_request(tp_cs_pin, NULL)) != 0)
{
return ret;
}
// 设置引脚为输出模式并初始化为高电平
ret = gpio_direction_output(tp_cs_pin, 1);
if (ret)
{
return ret;
}
printk("获取cs引脚成功\n");
//映射得到软中断号
irqno = irq_of_parse_and_map(node, 0);
if (irqno == 0)
{
printk("get irqno error\n");
return -EAGAIN;
}
printk("获取中断号成功\n");
//注册中断
ret = devm_request_irq(&spi->dev, irqno, key_irq_handle,
IRQF_TRIGGER_FALLING, my_irq_name, (void *)data);
if (ret)
{
printk("request irq error\n");
return ret;
}
printk("注册中断成功\n");
3.1.3 编写自己的驱动
如下是我自己编写的触摸驱动代码。因为是在测试阶段,只需要在内核打印数据就行;所以没实现操作方法结构体或者input子系统这部分和应用层交互的功能。
#include < linux/init.h >
#include < linux/interrupt.h >
#include < linux/module.h >
#include < linux/of.h >
#include < linux/of_irq.h >
#include < linux/of_gpio.h >
#include < linux/delay.h >
#include < linux/spi/spi.h >
#include < linux/platform_device.h >
struct device_node *node;
unsigned int irqno;
#define my_irq_name "my_irq"
int tp_cs_pin;
struct my_touch_data
{
struct spi_device *spi;
struct work_struct work;
void *private_data; /* 私有数据 */
};
uint16_t touch_buf = 0;
//u8 *tx_buf, *rx_buf;
uint16_t value_re;
struct my_touch_data *data;
static struct task_struct *oled_kthread;
void read_spi(struct my_touch_data *data, uint8_t tcmd)
{
/**************************************************************/
int ret;
unsigned char txdata;
struct spi_message m;
struct spi_transfer *t;
struct spi_device *spi = (struct spi_device *)data->private_data;
t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL); /* 申请内存 */
/* 第1次,发送要读取的寄存地址 */
txdata = tcmd;
t->speed_hz = 5000000;
t->tx_buf = &txdata; /* 要发送的数据 */
t->rx_buf = &touch_buf; /* 读取到的数据 */
t->len = 1; /* 1个字节 */
spi_message_init(&m); /* 初始化spi_message */
spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message队列 */
ret = spi_sync(spi, &m); /* 同步发送 */
mdelay(1);
// printk("11111x-1 = [%d]-[%x]\n", value_re,value_re);
printk("1111111x-2 = [%d]-[%x]\n", touch_buf,touch_buf);
/* 第2次,读取数据 */
txdata = 0x00; /* 随便一个值,此处无意义 */
t->tx_buf = &txdata;
t->rx_buf = &touch_buf; /* 读取到的数据 */
t->len = 1; /* 要读取的数据长度 */
spi_message_init(&m); /* 初始化spi_message */
spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message队列 */
ret = spi_sync(spi, &m); /* 同步发送 */
value_re = touch_buf<<8;
// printk("22222222x-1 = [%d]-[%x]\n", value_re,value_re);
printk("2222222222x-2 = [%d]-[%x]\n", touch_buf,touch_buf);
/* 第3次,读取数据 */
txdata = 0x00; /* 随便一个值,此处无意义 */
t->tx_buf = &txdata;
t->rx_buf = &touch_buf; /* 读取到的数据 */
t->len = 1; /* 要读取的数据长度 */
spi_message_init(&m); /* 初始化spi_message */
spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message队列 */
ret = spi_sync(spi, &m); /* 同步发送 */
value_re |= touch_buf;
value_re >>= 3;
if(value_re != 255 && value_re != 248 && value_re != 128){
printk("333333333x-write = [%d]-[%x]\n", tcmd,tcmd);
printk("333333333x-value = [%d]-[%x]\n", value_re,value_re);
printk("333333333x-read = [%d]-[%x]\n", touch_buf,touch_buf);
}
udelay(200);
kfree(t); /* 释放内存 */
}
// // 底半部处理函数
// void irq_work_func(struct work_struct *mwork)
// {
// printk("进入软中断,工作队列\n");
// gpio_set_value(tp_cs_pin, 0);
// disable_irq_nosync(irqno);
// read_spi(data, 0xd0);
// enable_irq(irqno);
// disable_irq_nosync(irqno);
// read_spi(data, 0x90);
// enable_irq(irqno);
// gpio_set_value(tp_cs_pin, 1);
// }
// // 中断顶半部
// irqreturn_t key_irq_handle(int irq, void *dev)
// {
// printk("进入中断上半部\n");
// if (schedule_work(&data->work))
// {
// printk("调用中断下半部完成\n");
// }else{
// printk("调用中断下半部失败\n");
// }
// return IRQ_HANDLED;
// }
static int oled_thread(void *data){
while(1){
//把数据刷新到OLED:
//if(gpio_get_value(mylcddev.dc_gpio) == 0)
read_spi(data, 0x90);
set_current_state(TASK_INTERRUPTIBLE);
schedule_timeout(HZ/10);
if(kthread_should_stop()){
set_current_state(TASK_RUNNING);
break;
}
}
return 0;
}
int my_touch_probe(struct spi_device *spi)
{
int ret;
data = devm_kzalloc(&spi->dev, sizeof(struct my_touch_data), GFP_KERNEL);
// 使用 devm_kzalloc 分配的内存空间是由设备管理器(device manager)自动管理的。
// 当设备被卸载时,分配的内存会自动释放,不需要手动释放。
// 这是 devm_ 系列函数的一个重要特性,使驱动开发更简洁,减少了内存泄漏的风险。
if (!data)
return -ENOMEM;
/*******************************************************************/
// 0.tasklet_setup初始化
spi->mode = SPI_MODE_0; /*MODE0,CPOL=0,CPHA=0*/
spi->max_speed_hz = 5000000;
spi_setup(spi);
data->private_data = spi; /* 设置私有数据 */
// tx_buf = kmalloc(sizeof(char), GFP_KERNEL);
// rx_buf = kmalloc(sizeof(char), GFP_KERNEL);
// INIT_WORK(&data->work, irq_work_func);
/*******************注册中断********************************/
// 1.获取设备树中的节点
node = of_find_compatible_node(NULL, NULL, "ilitek,ili9488");
if (node == NULL)
{
printk("get node error\n");
return -EINVAL;
}
printk("匹配设备树成功\n");
/******************************************************************/
tp_cs_pin = of_get_named_gpio(node, "tpcs", 0);
if (tp_cs_pin < 0)
{
ret = tp_cs_pin;
return tp_cs_pin;
}
// 2.初始化cs引脚为高电平状态
if ((ret = gpio_request(tp_cs_pin, NULL)) != 0)
{
return ret;
}
// 设置引脚为输出模式并初始化为高电平
ret = gpio_direction_output(tp_cs_pin, 1);
if (ret)
{
return ret;
}
printk("获取cs引脚成功\n");
/***********************************************************/
// // 2.映射得到软中断号
// irqno = irq_of_parse_and_map(node, 0);
// if (irqno == 0)
// {
// printk("get irqno error\n");
// return -EAGAIN;
// }
// printk("获取中断号成功\n");
// // 3.注册中断
// ret = devm_request_irq(&spi->dev, irqno, key_irq_handle,
// IRQF_TRIGGER_FALLING, my_irq_name, (void *)data);
// if (ret)
// {
// printk("request irq error\n");
// return ret;
// }
// printk("注册中断成功\n");
/******************中断注册完成*******************************/
oled_kthread = kthread_create(oled_thread, NULL, "my_touch_thread");
//第一个参数是线程函数,字符串是调试时在各种日志中的线程名字,
wake_up_process(oled_kthread);
return 0;
}
int my_touch_remove(struct spi_device *spi)
{
cancel_work_sync(&data->work);
free_irq(irqno, 0);
return 0;
}
const struct of_device_id of_match[] = {
{
.compatible = "ilitek,ili9488",
},
{},
};
MODULE_DEVICE_TABLE(of, of_match);
struct spi_driver my_touch = {
.probe = my_touch_probe,
.remove = my_touch_remove,
.driver = {
.name = "my_touch",
.of_match_table = of_match,
},
};
module_spi_driver(my_touch);
MODULE_LICENSE("GPL");
3.2 改写官方驱动
对比内核官方驱动代码可以看出我写的驱动太过简单了,健壮性肯定比不上系统里官方驱动,所以还是要以官方驱动为基础修改适配自己的开发板。但官方的ads7846驱动太过复杂,我手头有个专门的xpt2046的驱动代码更适合改动,因此就以xpt2046为基础,修改编写了我的驱动代码。修改的主要部分是把原来的定时器中断方式改为工作队列,然后把官方spi子系统传输处理数据部分代码逻辑进行了修改。
3.2.1 定时器触发改为工作队列
替换中断方式是因为前期排查时我怀疑定时器中断有问题,就把定时器中断模块整体替换成工作队列中断模块。问题解决后再改动回去太繁琐了,就没改动回去,倒是没有别的什么原因。
struct work_struct work; // 工作队列
struct xpt2046 *ts_tmp; // 中转变量
/*--------------------------------------------------------------------------*/
// 获取中断引脚状态
static int get_pendown_state(struct xpt2046 *ts)
{
if (ts->get_pendown_state) // 检测参数是否初始化
return ts->get_pendown_state(); // 如果初始化,则调用函数指针指向对应的函数
return !gpio_get_value(ts->gpio_pendown); // 未初始化,返回中断引脚的状态,取反则是中断引脚低电平有效,
}
// 工作队列下半部
void irq_work_func(struct work_struct *mwork)
{
struct xpt2046 *ts = ts_tmp;
int status = 0;
int point_x = 0, point_y = 0;
spin_lock(&ts->lock);
printk("进入工作队列中断\n");
struct input_dev *input = ts->input;
xpt2046printDebug("%s:delay 15 ms!!!\n", __FUNCTION__);
mdelay(15);
/* pen is still down, continue with the measurement */
xpt2046printInfo("%s:pen is still down, continue with the measurement\n", __FUNCTION__);
disable_irq_nosync(ts->spi->irq);
point_x = read_spi(0xd0);
enable_irq(ts->spi->irq);
disable_irq_nosync(ts->spi->irq);
point_y = read_spi(0x90);
enable_irq(ts->spi->irq);
if (point_y == 4095 || point_y == 0 || point_x == 4095 || point_x == 0)
{
point_y = 0;
point_x = 0;
}
else
{
report_touch_event(input, point_x, point_y);
}
spin_unlock(&ts->lock);
}
//触发中断
static irqreturn_t xpt2046_irq(int irq, void *handle)
{
struct xpt2046 *ts = handle;
unsigned long flags;
printk("进入硬中断\n");
// xpt2046printInfo("%s.....%s.....%d\n", __FILE__, __FUNCTION__, __LINE__);
spin_lock_irqsave(&ts->lock, flags);
if (likely(get_pendown_state(ts))) // 获取中断引脚的状态,低电平触发,在函数中已取反,在likely中加速判断
{
if (!ts->irq_disabled)
{
/* The ARM do_simple_IRQ() dispatcher doesn't act
* like the other dispatchers: it will report IRQs
* even after they've been disabled. We work around
* that here. (The "generic irq" framework may help...)
*/
disable_irq_nosync(ts->spi->irq);
schedule_work(&work);
enable_irq(ts->spi->irq);
}
}
spin_unlock_irqrestore(&ts->lock, flags);
return IRQ_HANDLED;
}
/*--------------------------------------------------------------------------*/
// 初始化中断引脚
static int setup_pendown(struct spi_device *spi, struct xpt2046 *ts)
{
// struct xpt2046_platform_data *pdata = spi->dev.platform_data;
int err;
/* REVISIT when the irq can be triggered active-low, or if for some
* reason the touchscreen isn't hooked up, we don't need to access
* the pendown state.
*/
if (!gpio_is_valid(ts->gpio_pendown /*pdata->gpio_pendown*/)) // 检测中断引脚是否有效
{
dev_err(&spi->dev, "no get_pendown_state nor gpio_pendown?\n");
return -EINVAL;
}
// printk(KERN_ERR "===============%s:%d==============\n", __func__, __LINE__);
err = gpio_request(ts->gpio_pendown, "xpt2046_pendown"); // 申请中断引脚
if (err)
{
dev_err(&spi->dev, "failed to request pendown GPIO%d\n",
ts->gpio_pendown);
return err;
}
err = gpio_direction_input(ts->gpio_pendown); // 将中断引脚设置为输入
if (err)
{
dev_err(&spi->dev, "failed to set input pendown GPIO%d\n",
ts->gpio_pendown);
return err;
}
return 0;
}
// 初始化工作队列
INIT_WORK(&work, irq_work_func);
if (devm_request_irq(&spi->dev, spi->irq, xpt2046_irq,
IRQF_TRIGGER_FALLING, spi->dev.driver->name, ts))
{
xpt2046printDebug("%s:trying pin change workaround on irq %d\n", __FUNCTION__, spi->irq);
err = devm_request_irq(&spi->dev, spi->irq, xpt2046_irq,
IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, spi->dev.driver->name, ts);
if (err)
{
dev_dbg(&spi->dev, "irq %d busy?\n", spi->irq);
goto err_free_gpio;
}
}
3.2.2 spi子系统传输数据逻辑
前面说了我自己写的中断驱动获取数据失败原因是没加中断锁;而后在适配官方驱动时,添加中断锁还是会存在中断无法触发的问题。通读整个代码,不断的删减非核心代码,最后定位数据传输失败的原因是spi子系统与中断冲突问题。
官方的那俩驱动spi传输数据的方式就是会和中断有冲突。我在中断篇写了我为了剔除中断子系统的问题,替换了好几种中断实现方式,最后确定问题不在中断系统。
又分析俩官方驱动的spi子系统代码也没问题(其实不用想肯定没问题,都被用过多少年了,有问题早改了)。对比发现,官方在这部分处理代码的逻辑是将要处理的数据组成一个结构体,一次传输接收多个字节的数据;而我的驱动处理是逐次发送和接收,最后再数据处理组成一个有效数据。就是这个差异导致官方驱动始终无法获取有效数据,修改后完美解决。(单单是数据传输格式不同就导致数据接收失败?我觉得这不可能是代码的问题,所以最后我怀疑可能是瑞芯微的这个芯片的问题。)
如下是我自己实现的spi传输函数:
static int read_spi(uint8_t tcmd)
{
/**************************************************************/
int ret;
unsigned char txdata;
struct spi_message m;
struct spi_transfer *t;
struct spi_device *spi = ts_tmp->spi;
t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL); /* 申请内存 */
/* 第1次,发送要读取的寄存地址 */
txdata = tcmd;
// t->speed_hz = 5000000;
t->tx_buf = &txdata; /* 要发送的数据 */
t->len = 1; /* 1个字节 */
spi_message_init(&m); /* 初始化spi_message */
spi_message_add_tail(t, &m); /* 将spi_transfer添加到spi_message队列 */
ret = spi_sync(spi, &m); /* 同步发送 */
mdelay(1);
/* 第2次,读取数据 */
txdata = 0x00; /* 随便一个值,此处无意义 */
t->rx_buf = &touch_buf; /* 读取到的数据 */
t->len = 1; /* 要读取的数据长度 */
spi_message_init(&m); /* 初始化spi_message */
spi_message_add_tail(t, &m); /* 将spi_transfer添加到spi_message队列 */
ret = spi_sync(spi, &m); /* 同步发送 */
value_re = touch_buf << 8;
/* 第3次,读取数据 */
txdata = 0x00; /* 随便一个值,此处无意义 */
t->rx_buf = &touch_buf; /* 读取到的数据 */
t->len = 1; /* 要读取的数据长度 */
spi_message_init(&m); /* 初始化spi_message */
spi_message_add_tail(t, &m); /* 将spi_transfer添加到spi_message队列 */
ret = spi_sync(spi, &m); /* 同步发送 */
value_re |= touch_buf;
value_re >>= 3;
value_re -= 4096;
if (value_re == 4095 || value_re == 0)
{
value_re = 0;
}
else
{
num++;
printk("333333333x-write = [%d]-[%x]\n", tcmd, tcmd);
printk("333333333x-value = [%d]-[%x]\n", value_re, value_re);
printk("333333333---num = [%d]\n", num);
}
// printk("333333333x-read = [%d]-[%x]\n", touch_buf,touch_buf);
udelay(200);
kfree(t); /* 释放内存 */
return value_re;
}
3.3 触摸驱动代码
修改完成后的驱动代码如下:
/*
* drivers/input/touchscreen/xpt2046_ts.c - driver for rk29 spi xpt2046 device and console
*
* Copyright (C) 2011 ROCKCHIP, Inc.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include < linux/module.h >
#include < linux/delay.h >
#include < linux/hrtimer.h >
#include < linux/input.h >
#include < linux/interrupt.h >
#include < linux/io.h >
#include < linux/platform_device.h >
#include < linux/async.h >
#include < linux/irq.h >
#include < linux/workqueue.h >
#include < linux/proc_fs.h >
#include < linux/input/mt.h >
#include < linux/gpio.h >
#include < linux/of_gpio.h >
#include < linux/spi/spi.h >
#include < asm/uaccess.h >
#include < linux/kernel.h >
#include < linux/module.h >
#include < linux/cdev.h >
#include < linux/fs.h >
#include < linux/errno.h >
#include < linux/uaccess.h >
#include < linux/device.h >
#include < linux/ioport.h >
#include < linux/io.h >
#include " calibration_ts.h "
#include < linux/types.h >
#include < linux/init.h >
#include " xpt2046_ts.h "
extern volatile struct adc_point gADPoint;
struct work_struct work; // 工作队列
struct xpt2046 *ts_tmp; // 中转变量
/*
* This code has been heavily tested on a Nokia 770, and lightly
* tested on other xpt2046 devices (OSK/Mistral, Lubbock).
* TSC2046 is just newer xpt2046 silicon.
* Support for ads7843 tested on Atmel at91sam926x-EK.
* Support for ads7845 has only been stubbed in.
*
* IRQ handling needs a workaround because of a shortcoming in handling
* edge triggered IRQs on some platforms like the OMAP1/2. These
* platforms don't handle the ARM lazy IRQ disabling properly, thus we
* have to maintain our own SW IRQ disabled status. This should be
* removed as soon as the affected platform's IRQ handling is fixed.
*
* app note sbaa036 talks in more detail about accurate sampling...
* that ought to help in situations like LCDs inducing noise (which
* can also be helped by using synch signals) and more generally.
* This driver tries to utilize the measures described in the app
* note. The strength of filtering can be set in the board-* specific
* files.
*/
#define XPT2046_DEBUG 1
#if XPT2046_DEBUG
#define xpt2046printInfo(msg...) printk(msg);
#define xpt2046printDebug(msg...) printk(KERN_ERR msg);
#else
#define xpt2046printInfo(msg...)
#define xpt2046printDebug(msg...)
#endif
#define TS_POLL_DELAY (10 * 1000000) /* ns delay before the first sample */
#define TS_POLL_PERIOD (20 * 1000000) /* ns delay between samples */
/* this driver doesn't aim at the peak continuous sample rate */
#define SAMPLE_BITS (8 /*cmd*/ + 16 /*sample*/ + 2 /* before, after */)
//---------------------------zdx
#define TAG "tp:"
#define DEVICE_NAME "TPModule"
#define DEVICE_COUNT 1
#define SET_Calibration _IO('S', 2)
#define SAVE_X _IO('S', 1)
#define SAVE_Y _IO('S', 0)
struct chr_dev
{
struct cdev tp_cdev;
dev_t dev_nr;
struct class *tp_class;
struct device *tp_device;
int TK_uncali_x[5];
int TK_uncali_y[5];
int TK_flag;
};
struct chr_dev *chr_devp;
static int my_open(struct inode *inode, struct file *filefp)
{
chr_devp->TK_flag = 0;
xpt2046printDebug("open the char device\n");
return 0; // success
}
static int my_release(struct inode *inode, struct file *filefp)
{
xpt2046printDebug("close the char device\n");
return 0;
}
static ssize_t my_read(struct file *filefp, char __user *buf, size_t count, loff_t *off)
{
int ret;
int msg[2];
// xpt2046printDebug("=====enter my_read\n");
msg[0] = gADPoint.x;
msg[1] = gADPoint.y;
// xpt2046printDebug("------ msg[0]=%d msg[1]=%d\n",msg[0],msg[1]);
ret = copy_to_user(buf, msg, count);
if (ret != 0)
{
xpt2046printDebug("copy_to_user ERR\n");
return -EFAULT;
}
// xpt2046printDebug("enter read.ret=%d\n",ret);
return 0;
}
static ssize_t my_write(struct file *filefp, const char __user *buf, size_t count, loff_t *off)
{
// unsigned char ret;
// xpt2046printDebug("=====enter my_write chr_devp.TK_flag = %d\n",chr_devp->TK_flag);
if (chr_devp->TK_flag == 1) // save x
{
printk(KERN_ERR "write: enter save x.\n");
if (copy_from_user(chr_devp->TK_uncali_x, buf, count))
{
xpt2046printDebug("copy_from_user ERR\n");
return -EFAULT;
}
}
else if (chr_devp->TK_flag == 2) // save y
{
printk(KERN_ERR "write: enter save y.\n");
if (copy_from_user(chr_devp->TK_uncali_y, buf, count))
{
xpt2046printDebug("copy_from_user ERR\n");
return -EFAULT;
}
}
else
return -EFAULT;
return 0;
}
static long my_ioctl(struct file *filefp, unsigned int cmd, unsigned long arg)
{
// xpt2046printDebug("%s:=====enter my_ioctl!!\n",__FUNCTION__);
if (cmd == SAVE_X)
{
chr_devp->TK_flag = 1;
return 0;
}
else if (cmd == SAVE_Y)
{
chr_devp->TK_flag = 2;
return 0;
}
else
return -1;
}
static struct file_operations tp_flops = {
.owner = THIS_MODULE,
.open = my_open,
.release = my_release,
.read = my_read,
.write = my_write,
.compat_ioctl = my_ioctl,
};
struct ts_event
{
/* For portability, we can't read 12 bit values using SPI (which
* would make the controller deliver them as native byteorder u16
* with msbs zeroed). Instead, we read them as two 8-bit values,
* *** WHICH NEED BYTESWAPPING *** and range adjustment.
*/
u16 x;
u16 y;
int ignore;
};
/*
* We allocate this separately to avoid cache line sharing issues when
* driver is used with DMA-based SPI controllers (like atmel_spi) on
* systems where main memory is not DMA-coherent (most non-x86 boards).
*/
struct xpt2046_packet
{
u8 read_x, read_y, pwrdown;
u16 dummy; /* for the pwrdown read */
struct ts_event tc;
};
struct xpt2046
{
struct input_dev *input;
char phys[32];
char name[32];
char pendown_iomux_name[IOMUX_NAME_SIZE];
struct spi_device *spi;
u16 model;
u16 x_min, x_max;
u16 y_min, y_max;
u16 debounce_max;
u16 debounce_tol;
u16 debounce_rep;
u16 penirq_recheck_delay_usecs;
bool swap_xy;
struct xpt2046_packet *packet;
struct spi_transfer xfer[18];
struct spi_message msg[5];
struct spi_message *last_msg;
int msg_idx;
int read_cnt;
int read_rep;
int last_read;
int pendown_iomux_mode;
int touch_virtualkey_length;
spinlock_t lock;
struct hrtimer timer;
unsigned pendown : 1; /* P: lock */
unsigned pending : 1; /* P: lock */ // 在我代码中没用到
// FIXME remove "irq_disabled"
unsigned irq_disabled : 1; /* P: lock */ // 和中断的锁感觉冲突了,先不用,修改了自己的逻辑,初始值在prob中置0
unsigned disabled : 1;
unsigned is_suspended : 1;
int (*filter)(void *data, int data_idx, int *val);
void *filter_data;
void (*filter_cleanup)(void *data);
int (*get_pendown_state)(void);
int gpio_pendown;
void (*wait_for_sync)(void);
};
/* leave chip selected when we're done, for quicker re-select? */
#if 0
#define CS_CHANGE(xfer) ((xfer).cs_change = 1)
#else
#define CS_CHANGE(xfer) ((xfer).cs_change = 0)
#endif
/*--------------------------------------------------------------------------*/
/* The xpt2046 has touchscreen and other sensors.
* Earlier xpt2046 chips are somewhat compatible.
*/
#define XPT2046_START (1 << 7)
#define XPT2046_A2A1A0_d_y (1 << 4) /* differential */
#define XPT2046_A2A1A0_d_z1 (3 << 4) /* differential */
#define XPT2046_A2A1A0_d_z2 (4 << 4) /* differential */
#define XPT2046_A2A1A0_d_x (5 << 4) /* differential */
#define XPT2046_A2A1A0_temp0 (0 << 4) /* non-differential */
#define XPT2046_A2A1A0_vbatt (2 << 4) /* non-differential */
#define XPT2046_A2A1A0_vaux (6 << 4) /* non-differential */
#define XPT2046_A2A1A0_temp1 (7 << 4) /* non-differential */
#define XPT2046_8_BIT (1 << 3)
#define XPT2046_12_BIT (0 << 3)
#define XPT2046_SER (1 << 2) /* non-differential */
#define XPT2046_DFR (0 << 2) /* differential */
#define XPT2046_PD10_PDOWN (0 << 0) /* lowpower mode + penirq */
#define XPT2046_PD10_ADC_ON (0 << 0) /* ADC on */ // 原值是1
#define XPT2046_PD10_REF_ON (2 << 0) /* vREF on + penirq */
#define XPT2046_PD10_ALL_ON (3 << 0) /* ADC + vREF on */
#define MAX_12BIT ((1 << 12) - 1)
/* leave ADC powered up (disables penirq) between differential samples */
#define READ_12BIT_DFR(x, adc, vref) (XPT2046_START | XPT2046_A2A1A0_d_##x | XPT2046_12_BIT | XPT2046_DFR | \
(adc ? XPT2046_PD10_ADC_ON : 0) | (vref ? XPT2046_PD10_REF_ON : 0))
#define READ_Y(vref) (READ_12BIT_DFR(y, 1, vref))
// #define READ_Y(vref) (READ_12BIT_DFR(y, 0, 0))
#define READ_Z1(vref) (READ_12BIT_DFR(z1, 1, vref))
#define READ_Z2(vref) (READ_12BIT_DFR(z2, 1, vref))
// #define READ_X(vref) (READ_12BIT_DFR(x, 0, 0))
#define READ_X(vref) (READ_12BIT_DFR(x, 1, vref))
#define PWRDOWN (READ_12BIT_DFR(y, 0, 0)) /* LAST */
/* single-ended samples need to first power up reference voltage;
* we leave both ADC and VREF powered
*/
#define READ_12BIT_SER(x) (XPT2046_START | XPT2046_A2A1A0_##x | XPT2046_12_BIT | XPT2046_SER)
#define REF_ON (READ_12BIT_DFR(x, 1, 1))
#define REF_OFF (READ_12BIT_DFR(y, 0, 0))
#define DEBOUNCE_MAX 10 // 消抖过程中允许采样的最大次数
#define DEBOUNCE_REP 5 // 定义消抖过程中需要连续检测到相同状态的次数
#define DEBOUNCE_TOL 10 // 输入信号过程中会产生轻微波动,这个值是允许波动的次数或范围
#define PENIRQ_RECHECK_DELAY_USECS 1 // 触发中断后,等待的时长,以避免误判
#define PRESS_MAX 255 // 检测的最大压力值
/*--------------------------------------------------------------------------*/
/*
* touchscreen sensors use differential conversions.
*/
struct dfr_req
{
u8 command;
u8 pwrdown;
u16 dummy; /* for the pwrdown read */
__be16 sample;
struct spi_message msg;
struct spi_transfer xfer[4];
};
volatile struct adc_point gADPoint;
volatile int gFirstIrq;
// static void xpt2046_enable(struct xpt2046 *ts);
// static void xpt2046_disable(struct xpt2046 *ts);
// 验证输入的坐标值是否在尺寸范围内
static int xpt2046_verifyAndConvert(struct xpt2046 *ts, int adx, int ady, int *x, int *y)
{
xpt2046printInfo("%s:(%d,%d)(%d/%d)\n", __FUNCTION__, adx, ady, *x, *y);
if ((*x < ts->x_min) || (*x > ts->x_max))
return 1;
if ((*y < ts->y_min) || (*y > ts->y_max + ts->touch_virtualkey_length))
return 1;
return 0;
}
/*--------------------------------------------------------------------------*/
// 获取中断引脚状态
static int get_pendown_state(struct xpt2046 *ts)
{
if (ts->get_pendown_state) // 检测参数是否初始化
return ts->get_pendown_state(); // 如果初始化,则调用函数指针指向对应的函数
return !gpio_get_value(ts->gpio_pendown); // 未初始化,返回中断引脚的状态,取反则是中断引脚低电平有效,
}
// 空函数,作用不明
static void null_wait_for_sync(void)
{
}
static void report_touch_event(struct input_dev *input_dev, int x, int y)
{
input_report_abs(input_dev, ABS_X, x);
input_report_abs(input_dev, ABS_Y, y);
input_report_key(input_dev, BTN_TOUCH, 1);
input_sync(input_dev);
}
uint16_t touch_buf = 0;
uint16_t value_re;
volatile int num;
static int read_spi(uint8_t tcmd)
{
/**************************************************************/
int ret;
unsigned char txdata;
struct spi_message m;
struct spi_transfer *t;
struct spi_device *spi = ts_tmp->spi;
t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL); /* 申请内存 */
/* 第1次,发送要读取的寄存地址 */
txdata = tcmd;
// t->speed_hz = 5000000;
t->tx_buf = &txdata; /* 要发送的数据 */
t->len = 1; /* 1个字节 */
spi_message_init(&m); /* 初始化spi_message */
spi_message_add_tail(t, &m); /* 将spi_transfer添加到spi_message队列 */
ret = spi_sync(spi, &m); /* 同步发送 */
mdelay(1);
/* 第2次,读取数据 */
txdata = 0x00; /* 随便一个值,此处无意义 */
t->rx_buf = &touch_buf; /* 读取到的数据 */
t->len = 1; /* 要读取的数据长度 */
spi_message_init(&m); /* 初始化spi_message */
spi_message_add_tail(t, &m); /* 将spi_transfer添加到spi_message队列 */
ret = spi_sync(spi, &m); /* 同步发送 */
value_re = touch_buf << 8;
/* 第3次,读取数据 */
txdata = 0x00; /* 随便一个值,此处无意义 */
t->rx_buf = &touch_buf; /* 读取到的数据 */
t->len = 1; /* 要读取的数据长度 */
spi_message_init(&m); /* 初始化spi_message */
spi_message_add_tail(t, &m); /* 将spi_transfer添加到spi_message队列 */
ret = spi_sync(spi, &m); /* 同步发送 */
value_re |= touch_buf;
value_re >>= 3;
value_re -= 4096;
if (value_re == 4095 || value_re == 0)
{
value_re = 0;
}
else
{
num++;
printk("333333333x-write = [%d]-[%x]\n", tcmd, tcmd);
printk("333333333x-value = [%d]-[%x]\n", value_re, value_re);
printk("333333333---num = [%d]\n", num);
}
// printk("333333333x-read = [%d]-[%x]\n", touch_buf,touch_buf);
udelay(200);
kfree(t); /* 释放内存 */
return value_re;
}
// 工作队列下半部
void irq_work_func(struct work_struct *mwork)
{
struct xpt2046 *ts = ts_tmp;
int status = 0;
int point_x = 0, point_y = 0;
spin_lock(&ts->lock);
printk("进入工作队列中断\n");
struct input_dev *input = ts->input;
xpt2046printDebug("%s:delay 15 ms!!!\n", __FUNCTION__);
mdelay(15);
/* pen is still down, continue with the measurement */
xpt2046printInfo("%s:pen is still down, continue with the measurement\n", __FUNCTION__);
disable_irq_nosync(ts->spi->irq);
point_x = read_spi(0xd0);
enable_irq(ts->spi->irq);
disable_irq_nosync(ts->spi->irq);
point_y = read_spi(0x90);
enable_irq(ts->spi->irq);
if (point_y == 4095 || point_y == 0 || point_x == 4095 || point_x == 0)
{
point_y = 0;
point_x = 0;
}
else
{
report_touch_event(input, point_x, point_y);
}
spin_unlock(&ts->lock);
}
static irqreturn_t xpt2046_irq(int irq, void *handle)
{
struct xpt2046 *ts = handle;
unsigned long flags;
printk("进入硬中断\n");
// xpt2046printInfo("%s.....%s.....%d\n", __FILE__, __FUNCTION__, __LINE__);
spin_lock_irqsave(&ts->lock, flags);
if (likely(get_pendown_state(ts))) // 获取中断引脚的状态,低电平触发,在函数中已取反,在likely中加速判断
{
if (!ts->irq_disabled)
{
/* The ARM do_simple_IRQ() dispatcher doesn't act
* like the other dispatchers: it will report IRQs
* even after they've been disabled. We work around
* that here. (The "generic irq" framework may help...)
*/
disable_irq_nosync(ts->spi->irq);
schedule_work(&work);
enable_irq(ts->spi->irq);
}
}
spin_unlock_irqrestore(&ts->lock, flags);
return IRQ_HANDLED;
}
/*--------------------------------------------------------------------------*/
// 初始化中断引脚
static int setup_pendown(struct spi_device *spi, struct xpt2046 *ts)
{
// struct xpt2046_platform_data *pdata = spi->dev.platform_data;
int err;
/* REVISIT when the irq can be triggered active-low, or if for some
* reason the touchscreen isn't hooked up, we don't need to access
* the pendown state.
*/
if (!gpio_is_valid(ts->gpio_pendown /*pdata->gpio_pendown*/)) // 检测中断引脚是否有效
{
dev_err(&spi->dev, "no get_pendown_state nor gpio_pendown?\n");
return -EINVAL;
}
// printk(KERN_ERR "===============%s:%d==============\n", __func__, __LINE__);
err = gpio_request(ts->gpio_pendown, "xpt2046_pendown"); // 申请中断引脚
if (err)
{
dev_err(&spi->dev, "failed to request pendown GPIO%d\n",
ts->gpio_pendown);
return err;
}
err = gpio_direction_input(ts->gpio_pendown); // 将中断引脚设置为输入
if (err)
{
dev_err(&spi->dev, "failed to set input pendown GPIO%d\n",
ts->gpio_pendown);
return err;
}
return 0;
}
// 创建设备节点
static int tp_init(void)
{
int res;
chr_devp = (struct chr_dev *)kmalloc(sizeof(struct chr_dev), GFP_KERNEL);
// xpt2046printDebug("==>tp_init!\n");
res = alloc_chrdev_region(&chr_devp->dev_nr, 0, 1, "tp_chrdev");
if (res)
{
xpt2046printDebug("==>alloc chrdev region failed!\n");
goto chrdev_err;
}
// xpt2046printDebug("==>alloc_chrdev_region!\n");
cdev_init(&chr_devp->tp_cdev, &tp_flops);
// xpt2046printDebug("==>cdev_init!\n");
res = cdev_add(&chr_devp->tp_cdev, chr_devp->dev_nr, DEVICE_COUNT);
if (res)
{
xpt2046printDebug("==>cdev add failed!\n");
goto cdev_err;
}
// xpt2046printDebug("==>cdev_add!\n");
chr_devp->tp_class = class_create(THIS_MODULE, "tp_class");
if (IS_ERR(&chr_devp->tp_class))
{
res = PTR_ERR(chr_devp->tp_class);
goto class_err;
}
// xpt2046printDebug("==>class_create!\n");
chr_devp->tp_device = device_create(chr_devp->tp_class, NULL, chr_devp->dev_nr, NULL, "tp_device");
if (IS_ERR(&chr_devp->tp_device))
{
res = PTR_ERR(&chr_devp->tp_device);
goto device_err;
}
// xpt2046printDebug("==>device_create!\n");
// xpt2046printDebug("==>tp begin.\n");
xpt2046printDebug(DEVICE_NAME " initialized.\n");
return 0;
device_err:
device_destroy(chr_devp->tp_class, chr_devp->dev_nr);
class_destroy(chr_devp->tp_class);
class_err:
cdev_del(&chr_devp->tp_cdev);
cdev_err:
unregister_chrdev_region(chr_devp->dev_nr, DEVICE_COUNT);
chrdev_err:
return res;
}
static int xpt2046_probe(struct spi_device *spi)
{
struct xpt2046 *ts; // 总的数据结构体
struct xpt2046_packet *packet; // 避免缓存行共享的问题
struct input_dev *input_dev; // 输入设备的核心结构体,保存各种信息属性等
struct spi_message *m; // 传输数据的结构体
struct spi_transfer *x; // 保存数据的结构体
int vref;
int err;
struct device_node *np = spi->dev.of_node; // node节点
unsigned long irq_flags;
spi->irq = of_get_named_gpio_flags(np, "pendown-gpio", 0, (enum of_gpio_flags *)&irq_flags); // 获取gpio引脚
printk(KERN_ERR "===============%s:%d:spi->irq=%d==============\n", __func__, __LINE__, spi->irq);
/* don't exceed max specified sample rate */ // 防止设置超出最大采样率
if (spi->max_speed_hz > (125000 * SAMPLE_BITS))
{
xpt2046printDebug("f(sample) %d KHz?\n",
(spi->max_speed_hz / SAMPLE_BITS) / 1000);
return -EINVAL;
}
/* We'd set TX wordsize 8 bits and RX wordsize to 13 bits ... except
* that even if the hardware can do that, the SPI controller driver
* may not. So we stick to very-portable 8 bit words, both RX and TX.
* 将tx和rx都设置为8位
*/
spi->bits_per_word = 8;
spi->mode = SPI_MODE_0;
err = spi_setup(spi);
if (err < 0)
return err;
ts = kzalloc(sizeof(struct xpt2046), GFP_KERNEL);
packet = kzalloc(sizeof(struct xpt2046_packet), GFP_KERNEL); // 单独分配缓存的数据
input_dev = input_allocate_device(); // 初始化一个新的input_dev的结构体
if (!ts || !packet || !input_dev)
{
err = -ENOMEM;
goto err_free_mem;
}
dev_set_drvdata(&spi->dev, ts); // 将ts和spi->dev的结构体关联起来
// printk(KERN_ERR "===============%s:%d==============\n", __func__, __LINE__);
ts->packet = packet;
ts->spi = spi;
ts->input = input_dev;
ts->swap_xy = 0; // pdata->swap_xy;
// printk(KERN_ERR "===============%s:%d==============\n", __func__, __LINE__);
gFirstIrq = 1; // 标志位
// ts->irq_disabled = 0; //相当于锁,在硬中断中保证触发后进入工作队列
ts->pendown = 0; // 标志位,判断数据读取成功后进入event的标志
ts->gpio_pendown = spi->irq; // 将引脚号赋给结构体的引脚上
err = setup_pendown(spi, ts); // 用于检测触摸事件的 GPIO 引脚。例如,它会将一个特定的 GPIO 引脚设置为输入模式,用于检测触摸笔是否按下
if (err)
goto err_free_mem;
if (!spi->irq)
{
dev_err(&spi->dev, "%s:no IRQ?\n", __func__);
goto err_free_mem;
}
else
{
spi->irq = gpio_to_irq(spi->irq); // 将gpio号转换为中断号
dev_err(&spi->dev, "%s:IRQ=%d\n", __func__, spi->irq);
}
// printk(KERN_ERR "===============%s:%d==============\n", __func__, __LINE__);
// hrtimer_init(&ts->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); // 初始化时钟
// ts->timer.function = xpt2046_timer;
spin_lock_init(&ts->lock); // 初始化锁
ts->model = 2046; // 模式2046,用于sprintf填写信息
// printk(KERN_ERR "===============%s:%d==============\n", __func__, __LINE__);
ts->debounce_max = DEBOUNCE_MAX; // 消抖最大采样次数
if (ts->debounce_max < DEBOUNCE_REP) // 连续检测相同状态次数
ts->debounce_max = DEBOUNCE_REP;
ts->debounce_tol = DEBOUNCE_TOL; // 允许值波动的范围或次数,范围内为相同
ts->debounce_rep = DEBOUNCE_REP;
//ts->filter = xpt2046_debounce; // 处理 XPT2046 触摸屏控制器的去抖动
ts->filter_data = ts;
ts->penirq_recheck_delay_usecs = PENIRQ_RECHECK_DELAY_USECS; // 采样间歇时长
ts->wait_for_sync = null_wait_for_sync; // 空函数,作用不明
ts->x_min = 0; // pdata->x_min; //屏幕x的最小值
ts->x_max = SCREEN_MAX_X; // pdata->x_max; //屏幕x的最大值
ts->y_min = 0; // pdata->y_min; //屏幕y的最小值
ts->y_max = SCREEN_MAX_Y; // pdata->y_max; //屏幕y的最大值
ts->touch_virtualkey_length = 0; // 触摸虚拟按键长度,作用不明,可视作一个额外值变量,调节xy的长度
snprintf(ts->phys, sizeof(ts->phys), "%s/input0", dev_name(&spi->dev)); // 将dev_name(&spi->dev的值赋给phys
snprintf(ts->name, sizeof(ts->name), "xpt%d-touchscreen", ts->model); // 将mode的值组合后赋给ts->name
input_dev->name = ts->name; // 赋值
input_dev->phys = ts->phys; // 赋值
input_dev->dev.parent = &spi->dev; // 父节点赋值,这点意义未知
#if 1
// 设置输入设备支持的事件类型和具体按键或触摸事件
input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); // 设置输入设备支持的事件类型,按键事件,绝对坐标事件
// 计算 BTN_TOUCH的值,设置到keybit数组中的位置,一般为0
input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); // 设置输入设备支持的具体事件,触摸事件,
// 设置绝对坐标轴(X 轴和 Y 轴)的参数,确定屏幕的坐标范围
input_set_abs_params(input_dev, ABS_X,
ts->x_min ?: 0,
ts->x_max ?: MAX_12BIT, // MAX_12BIT最大值是4095
0, 0);
input_set_abs_params(input_dev, ABS_Y,
ts->y_min ?: 0,
ts->y_max ?: MAX_12BIT,
0, 0);
#endif
// m->complete = xpt2046_rx_val; //消息完成时的回调函数
// m->context = ts; //回调函数的上下文参数
// ts->last_msg = m;
xpt2046printInfo("%s:READ_X(1):value=10-[%d]-16-[%x]\n", __FUNCTION__, READ_X(vref), READ_X(vref));
INIT_WORK(&work, irq_work_func); // 初始化工作队列
if (devm_request_irq(&spi->dev, spi->irq, xpt2046_irq,
IRQF_TRIGGER_FALLING, spi->dev.driver->name, ts))
{
xpt2046printDebug("%s:trying pin change workaround on irq %d\n", __FUNCTION__, spi->irq);
err = devm_request_irq(&spi->dev, spi->irq, xpt2046_irq,
IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, spi->dev.driver->name, ts);
if (err)
{
dev_dbg(&spi->dev, "irq %d busy?\n", spi->irq);
goto err_free_gpio;
}
}
xpt2046printInfo("%s:touchscreen irq %d\n", __FUNCTION__, spi->irq);
/* take a first sample, leaving nPENIRQ active and vREF off; avoid
* the touchscreen, in case it's not connected.
*/
err = input_register_device(input_dev); // 注册event节点
if (err)
goto err_remove_attr_group;
tp_init(); // 创建设备节点
xpt2046printDebug("======xpt2046_tp initerface: driver initialized======\n");
xpt2046printDebug("======xpt2046_ts: driver initialized======\n");
ts_tmp = ts; // 中间值,传递ts指针
return 0;
err_remove_attr_group:
free_irq(spi->irq, ts);
err_free_gpio:
if (ts->gpio_pendown != -1)
gpio_free(ts->gpio_pendown);
err_free_mem:
input_free_device(input_dev);
kfree(packet);
kfree(ts);
return err;
}
static int xpt2046_remove(struct spi_device *spi)
{
struct xpt2046 *ts = dev_get_drvdata(&spi->dev);
input_unregister_device(ts->input);
cancel_work_sync(&work);
free_irq(ts->spi->irq, ts);
/* suspend left the IRQ disabled */
enable_irq(ts->spi->irq);
if (ts->gpio_pendown != -1)
gpio_free(ts->gpio_pendown);
if (ts->filter_cleanup)
ts->filter_cleanup(ts->filter_data);
kfree(ts->packet);
kfree(ts);
// tp_calib_iface_exit();
dev_dbg(&spi->dev, "unregistered touchscreen\n");
return 0;
}
static struct of_device_id xpt2046_dt_ids[] = {
{.compatible = "ti,tsc2046"},
{}};
static struct spi_driver xpt2046_driver = {
.driver = {
.name = "xpt2046_ts",
.bus = &spi_bus_type,
.owner = THIS_MODULE,
.of_match_table = xpt2046_dt_ids,
},
.probe = xpt2046_probe,
.remove = xpt2046_remove,
};
static int __init xpt2046_init(void)
{
int ret;
gADPoint.x = 0;
gADPoint.y = 0;
ret = spi_register_driver(&xpt2046_driver);
if (ret)
{
printk(KERN_ERR "Register XPT2046 driver failed.\n");
return ret;
}
else
// xpt2046printDebug("Touch panel drive XPT2046 driver init...\n");
return 0;
}
static void __exit xpt2046_exit(void)
{
xpt2046printDebug("Touch panel drive XPT2046 driver exit...\n");
spi_unregister_driver(&xpt2046_driver);
}
module_init(xpt2046_init);
module_exit(xpt2046_exit);
MODULE_DESCRIPTION("spi xpt2046 TouchScreen Driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("spi:xpt2046");
4 设备树
参照官方ultar板子的中断设备树编写自己的设备树。官方的触摸设备是基于i2c总线驱动的,所以被动研究了一下i2c设备树的写法。
4.1 i2c设备树简介
其他属性没啥太大问题,但其中有个reg属性和spi写法不太一样。平时使用spi的协议中这里的值一般为0,而在i2c协议下值变为了0x14?
查了一下发现原因很简单,spi协议有cs片选线,所以主从机之间通信可以根据片选线确定是哪一台从机。而i2c只有2根线,无法确定是哪个从机,这就需要通信地址了,因此0x14就是用来区分从机的,不同类型的i2c器件都会定义自己独特的i2c地址。有多个相同的i2c设备时,它们的地址相同,这种情况下依靠i2c复用器PCA9548解决,这个时候每个节点下的第一个reg值用于PCA9548区分。
&i2c3 {
status = "okay";
clock-frequency = <100000>;
pinctrl-0 = <&i2c3m2_xfer &tp_rst &tp_irq>;
pca9548: i2c-mux@70 {
compatible = "nxp,pca9548";
reg = <0x70>;
#address-cells = <1>;
#size-cells = <0>;
i2c@0 {
reg = <0>;//用于pca9548区分多个相同的设备
touchscreen1: touchscreen@14 {
compatible = "goodix,gt911";
reg = <0x14>;//特定类型的i2c设备
interrupt-parent = <&gpio0>;
interrupts = ;
reset-gpios = <&gpio0 RK_PA4 GPIO_ACTIVE_LOW>;
pinctrl-names = "default";
};
};
i2c@1 {
reg = <1>;
touchscreen2: touchscreen@14 {
compatible = "goodix,gt911";
reg = <0x14>;
interrupt-parent = <&gpio0>;
interrupts = ;
reset-gpios = <&gpio0 RK_PA6 GPIO_ACTIVE_LOW>;
pinctrl-names = "default";
};
};
};
};
&pinctrl {
touchscreen {
tp_rst: tp-rst {
rockchip,pins = <0 RK_PA4 RK_FUNC_GPIO &pcfg_pull_up>;
};
tp_irq: tp-irq {
rockchip,pins = <0 RK_PA3 RK_FUNC_GPIO &pcfg_pull_none>;
};
tp_rst2: tp-rst2 {
rockchip,pins = <0 RK_PA6 RK_FUNC_GPIO &pcfg_pull_up>;
};
tp_irq2: tp-irq2 {
rockchip,pins = <0 RK_PA5 RK_FUNC_GPIO &pcfg_pull_none>;
};
};
};
4.2 设备树的中断宏
选择合适的中断触发类型可以确保设备在预期的信号变化或状态时正确响应,这对于实现可靠的硬件驱动程序很重要。例如我的驱动代码使用的IRQ_TYPE_LEVEL_HIGH是高电平触发;在Linux内核代码中,IRQ_TYPE_LEVEL_HIGH这类宏定义在include/linux/irq.h头文件中,有需要可以查看内核文件挑选。具体作用及其适用设备如下所示:
- IRQ_TYPE_EDGE_RISING
在信号的上升沿(从低电平到高电平)触发中断。适用于需要在信号由低到高变化时响应的设备或传感器。#define IRQ_TYPE_EDGE_RISING 0x01
- IRQ_TYPE_EDGE_BOTH
在信号的任一边沿(无论是上升沿还是下降沿)都触发中断。适用于需要捕获信号的任何变化(上升或下降)事件的场景。#define IRQ_TYPE_EDGE_BOTH (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING)
- IRQ_TYPE_LEVEL_HIGH
在信号保持高电平时触发中断,且在信号维持高电平期间,中断会一直触发(或保持触发状态)。适用于需要在信号保持高电平期间响应的设备,如电平敏感的设备或状态监控。#define IRQ_TYPE_LEVEL_HIGH 0x04
- IRQ_TYPE_LEVEL_LOW
在信号保持低电平时触发中断,且在信号维持低电平期间,中断会一直触发(或保持触发状态)。适用于需要在信号保持低电平期间响应的设备,如电平敏感的设备或状态监控。#define IRQ_TYPE_LEVEL_LOW 0x08
- IRQ_TYPE_NONE
表示没有指定中断触发类型,通常用作初始化或默认值。适用于不需要特定触发类型或在初始化期间不设置触发类型的情况。#define IRQ_TYPE_NONE 0x00000000
-
IRQ_TYPE_SENSE_MASK
用于掩码中断类型的位字段,通常用于检测和处理中断触发类型。适用于内核中需要处理不同中断类型的代码逻辑。#define IRQ_TYPE_SENSE_MASK 0x0000000f
如下是实际使用的示例:
设备树端: my_device: my_device@0 { compatible = "my,device"; interrupt-parent = <&gpio>; interrupts = <17 IRQ_TYPE_LEVEL_HIGH>; };
驱动端: int irq_number; irq_number = gpio_to_irq(gpio_number); ret = request_irq(irq_number, my_irq_handler, IRQ_TYPE_LEVEL_HIGH, "my_device", dev); if (ret) { pr_err("Failed to request IRQ: %d\n", ret); return ret; }
4.3 编写自己的设备树
如下是编写的触摸屏中断设备树,因为和显示驱动共用一个spi接口,所以写到了一起。别看最后精简成了这几行;当初中断和spi子系统冲突的时候,没少研究分析是否是设备树的问题,也是被折磨的欲死欲仙。
&spi0 { status = "okay"; pinctrl-names = "default"; pinctrl-0 = <&spi0m0_pins ,&tp_irq>; #address-cells = <1>; #size-cells = <0>; spidev@0 { status = "disabled"; }; ili9488@0{ status = "okay"; compatible = "ilitek,ili9488"; reg = <0>; spi-max-frequency = <20000000>; fps = <30>; bpp = <24>; buswidth = <8>; debug = <0x7>; led-gpios = <&gpio0 RK_PA4 GPIO_ACTIVE_LOW>;//BL dc = <&gpio1 RK_PA2 GPIO_ACTIVE_HIGH>; //DC reset = <&gpio1 RK_PD1 GPIO_ACTIVE_LOW>; //RES-ili9488 lcd_cs = <&gpio1 RK_PC0 GPIO_ACTIVE_LOW>; //CS // reset = <&gpio1 RK_PC3 GPIO_ACTIVE_LOW>; //RES-st7789v }; tsc2046@0 { status = "okay"; compatible = "ti,tsc2046"; spi-max-frequency = <1500000>; reg = <1>; /* CS0 */ tp_cs = <&gpio3 RK_PA6 GPIO_ACTIVE_HIGH>; //tp-cs interrupt-parent = <&gpio3>; interrupts =
; /* GPIO 29 */ pendown-gpio = <&gpio3 RK_PA7 GPIO_ACTIVE_HIGH>; }; }; 至此触摸驱动编写完成,剩余部分就是和lvgl进行适配了!
5 总结
总结的话前言都说了,剩下的只想吐槽。嵌入式果然是个大深坑,各种奇葩问题都会遇到,不像直接用别人成熟的流程,可以轻松点亮屏幕;不过最有意思和成就感的恰恰也是解决这些问题。
最后说了半天总要看看驱动执行成功的成果,因此我在专门实现了input子系统,如下是实现触摸功能后在终端打印的触摸信息。