diff options
Diffstat (limited to 'drivers/input/touchscreen/tsc2007.c')
| -rw-r--r-- | drivers/input/touchscreen/tsc2007.c | 475 |
1 files changed, 295 insertions, 180 deletions
diff --git a/drivers/input/touchscreen/tsc2007.c b/drivers/input/touchscreen/tsc2007.c index 948e167557f..1bf9906b5a3 100644 --- a/drivers/input/touchscreen/tsc2007.c +++ b/drivers/input/touchscreen/tsc2007.c @@ -21,15 +21,14 @@ */ #include <linux/module.h> -#include <linux/hrtimer.h> #include <linux/slab.h> #include <linux/input.h> #include <linux/interrupt.h> #include <linux/i2c.h> #include <linux/i2c/tsc2007.h> - -#define TS_POLL_DELAY (10 * 1000) /* ns delay before the first sample */ -#define TS_POLL_PERIOD (5 * 1000) /* ns delay between samples */ +#include <linux/of_device.h> +#include <linux/of.h> +#include <linux/of_gpio.h> #define TSC2007_MEASURE_TEMP0 (0x0 << 4) #define TSC2007_MEASURE_AUX (0x2 << 4) @@ -70,20 +69,24 @@ struct ts_event { struct tsc2007 { struct input_dev *input; char phys[32]; - struct hrtimer timer; - struct ts_event tc; struct i2c_client *client; - spinlock_t lock; - u16 model; u16 x_plate_ohms; + u16 max_rt; + unsigned long poll_period; + int fuzzx; + int fuzzy; + int fuzzz; - unsigned pendown; + unsigned gpio; int irq; - int (*get_pendown_state)(void); + wait_queue_head_t wait; + bool stopped; + + int (*get_pendown_state)(struct device *); void (*clear_penirq)(void); }; @@ -109,186 +112,300 @@ static inline int tsc2007_xfer(struct tsc2007 *tsc, u8 cmd) return val; } -static void tsc2007_send_event(void *tsc) +static void tsc2007_read_values(struct tsc2007 *tsc, struct ts_event *tc) { - struct tsc2007 *ts = tsc; - u32 rt; - u16 x, y, z1, z2; + /* y- still on; turn on only y+ (and ADC) */ + tc->y = tsc2007_xfer(tsc, READ_Y); - x = ts->tc.x; - y = ts->tc.y; - z1 = ts->tc.z1; - z2 = ts->tc.z2; + /* 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); +} + +static u32 tsc2007_calculate_pressure(struct tsc2007 *tsc, struct ts_event *tc) +{ + u32 rt = 0; /* range filtering */ - if (x == MAX_12BIT) - x = 0; + if (tc->x == MAX_12BIT) + tc->x = 0; - if (likely(x && z1)) { + if (likely(tc->x && tc->z1)) { /* compute touch pressure resistance using equation #1 */ - rt = z2; - rt -= z1; - rt *= x; - rt *= ts->x_plate_ohms; - rt /= z1; + rt = tc->z2 - tc->z1; + rt *= tc->x; + rt *= tsc->x_plate_ohms; + rt /= tc->z1; rt = (rt + 2047) >> 12; - } else - rt = 0; - - /* 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 - */ - if (rt > MAX_12BIT) { - dev_dbg(&ts->client->dev, "ignored pressure %d\n", rt); - - hrtimer_start(&ts->timer, ktime_set(0, TS_POLL_PERIOD), - HRTIMER_MODE_REL); - return; } - /* NOTE: We can't rely on the pressure to determine the pen down - * state, even 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. + return rt; +} + +static bool tsc2007_is_pen_down(struct tsc2007 *ts) +{ + /* + * 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 - * timer by reading the pen signal state (it's a GPIO _and_ IRQ). + * 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 assume that the pen is down and expect caller + * to fall back on the pressure reading. */ - if (rt) { - struct input_dev *input = ts->input; - if (!ts->pendown) { - dev_dbg(&ts->client->dev, "DOWN\n"); + if (!ts->get_pendown_state) + return true; - input_report_key(input, BTN_TOUCH, 1); - ts->pendown = 1; + return ts->get_pendown_state(&ts->client->dev); +} + +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; + + 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 && !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; } - input_report_abs(input, ABS_X, x); - input_report_abs(input, ABS_Y, y); - input_report_abs(input, ABS_PRESSURE, rt); + if (rt <= ts->max_rt) { + dev_dbg(&ts->client->dev, + "DOWN point(%4d,%4d), pressure (%4u)\n", + tc.x, tc.y, rt); - input_sync(input); + 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); + } - dev_dbg(&ts->client->dev, "point(%4d,%4d), pressure (%4u)\n", - x, y, rt); + wait_event_timeout(ts->wait, ts->stopped, + msecs_to_jiffies(ts->poll_period)); } - hrtimer_start(&ts->timer, ktime_set(0, TS_POLL_PERIOD), - HRTIMER_MODE_REL); + 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; } -static int tsc2007_read_values(struct tsc2007 *tsc) +static irqreturn_t tsc2007_hard_irq(int irq, void *handle) { - /* y- still on; turn on only y+ (and ADC) */ - tsc->tc.y = tsc2007_xfer(tsc, READ_Y); + struct tsc2007 *ts = handle; - /* turn y- off, x+ on, then leave in lowpower */ - tsc->tc.x = tsc2007_xfer(tsc, READ_X); + if (tsc2007_is_pen_down(ts)) + return IRQ_WAKE_THREAD; - /* turn y+ off, x- on; we'll use formula #1 */ - tsc->tc.z1 = tsc2007_xfer(tsc, READ_Z1); - tsc->tc.z2 = tsc2007_xfer(tsc, READ_Z2); + if (ts->clear_penirq) + ts->clear_penirq(); - /* power down */ - tsc2007_xfer(tsc, PWRDOWN); + return IRQ_HANDLED; +} + +static void tsc2007_stop(struct tsc2007 *ts) +{ + ts->stopped = true; + mb(); + wake_up(&ts->wait); + + disable_irq(ts->irq); +} + +static int tsc2007_open(struct input_dev *input_dev) +{ + struct tsc2007 *ts = input_get_drvdata(input_dev); + int err; + + ts->stopped = false; + mb(); + + enable_irq(ts->irq); + + /* Prepare for touch readings - power down ADC and enable PENIRQ */ + err = tsc2007_xfer(ts, PWRDOWN); + if (err < 0) { + tsc2007_stop(ts); + return err; + } return 0; } -static enum hrtimer_restart tsc2007_timer(struct hrtimer *handle) +static void tsc2007_close(struct input_dev *input_dev) +{ + struct tsc2007 *ts = input_get_drvdata(input_dev); + + tsc2007_stop(ts); +} + +#ifdef CONFIG_OF +static int tsc2007_get_pendown_state_gpio(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct tsc2007 *ts = i2c_get_clientdata(client); + + return !gpio_get_value(ts->gpio); +} + +static int tsc2007_probe_dt(struct i2c_client *client, struct tsc2007 *ts) { - struct tsc2007 *ts = container_of(handle, struct tsc2007, timer); - unsigned long flags; + struct device_node *np = client->dev.of_node; + u32 val32; + u64 val64; - spin_lock_irqsave(&ts->lock, flags); + if (!np) { + dev_err(&client->dev, "missing device tree data\n"); + return -EINVAL; + } - if (unlikely(!ts->get_pendown_state() && ts->pendown)) { - struct input_dev *input = ts->input; + if (!of_property_read_u32(np, "ti,max-rt", &val32)) + ts->max_rt = val32; + else + ts->max_rt = MAX_12BIT; - dev_dbg(&ts->client->dev, "UP\n"); + if (!of_property_read_u32(np, "ti,fuzzx", &val32)) + ts->fuzzx = val32; - input_report_key(input, BTN_TOUCH, 0); - input_report_abs(input, ABS_PRESSURE, 0); - input_sync(input); + if (!of_property_read_u32(np, "ti,fuzzy", &val32)) + ts->fuzzy = val32; - ts->pendown = 0; - enable_irq(ts->irq); - } else { - /* pen is still down, continue with the measurement */ - dev_dbg(&ts->client->dev, "pen is still down\n"); + if (!of_property_read_u32(np, "ti,fuzzz", &val32)) + ts->fuzzz = val32; - tsc2007_read_values(ts); - tsc2007_send_event(ts); + if (!of_property_read_u64(np, "ti,poll-period", &val64)) + ts->poll_period = val64; + else + ts->poll_period = 1; + + if (!of_property_read_u32(np, "ti,x-plate-ohms", &val32)) { + ts->x_plate_ohms = val32; + } else { + dev_err(&client->dev, "missing ti,x-plate-ohms devicetree property."); + return -EINVAL; } - spin_unlock_irqrestore(&ts->lock, flags); + ts->gpio = of_get_gpio(np, 0); + if (gpio_is_valid(ts->gpio)) + ts->get_pendown_state = tsc2007_get_pendown_state_gpio; + else + dev_warn(&client->dev, + "GPIO not specified in DT (of_get_gpio returned %d)\n", + ts->gpio); - return HRTIMER_NORESTART; + return 0; } - -static irqreturn_t tsc2007_irq(int irq, void *handle) +#else +static int tsc2007_probe_dt(struct i2c_client *client, struct tsc2007 *ts) { - struct tsc2007 *ts = handle; - unsigned long flags; + dev_err(&client->dev, "platform data is required!\n"); + return -EINVAL; +} +#endif - spin_lock_irqsave(&ts->lock, flags); +static int tsc2007_probe_pdev(struct i2c_client *client, struct tsc2007 *ts, + const struct tsc2007_platform_data *pdata, + const struct i2c_device_id *id) +{ + ts->model = pdata->model; + ts->x_plate_ohms = pdata->x_plate_ohms; + ts->max_rt = pdata->max_rt ? : MAX_12BIT; + ts->poll_period = pdata->poll_period ? : 1; + ts->get_pendown_state = pdata->get_pendown_state; + ts->clear_penirq = pdata->clear_penirq; + ts->fuzzx = pdata->fuzzx; + ts->fuzzy = pdata->fuzzy; + ts->fuzzz = pdata->fuzzz; - if (likely(ts->get_pendown_state())) { - disable_irq_nosync(ts->irq); - hrtimer_start(&ts->timer, ktime_set(0, TS_POLL_DELAY), - HRTIMER_MODE_REL); + if (pdata->x_plate_ohms == 0) { + dev_err(&client->dev, "x_plate_ohms is not set up in platform data"); + return -EINVAL; } - if (ts->clear_penirq) - ts->clear_penirq(); + return 0; +} - spin_unlock_irqrestore(&ts->lock, flags); +static void tsc2007_call_exit_platform_hw(void *data) +{ + struct device *dev = data; + const struct tsc2007_platform_data *pdata = dev_get_platdata(dev); - return IRQ_HANDLED; + pdata->exit_platform_hw(); } static int tsc2007_probe(struct i2c_client *client, - const struct i2c_device_id *id) + const struct i2c_device_id *id) { + const struct tsc2007_platform_data *pdata = dev_get_platdata(&client->dev); struct tsc2007 *ts; - struct tsc2007_platform_data *pdata = 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; - i2c_set_clientdata(client, ts); - - ts->input = input_dev; + ts = devm_kzalloc(&client->dev, sizeof(struct tsc2007), GFP_KERNEL); + if (!ts) + return -ENOMEM; - hrtimer_init(&ts->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); - ts->timer.function = tsc2007_timer; + if (pdata) + err = tsc2007_probe_pdev(client, ts, pdata, id); + else + err = tsc2007_probe_dt(client, ts); + if (err) + return err; - spin_lock_init(&ts->lock); + input_dev = devm_input_allocate_device(&client->dev); + if (!input_dev) + return -ENOMEM; - 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); - pdata->init_platform_hw(); + ts->client = client; + ts->irq = client->irq; + ts->input = input_dev; + init_waitqueue_head(&ts->wait); snprintf(ts->phys, sizeof(ts->phys), "%s/input0", dev_name(&client->dev)); @@ -297,86 +414,84 @@ static int tsc2007_probe(struct i2c_client *client, 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_dev->open = tsc2007_open; + input_dev->close = tsc2007_close; - 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_set_drvdata(input_dev, ts); - tsc2007_read_values(ts); + input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); + input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); - ts->irq = client->irq; + input_set_abs_params(input_dev, ABS_X, 0, MAX_12BIT, ts->fuzzx, 0); + input_set_abs_params(input_dev, ABS_Y, 0, MAX_12BIT, ts->fuzzy, 0); + input_set_abs_params(input_dev, ABS_PRESSURE, 0, MAX_12BIT, + ts->fuzzz, 0); + + if (pdata) { + if (pdata->exit_platform_hw) { + err = devm_add_action(&client->dev, + tsc2007_call_exit_platform_hw, + &client->dev); + if (err) { + dev_err(&client->dev, + "Failed to register exit_platform_hw action, %d\n", + err); + return err; + } + } - 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; + if (pdata->init_platform_hw) + pdata->init_platform_hw(); } - err = input_register_device(input_dev); - if (err) - goto err_free_irq; - - dev_info(&client->dev, "registered with irq (%d)\n", ts->irq); - - return 0; - - err_free_irq: - free_irq(ts->irq, ts); - hrtimer_cancel(&ts->timer); - err_free_mem: - input_free_device(input_dev); - kfree(ts); - return err; -} - -static int tsc2007_remove(struct i2c_client *client) -{ - struct tsc2007 *ts = i2c_get_clientdata(client); - struct tsc2007_platform_data *pdata; + err = devm_request_threaded_irq(&client->dev, ts->irq, + tsc2007_hard_irq, tsc2007_soft_irq, + IRQF_ONESHOT, + client->dev.driver->name, ts); + if (err) { + dev_err(&client->dev, "Failed to request irq %d: %d\n", + ts->irq, err); + return err; + } - pdata = client->dev.platform_data; - pdata->exit_platform_hw(); + tsc2007_stop(ts); - free_irq(ts->irq, ts); - hrtimer_cancel(&ts->timer); - input_unregister_device(ts->input); - kfree(ts); + err = input_register_device(input_dev); + if (err) { + dev_err(&client->dev, + "Failed to register input device: %d\n", err); + return err; + } return 0; } -static struct i2c_device_id tsc2007_idtable[] = { +static const struct i2c_device_id tsc2007_idtable[] = { { "tsc2007", 0 }, { } }; MODULE_DEVICE_TABLE(i2c, tsc2007_idtable); +#ifdef CONFIG_OF +static const struct of_device_id tsc2007_of_match[] = { + { .compatible = "ti,tsc2007" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, tsc2007_of_match); +#endif + static struct i2c_driver tsc2007_driver = { .driver = { .owner = THIS_MODULE, - .name = "tsc2007" + .name = "tsc2007", + .of_match_table = of_match_ptr(tsc2007_of_match), }, .id_table = tsc2007_idtable, .probe = tsc2007_probe, - .remove = 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); +module_i2c_driver(tsc2007_driver); MODULE_AUTHOR("Kwangwoo Lee <kwlee@mtekvision.com>"); MODULE_DESCRIPTION("TSC2007 TouchScreen Driver"); |
