今天继续分析NUC972触摸屏驱动,今天咱们详细说一下具体的TSC2007这个芯片的驱动代码。其实这段代码并不十分复杂,主要先搞清楚以下几点:
第一:触摸屏是注册到I2C总线上的input设备,所以驱动里面既包含I2C相关配置,也包含input设备相关配置;
第二:触摸屏驱动中具体的位置采样和各种滤波算法是触摸屏供应商提供的,作为触摸屏使用者一般不需要关注;
第三:触摸屏的中断配置和处理,主要是中断上半部和下半部的处理方法。
废话少说,直接步入正题,先看驱动的最后几行:
static const struct i2c_device_id tsc2007_idtable[] = {
{ "tsc2007", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, tsc2007_idtable);//将i2c设备表注册到内核中,方便内核管理设备
static struct i2c_driver tsc2007_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "tsc2007"
},
.id_table = tsc2007_idtable,
.probe = tsc2007_probe,
.remove = tsc2007_remove,
};
module_i2c_driver(tsc2007_driver);
MODULE_AUTHOR("Kwangwoo Lee <[email protected]>");
MODULE_DESCRIPTION("TSC2007 TouchScreen Driver");
MODULE_LICENSE("GPL");
这部分代码主要是实现了两个功能,第一是将触摸屏芯片tsc2007的id_table添加到相对应的I2C总线设备id_table的链表中;第二是将触摸屏芯片tsc2007的驱动注册到I2C总线上。
再看一probe函数:
static int tsc2007_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct tsc2007 *ts;
struct tsc2007_platform_data *pdata = client->dev.platform_data;
struct input_dev *input_dev;
int err;
if (!pdata) {
dev_err(&client->dev, "platform data is required!\n");
return -EINVAL;
}
if (!i2c_check_functionality(client->adapter,
I2C_FUNC_SMBUS_READ_WORD_DATA))
return -EIO;
ts = kzalloc(sizeof(struct tsc2007), GFP_KERNEL);
input_dev = input_allocate_device();
if (!ts || !input_dev) {
err = -ENOMEM;
goto err_free_mem;
}
ts->client = client;
ts->irq = client->irq;
ts->input = input_dev;
init_waitqueue_head(&ts->wait);
ts->model = pdata->model;
ts->x_plate_ohms = pdata->x_plate_ohms;
ts->max_rt = pdata->max_rt ? : MAX_12BIT;
ts->poll_delay = pdata->poll_delay ? : 1;
ts->poll_period = pdata->poll_period ? : 1;
ts->get_pendown_state = pdata->get_pendown_state;
ts->clear_penirq = pdata->clear_penirq;
printk("poll_period is %dms!\n",ts->poll_period);
if (pdata->x_plate_ohms == 0) {
dev_err(&client->dev, "x_plate_ohms is not set up in platform data");
err = -EINVAL;
goto err_free_mem;
}
snprintf(ts->phys, sizeof(ts->phys),
"%s/input0", dev_name(&client->dev));
input_dev->name = "TSC2007 Touchscreen";
input_dev->phys = ts->phys;
input_dev->id.bustype = BUS_I2C;
input_dev->open = tsc2007_open;
input_dev->close = tsc2007_close;
input_set_drvdata(input_dev, ts);
input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
input_set_abs_params(input_dev, ABS_X, 0, MAX_12BIT, pdata->fuzzx, 0);
input_set_abs_params(input_dev, ABS_Y, 0, MAX_12BIT, pdata->fuzzy, 0);
input_set_abs_params(input_dev, ABS_PRESSURE, 0, MAX_12BIT,
pdata->fuzzz, 0);
if (pdata->init_platform_hw)
pdata->init_platform_hw();
err = request_threaded_irq(ts->irq, tsc2007_hard_irq, tsc2007_soft_irq,
IRQF_ONESHOT, client->dev.driver->name, ts);
if (err < 0) {
dev_err(&client->dev, "irq %d busy?\n", ts->irq);
goto err_free_mem;
}
tsc2007_stop(ts);
err = input_register_device(input_dev);
if (err)
goto err_free_irq;
i2c_set_clientdata(client, ts);
return 0;
err_free_irq:
free_irq(ts->irq, ts);
if (pdata->exit_platform_hw)
pdata->exit_platform_hw();
err_free_mem:
input_free_device(input_dev);
kfree(ts);
return err;
}
这段代码主要是定义一个input_dev类型的结构体,并用device端传递进来的数据进行填充,并配置触摸屏相关的参数,比如向input核心层上传数据的类型和数据的具体参数等,并向input子系统注册一个input设备;还有就是通过I2C接口对触摸屏进行初始化。下面详细分析一下:
首先看一下这段代码:
if (!i2c_check_functionality(client->adapter,I2C_FUNC_SMBUS_READ_WORD_DATA))
return -EIO;
这段代码主要是检查当前的I2C适配器是否支持SMbus的读取寄存器的功能,详细的SMbus协议和功能标识有兴趣的可以自己百度,这里就不详细说了。
再看一下下面这段代码:
ts = kzalloc(sizeof(struct tsc2007), GFP_KERNEL);
input_dev = input_allocate_device();
if (!ts || !input_dev) {
err = -ENOMEM;
goto err_free_mem;
}
这段代码主要的功能是为抽象触摸屏的结构体ts社情内存并向内核申请一个input_dev的设备。
再看一下这段代码:
ts->client = client;
ts->irq = client->irq;
ts->input = input_dev;
init_waitqueue_head(&ts->wait);
ts->model = pdata->model;
ts->x_plate_ohms = pdata->x_plate_ohms;
ts->max_rt = pdata->max_rt ? : MAX_12BIT;
ts->poll_delay = pdata->poll_delay ? : 1;
ts->poll_period = pdata->poll_period ? : 1;
ts->get_pendown_state = pdata->get_pendown_state;
ts->clear_penirq = pdata->clear_penirq;
这段代码主要是对代表触摸屏的结构体ts的填充,主要包括名字、设备指针、初始化工作队列、触摸屏轮询延时和周期、触摸笔按下状态操作方法、清除触摸笔中断函数等。
再看一下这段代码:
input_dev->name = "TSC2007 Touchscreen";
input_dev->phys = ts->phys;
input_dev->id.bustype = BUS_I2C;
input_dev->open = tsc2007_open;
input_dev->close = tsc2007_close;
input_set_drvdata(input_dev, ts);
input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS);
input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH);
input_set_abs_params(input_dev, ABS_X, 0, MAX_12BIT, pdata->fuzzx, 0);
input_set_abs_params(input_dev, ABS_Y, 0, MAX_12BIT, pdata->fuzzy, 0);
input_set_abs_params(input_dev, ABS_PRESSURE, 0, MAX_12BIT,
pdata->fuzzz, 0);
同样的操作,这一段代码主要是对input设备相关的内容进行填充,有兴趣的可以将这个结构体追进去查看每个结构体成员的详细内容。
继续看下面的这一段代码:
err = request_threaded_irq(ts->irq, tsc2007_hard_irq, tsc2007_soft_irq,
IRQF_ONESHOT, client->dev.driver->name, ts);
if (err < 0) {
dev_err(&client->dev, "irq %d busy?\n", ts->irq);
goto err_free_mem;
这段代码主要是为触摸屏申请中断,这里面需要注意几个参数,首先是第二个参数,这个参数指向的参数是中断上半部的操作方法;其次是第三个参数,这个参数指向的是中断下半部的操作方式;第三个参数是中断类型的标志,IRQF_ONESHOT代表在这次中断没有处理完之前,如果这时再来中断的话,是不会再触发中断的,这样就避免了这次中断结束了出现中断泛滥的情况。
在看看这个函数中的最后几行代码:
tsc2007_stop(ts);
err = input_register_device(input_dev);
if (err)
goto err_free_irq;
i2c_set_clientdata(client, ts);
首先是要将触摸屏暂停了,主要是关闭中断、同步、置标志等,因为下面要对触摸屏进行初始化,即内核主动对触摸屏芯片进行写操作,这个时候如果有中断到来,会调用I2C的读操作,这样I2C的读写操作就会冲突。接下来的代码主要是注册input设备和通过i2c总线对触摸屏芯片TSC2007进行主动配置。
说到这里,tsc2007的probe函数已经分析完了。
接下来咱们讨论对应的中断函数,首先看一下中断上半部的操作函数:
static irqreturn_t tsc2007_hard_irq(int irq, void *handle)
{
struct tsc2007 *ts = handle;
if (!ts->get_pendown_state || likely(ts->get_pendown_state()))
return IRQ_WAKE_THREAD;
if (ts->clear_penirq)
ts->clear_penirq();
return IRQ_HANDLED;
}
这段代码主要看函数的两个返回值,返回IRQ_WAKE_THREAD这个标志代表此次中断有效,调用中断下半部处理函数进行处理,返回IRQ_HANDLED这个标志代表此次中断无效,不处理,直接返回。其实在这里这样做的意义其实不大,只是又读了一次触屏笔的状态,起到了一定的消抖作用。
下面咱们看一下中断下半部的操作函数:
static irqreturn_t tsc2007_soft_irq(int irq, void *handle)
{
struct tsc2007 *ts = handle;
struct input_dev *input = ts->input;
struct ts_event tc;
u32 rt;
//第一次调用soft中断处理函数,要进行消抖处理
printk("touchscreen's pen is down!\n");
msleep(1);//睡眠30ms
//确实有触摸屏按下
if (tsc2007_is_pen_down(ts)) {
while (!ts->stopped && tsc2007_is_pen_down(ts)) {
/* pen is down, continue with the measurement */
tsc2007_read_values(ts, &tc);
rt = tsc2007_calculate_pressure(ts, &tc);
if (rt == 0 && !ts->get_pendown_state) {
/*
* If pressure reported is 0 and we don't have
* callback to check pendown state, we have to
* assume that pen was lifted up.
*/
break;
}
if (rt <= ts->max_rt) {
dev_dbg(&ts->client->dev,
"DOWN point(%4d,%4d), pressure (%4u)\n",
tc.x, tc.y, rt);
input_report_key(input, BTN_TOUCH, 1);
input_report_abs(input, ABS_X, tc.x);
input_report_abs(input, ABS_Y, tc.y);
input_report_abs(input, ABS_PRESSURE, rt);
input_sync(input);
} else {
/*
* Sample found inconsistent by debouncing or pressure is
* beyond the maximum. Don't report it to user space,
* repeat at least once more the measurement.
*/
dev_dbg(&ts->client->dev, "ignored pressure %d\n", rt);
}
wait_event_timeout(ts->wait, ts->stopped,
msecs_to_jiffies(ts->poll_period));
}
dev_dbg(&ts->client->dev, "UP\n");
input_report_key(input, BTN_TOUCH, 0);
input_report_abs(input, ABS_PRESSURE, 0);
input_sync(input);
}
if (ts->clear_penirq)
ts->clear_penirq();
return IRQ_HANDLED;
}
这个函数的内容是触摸屏读取上传位置信息和键值的核心部分,咱们详细分析一下:
tsc2007_read_values(ts, &tc);
rt = tsc2007_calculate_pressure(ts, &tc);
上面这两行代码分别是读取触屏位置信息和压力信息的函数。
input_report_key(input, BTN_TOUCH, 1);
input_report_abs(input, ABS_X, tc.x);
input_report_abs(input, ABS_Y, tc.y);
input_report_abs(input, ABS_PRESSURE, rt);
input_sync(input);
上面这几行代码分别是想input子系统核心层上传按键值、触屏X轴、Y轴数据、触屏压力数据和同步信号。注意,这里面上传的是触摸屏的坐标值,不是与其相配合的LCD的坐标值,不过不要担心,在驱动里面不需要转换,因为应用层操作触屏是通过tslib这个库的API接口来进行的,这个工作tslib会帮大家做的,除非你在应用层直接操作的是root/dev/input/event*的设备节点,读出来的数据是触摸屏的原始坐标数据。
wait_event_timeout(ts->wait, ts->stopped,
msecs_to_jiffies(ts->poll_period));
上面这一行代码主要是等待每次读取触屏数据的时间间隔,在调试触摸屏时,这个参数也挺重要。
input_report_key(input, BTN_TOUCH, 0);
input_report_abs(input, ABS_PRESSURE, 0);
input_sync(input);
上面这几行主要是等到触摸屏松开时,向input子系统核心层上报的信息。
说到这里,触摸屏驱动里面需要注意的地方已经基本上完了,其他的主要是触摸屏本身的一些操作。
好了,到今天为止,nuc972的触摸屏驱动代码整体就分析忘了,希望对大家有点儿帮助,如果有什么错误,可以直接给我留言,谢谢!拜拜!