tsc2007是个多功能电阻触摸屏, 具有12bit精度的电阻屏A/D装换器, 压力传感器, 温度传感器等功能.
根据不同的触摸屏尺寸,可以支持8bit和12bit精度的转换.
支持I2C接口, 一个中断输出引脚, 中断输出低电平.
内部结构框图如下:
1. tsc2007 结构体定义:
-
struct
tsc2007 {
-
struct
input_dev *input;
-
char phys[
32];
-
struct
delayed_work work;
-
-
struct
i2c_client *client;
-
-
u16 model;
-
u16 x_plate_ohms;
-
-
bool pendown;
-
int irq;
-
-
int (*get_pendown_state)(
void);
-
void (*clear_penirq)(
void);
-
};
因为是input设备,所以肯定会有 input_dev 指针。
这里由于中断使用workqueue 下半部机制,所以才会有 delayed_work。
因为是一个i2c设备,所以肯定会有代表i2c设备的i2c_client.
irq 是该设备的中断输出用的中断号。
不知道 model, x_plate_ohms 和pendown是干啥用的。
2. module init:
-
static
const
struct
i2c_device_id tsc2007_idtable[] = {
-
{
"tsc2007",
0 },
-
{ }
-
};
-
-
MODULE_DEVICE_TABLE(i2c, tsc2007_idtable);
-
-
static
struct
i2c_driver tsc2007_driver = {
-
.driver = {
-
.owner = THIS_MODULE,
-
.name =
"tsc2007"
-
},
-
.id_table = tsc2007_idtable,
-
.probe = tsc2007_probe,
-
.remove = __devexit_p(tsc2007_remove),
-
};
-
-
static int __init tsc2007_init(void)
-
{
-
return
i2c_add_driver(&tsc2007_driver);
-
}
-
-
static void __exit tsc2007_exit(void)
-
{
-
i2c_del_driver(&tsc2007_driver);
-
}
-
-
module_init(tsc2007_init);
-
module_exit(tsc2007_exit);
这里用 i2c_add_driver() 来添加tsc2007_driver,就相当于把i2c_driver注册进I2C bus,
还会把tsc2007_idtable 添加进 i2c_device id_table 中。
3. tsc2007_probe:
-
static int __devinit tsc2007_probe(struct i2c_client *client,
-
const
struct i2c_device_id *id)
-
{
-
struct
tsc2007 *ts;
-
struct
tsc2007_platform_data *pdata = pdata = client->dev.platform_data;
-
struct
input_dev *input_dev;
-
int err;
-
void __iomem *cpld_base;
-
unsigned
short reg;
-
-
cpld_base =
ioremap(CPLD_BASE_ADDR,
64);
-
if(!cpld_base) {
-
dev_err(&client->dev,
"map cpld base failed!\n");
-
return -ENOMEM;
-
}
-
-
reg = __raw_readw(cpld_base + CNTL_REG1);
-
reg &= ~TP_POWER;
-
__raw_writew(reg, cpld_base + CNTL_REG1);
-
-
mdelay(
500);
-
reg |= TP_POWER;
-
__raw_writew(reg, cpld_base + CNTL_REG1);
-
-
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_DELAYED_WORK(&ts->work, tsc2007_work);
-
-
ts->model = pdata->model;
-
ts->x_plate_ohms = pdata->x_plate_ohms;
-
ts->get_pendown_state = pdata->get_pendown_state;
-
ts->clear_penirq = pdata->clear_penirq;
-
-
i2c_set_clientdata(client, ts);
-
-
set_irq_type(ts->irq, IRQF_TRIGGER_FALLING);
-
if (pdata->init_platform_hw) {
-
pdata->
init_platform_hw();
-
}
-
-
if (
tsc2007_xfer(ts, PWRDOWN) <
0) {
-
err = -ENODEV;
-
goto err_no_dev;
-
}
-
-
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->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,
0,
0);
-
input_set_abs_params(input_dev, ABS_Y,
0, MAX_12BIT,
0,
0);
-
input_set_abs_params(input_dev, ABS_PRESSURE,
0, MAX_12BIT,
0,
0);
-
-
if (pdata->init_platform_hw)
-
pdata->
init_platform_hw();
-
-
err =
request_irq(ts->irq, tsc2007_irq,
0,
-
client->dev.driver->name, ts);
-
if (err <
0) {
-
dev_err(&client->dev,
"irq %d busy?\n", ts->irq);
-
goto err_free_mem;
-
}
-
-
-
err =
input_register_device(input_dev);
-
if (err)
-
goto err_free_irq;
-
-
/* Prepare for touch readings - power down ADC and enable PENIRQ */
-
err =
tsc2007_xfer(ts, PWRDOWN);
-
if (err <
0)
-
goto err_free_irq;
-
-
return
0;
-
-
err_free_irq:
-
tsc2007_free_irq(ts);
-
if (pdata->exit_platform_hw)
-
pdata->
exit_platform_hw();
-
err_free_mem:
-
input_free_device(input_dev);
-
err_no_dev:
-
printk(
"Can't find TSC2007, I2C time out!\n");
-
if (pdata->exit_platform_hw) pdata->
exit_platform_hw();
-
kfree(ts);
-
return err;
-
}
这里probe算是最长的函数了,内容也算是标准的input设备初始化过程。
首先因为TSC2007 的上电需要一个300ms的低电平,用来reset 该芯片,
所以这里用cpld的一个引脚来连接VDD,所以就有了一段CPLD拉低TP_POWER引脚的控制代码:
-
cpld_base =
ioremap(CPLD_BASE_ADDR,
64);
-
if(!cpld_base) {
-
dev_err(&client->dev,
"map cpld base failed!\n");
-
return -ENOMEM;
-
}
-
-
reg = __raw_readw(cpld_base + CNTL_REG1);
-
reg &= ~TP_POWER;
-
__raw_writew(reg, cpld_base + CNTL_REG1);
-
-
mdelay(
500);
-
reg |= TP_POWER;
-
__raw_writew(reg, cpld_base + CNTL_REG1);
-
-
iounmap(cpld_base);
接着是检查i2c 的功能, 这个功能会在i2c 通讯(i2c_trasfer)的时候使用,所以要先检查是不是有这个功能:
-
if (!
i2c_check_functionality(client->adapter,
-
I2C_FUNC_SMBUS_READ_WORD_DATA))
-
return -EIO;
然后是初始化 tsc2007 设备,给里面的成员赋值,如中断号,input_dev 等:
-
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_DELAYED_WORK(&ts->work, tsc2007_work);
-
-
ts->model = pdata->model;
-
ts->x_plate_ohms = pdata->x_plate_ohms;
-
ts->get_pendown_state = pdata->get_pendown_state;
-
ts->clear_penirq = pdata->clear_penirq;
-
-
i2c_set_clientdata(client, ts);
初始化工作队列:
INIT_DELAYED_WORK(&ts->work, tsc2007_work);
设置中断触发方式,这段其实可以在初始化的时候完成,跟gpio初始化一起最好:
-
set_irq_type(ts->irq, IRQF_TRIGGER_FALLING);
-
if (pdata->init_platform_hw) {
-
pdata->
init_platform_hw();
-
}
接着是使能芯片的中断功能:
-
if (
tsc2007_xfer(ts, PWRDOWN) <
0) {
-
err = -ENODEV;
-
goto err_no_dev;
-
}
接着是input_dev , 标准的触摸屏input设备的初始化:
-
input_dev->name =
"TSC2007 Touchscreen";
-
input_dev->phys = ts->phys;
-
input_dev->id.bustype = BUS_I2C;
-
-
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,
0,
0);
-
input_set_abs_params(input_dev, ABS_Y,
0, MAX_12BIT,
0,
0);
-
input_set_abs_params(input_dev, ABS_PRESSURE,
0, MAX_12BIT,
0,
0);
注册input设备:
-
err =
input_register_device(input_dev);
-
if (err)
-
goto err_free_irq;
注册中断:
-
err =
request_irq(ts->irq, tsc2007_irq,
0,
-
client->dev.driver->name, ts);
-
if (err <
0) {
-
dev_err(&client->dev,
"irq %d busy?\n", ts->irq);
-
goto err_free_mem;
-
}
probe之后就等着中断就可以了。
4. 中断服务程序tsc2007_irq() 和下半部工作队列tsc2007_work:
-
static irqreturn_t tsc2007_irq(int irq, void *handle)
-
{
-
struct
tsc2007 *ts = handle;
-
-
if (!ts->get_pendown_state ||
likely(ts->
get_pendown_state())) {
-
disable_irq_nosync(ts->irq);
-
schedule_delayed_work(&ts->work,
-
msecs_to_jiffies(TS_POLL_DELAY));
-
}
-
-
if (ts->clear_penirq)
-
ts->
clear_penirq();
-
-
return IRQ_HANDLED;
-
}
这个中断服务程序会调用ts-〉work 工作队列,也就是probe中注册的tsc2007_work():
-
static void tsc2007_work(struct work_struct *work)
-
{
-
struct
tsc2007 *ts =
-
container_of(
to_delayed_work(work),
struct tsc2007, work);
-
struct
ts_event tc;
-
u32 rt;
-
-
/*
-
* NOTE: We can't rely on the pressure to determine the pen down
-
* state, even though this controller has a pressure sensor.
-
* The pressure value can fluctuate for quite a while after
-
* lifting the pen and in some cases may not even settle at the
-
* expected value.
-
*
-
* The only safe way to check for the pen up condition is in the
-
* work function by reading the pen signal state (it's a GPIO
-
* and IRQ). Unfortunately such callback is not always available,
-
* in that case we have rely on the pressure anyway.
-
*/
-
if (ts->get_pendown_state) {
-
if (
unlikely(!ts->
get_pendown_state())) {
-
tsc2007_send_up_event(ts);
-
ts->pendown =
false;
-
goto out;
-
}
-
-
dev_dbg(&ts->client->dev,
"pen is still down\n");
-
}
-
-
tsc2007_read_values(ts, &tc);
-
-
rt =
tsc2007_calculate_pressure(ts, &tc);
-
if (rt > MAX_12BIT) {
-
/*
-
* 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);
-
goto out;
-
-
}
-
-
if (rt) {
-
struct
input_dev *input = ts->input;
-
-
if (!ts->pendown) {
-
dev_dbg(&ts->client->dev,
"DOWN\n");
-
-
input_report_key(input, BTN_TOUCH,
1);
-
ts->pendown =
true;
-
}
-
-
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);
-
-
dev_dbg(&ts->client->dev,
"point(%4d,%4d), pressure (%4u)\n",
-
tc.x, tc.y, rt);
-
-
}
else
if (!ts->get_pendown_state && ts->pendown) {
-
/*
-
* We don't have callback to check pendown state, so we
-
* have to assume that since pressure reported is 0 the
-
* pen was lifted up.
-
*/
-
tsc2007_send_up_event(ts);
-
ts->pendown =
false;
-
}
-
-
out:
-
if (ts->pendown)
-
schedule_delayed_work(&ts->work,
-
msecs_to_jiffies(TS_POLL_PERIOD));
-
else
-
enable_irq(ts->irq);
-
}
这个工作队列的内容就是该芯片核心内容了。
tsc2007_read_values(), 用来读取X,Y,Z1, Z2等坐标值和压力值。
-
static void tsc2007_read_values(struct tsc2007 *tsc, struct ts_event *tc)
-
{
-
/* y- still on; turn on only y+ (and ADC) */
-
tc->y =
tsc2007_xfer(tsc, READ_Y);
-
-
/* turn y- off, x+ on, then leave in lowpower */
-
tc->x =
tsc2007_xfer(tsc, READ_X);
-
-
/* turn y+ off, x- on; we'll use formula #1 */
-
tc->z1 =
tsc2007_xfer(tsc, READ_Z1);
-
tc->z2 =
tsc2007_xfer(tsc, READ_Z2);
-
-
/* Prepare for next touch reading - power down ADC, enable PENIRQ */
-
tsc2007_xfer(tsc, PWRDOWN);
-
}
tsc2007_calculate_pressure() 用来计算压力值:
-
static u32 tsc2007_calculate_pressure(struct tsc2007 *tsc, struct ts_event *tc)
-
{
-
u32 rt =
0;
-
-
/* range filtering */
-
if (tc->x == MAX_12BIT)
-
tc->x =
0;
-
-
if (
likely(tc->x && tc->z1)) {
-
/* compute touch pressure resistance using equation #1 */
-
rt = tc->z2 - tc->z1;
-
rt *= tc->x;
-
rt *= tsc->x_plate_ohms;
-
rt /= tc->z1;
-
rt = (rt +
2047) >>
12;
-
}
-
-
return rt;
-
}
向事件层报告事件:
-
if (rt) {
-
struct
input_dev *input = ts->input;
-
-
if (!ts->pendown) {
-
dev_dbg(&ts->client->dev,
"DOWN\n");
-
-
input_report_key(input, BTN_TOUCH,
1);
-
ts->pendown =
true;
-
}
-
-
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);
-
-
dev_dbg(&ts->client->dev,
"point(%4d,%4d), pressure (%4u)\n",
-
tc.x, tc.y, rt);
-
-
}
else
if (!ts->get_pendown_state && ts->pendown) {
-
/*
-
* We don't have callback to check pendown state, so we
-
* have to assume that since pressure reported is 0 the
-
* pen was lifted up.
-
*/
-
tsc2007_send_up_event(ts);
-
ts->pendown =
false;
-
}
tsc2007_send_up_event():
-
static void tsc2007_send_up_event(struct tsc2007 *tsc)
-
{
-
struct
input_dev *input = tsc->input;
-
-
dev_dbg(&tsc->client->dev,
"UP\n");
-
-
input_report_key(input, BTN_TOUCH,
0);
-
input_report_abs(input, ABS_PRESSURE,
0);
-
input_sync(input);
-
}
触摸屏事件结构体的定义,主要就是2个坐标值,2个压力值:
-
struct
ts_event {
-
u16 x;
-
u16 y;
-
u16 z1, z2;
-
};
5. i2c 传输:
-
static inline int tsc2007_xfer(struct tsc2007 *tsc, u8 cmd)
-
{
-
s32 data;
-
u16 val;
-
-
data =
i2c_smbus_read_word_data(tsc->client, cmd);
-
if (data <
0) {
-
dev_err(&tsc->client->dev,
"i2c io error: %d\n", data);
-
return data;
-
}
-
-
/* The protocol and raw data format from i2c interface:
-
* S Addr Wr [A] Comm [A] S Addr Rd [A] [DataLow] A [DataHigh] NA P
-
* Where DataLow has [D11-D4], DataHigh has [D3-D0 << 4 | Dummy 4bit].
-
*/
-
val =
swab16(data) >>
4;
-
-
dev_dbg(&tsc->client->dev,
"data: 0x%x, val: 0x%x\n", data, val);
-
-
return val;
-
}