diff options
Diffstat (limited to 'drivers/input/touchscreen/tsc2007.c')
| -rw-r--r-- | drivers/input/touchscreen/tsc2007.c | 427 |
1 files changed, 266 insertions, 161 deletions
diff --git a/drivers/input/touchscreen/tsc2007.c b/drivers/input/touchscreen/tsc2007.c index 80467f26233..1bf9906b5a3 100644 --- a/drivers/input/touchscreen/tsc2007.c +++ b/drivers/input/touchscreen/tsc2007.c @@ -26,9 +26,9 @@ #include <linux/interrupt.h> #include <linux/i2c.h> #include <linux/i2c/tsc2007.h> - -#define TS_POLL_DELAY 1 /* ms delay between samples */ -#define TS_POLL_PERIOD 1 /* ms 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) @@ -69,17 +69,24 @@ struct ts_event { struct tsc2007 { struct input_dev *input; char phys[32]; - struct delayed_work work; struct i2c_client *client; u16 model; u16 x_plate_ohms; + u16 max_rt; + unsigned long poll_period; + int fuzzx; + int fuzzy; + int fuzzz; - bool 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); }; @@ -141,24 +148,8 @@ static u32 tsc2007_calculate_pressure(struct tsc2007 *tsc, struct ts_event *tc) return rt; } -static void tsc2007_send_up_event(struct tsc2007 *tsc) +static bool tsc2007_is_pen_down(struct tsc2007 *ts) { - 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); -} - -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. @@ -169,78 +160,82 @@ static void tsc2007_work(struct work_struct *work) * 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. + * in that case we assume that the pen is down and expect caller + * to fall back on the pressure reading. */ - 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"); - } + if (!ts->get_pendown_state) + return true; + + return ts->get_pendown_state(&ts->client->dev); +} - tsc2007_read_values(ts, &tc); +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; - 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; + while (!ts->stopped && tsc2007_is_pen_down(ts)) { - } + /* pen is down, continue with the measurement */ + tsc2007_read_values(ts, &tc); - if (rt) { - struct input_dev *input = ts->input; + rt = tsc2007_calculate_pressure(ts, &tc); - if (!ts->pendown) { - dev_dbg(&ts->client->dev, "DOWN\n"); + 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; + } + + 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); - 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); + + } 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); } - input_report_abs(input, ABS_X, tc.x); - input_report_abs(input, ABS_Y, tc.y); - input_report_abs(input, ABS_PRESSURE, rt); + wait_event_timeout(ts->wait, ts->stopped, + msecs_to_jiffies(ts->poll_period)); + } - input_sync(input); + dev_dbg(&ts->client->dev, "UP\n"); - dev_dbg(&ts->client->dev, "point(%4d,%4d), pressure (%4u)\n", - tc.x, tc.y, rt); + input_report_key(input, BTN_TOUCH, 0); + input_report_abs(input, ABS_PRESSURE, 0); + input_sync(input); - } 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; - } + if (ts->clear_penirq) + ts->clear_penirq(); - out: - if (ts->pendown) - schedule_delayed_work(&ts->work, - msecs_to_jiffies(TS_POLL_PERIOD)); - else - enable_irq(ts->irq); + return IRQ_HANDLED; } -static irqreturn_t tsc2007_irq(int irq, void *handle) +static irqreturn_t tsc2007_hard_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 (tsc2007_is_pen_down(ts)) + return IRQ_WAKE_THREAD; if (ts->clear_penirq) ts->clear_penirq(); @@ -248,112 +243,225 @@ static irqreturn_t tsc2007_irq(int irq, void *handle) return IRQ_HANDLED; } -static void tsc2007_free_irq(struct tsc2007 *ts) +static void tsc2007_stop(struct tsc2007 *ts) { - free_irq(ts->irq, ts); - if (cancel_delayed_work_sync(&ts->work)) { - /* - * Work was pending, therefore we need to enable - * IRQ here to balance the disable_irq() done in the - * interrupt handler. - */ - enable_irq(ts->irq); - } + ts->stopped = true; + mb(); + wake_up(&ts->wait); + + disable_irq(ts->irq); } -static int __devinit tsc2007_probe(struct i2c_client *client, - const struct i2c_device_id *id) +static int tsc2007_open(struct input_dev *input_dev) { - struct tsc2007 *ts; - struct tsc2007_platform_data *pdata = client->dev.platform_data; - struct input_dev *input_dev; + struct tsc2007 *ts = input_get_drvdata(input_dev); int err; - if (!pdata) { - dev_err(&client->dev, "platform data is required!\n"); + 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 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 device_node *np = client->dev.of_node; + u32 val32; + u64 val64; + + if (!np) { + dev_err(&client->dev, "missing device tree data\n"); return -EINVAL; } - if (!i2c_check_functionality(client->adapter, - I2C_FUNC_SMBUS_READ_WORD_DATA)) - return -EIO; + if (!of_property_read_u32(np, "ti,max-rt", &val32)) + ts->max_rt = val32; + else + ts->max_rt = MAX_12BIT; + + if (!of_property_read_u32(np, "ti,fuzzx", &val32)) + ts->fuzzx = val32; + + if (!of_property_read_u32(np, "ti,fuzzy", &val32)) + ts->fuzzy = val32; - ts = kzalloc(sizeof(struct tsc2007), GFP_KERNEL); - input_dev = input_allocate_device(); - if (!ts || !input_dev) { - err = -ENOMEM; - goto err_free_mem; + if (!of_property_read_u32(np, "ti,fuzzz", &val32)) + ts->fuzzz = val32; + + 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; } - ts->client = client; - ts->irq = client->irq; - ts->input = input_dev; - INIT_DELAYED_WORK(&ts->work, tsc2007_work); + 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 0; +} +#else +static int tsc2007_probe_dt(struct i2c_client *client, struct tsc2007 *ts) +{ + dev_err(&client->dev, "platform data is required!\n"); + return -EINVAL; +} +#endif +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; - snprintf(ts->phys, sizeof(ts->phys), - "%s/input0", dev_name(&client->dev)); + if (pdata->x_plate_ohms == 0) { + dev_err(&client->dev, "x_plate_ohms is not set up in platform data"); + return -EINVAL; + } - input_dev->name = "TSC2007 Touchscreen"; - input_dev->phys = ts->phys; - input_dev->id.bustype = BUS_I2C; + return 0; +} - input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); - input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); +static void tsc2007_call_exit_platform_hw(void *data) +{ + struct device *dev = data; + const struct tsc2007_platform_data *pdata = dev_get_platdata(dev); - 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); + pdata->exit_platform_hw(); +} - if (pdata->init_platform_hw) - pdata->init_platform_hw(); +static int tsc2007_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + const struct tsc2007_platform_data *pdata = dev_get_platdata(&client->dev); + struct tsc2007 *ts; + struct input_dev *input_dev; + int 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 (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_READ_WORD_DATA)) + return -EIO; - /* Prepare for touch readings - power down ADC and enable PENIRQ */ - err = tsc2007_xfer(ts, PWRDOWN); - if (err < 0) - goto err_free_irq; + ts = devm_kzalloc(&client->dev, sizeof(struct tsc2007), GFP_KERNEL); + if (!ts) + return -ENOMEM; - err = input_register_device(input_dev); + if (pdata) + err = tsc2007_probe_pdev(client, ts, pdata, id); + else + err = tsc2007_probe_dt(client, ts); if (err) - goto err_free_irq; + return err; + + input_dev = devm_input_allocate_device(&client->dev); + if (!input_dev) + return -ENOMEM; i2c_set_clientdata(client, ts); - return 0; + ts->client = client; + ts->irq = client->irq; + ts->input = input_dev; + init_waitqueue_head(&ts->wait); - err_free_irq: - tsc2007_free_irq(ts); - if (pdata->exit_platform_hw) - pdata->exit_platform_hw(); - err_free_mem: - input_free_device(input_dev); - kfree(ts); - return err; -} + snprintf(ts->phys, sizeof(ts->phys), + "%s/input0", dev_name(&client->dev)); -static int __devexit tsc2007_remove(struct i2c_client *client) -{ - struct tsc2007 *ts = i2c_get_clientdata(client); - struct tsc2007_platform_data *pdata = client->dev.platform_data; + 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); - tsc2007_free_irq(ts); + 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; + } + } - if (pdata->exit_platform_hw) - pdata->exit_platform_hw(); + if (pdata->init_platform_hw) + pdata->init_platform_hw(); + } - input_unregister_device(ts->input); - kfree(ts); + 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; + } + + tsc2007_stop(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; } @@ -365,28 +473,25 @@ static const struct i2c_device_id tsc2007_idtable[] = { 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 = __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); +module_i2c_driver(tsc2007_driver); MODULE_AUTHOR("Kwangwoo Lee <kwlee@mtekvision.com>"); MODULE_DESCRIPTION("TSC2007 TouchScreen Driver"); |
