linux/drivers/input/touchscreen/da9034-ts.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-only
   2/*
   3 * Touchscreen driver for Dialog Semiconductor DA9034
   4 *
   5 * Copyright (C) 2006-2008 Marvell International Ltd.
   6 *      Fengwei Yin <fengwei.yin@marvell.com>
   7 *      Bin Yang  <bin.yang@marvell.com>
   8 *      Eric Miao <eric.miao@marvell.com>
   9 */
  10
  11#include <linux/module.h>
  12#include <linux/kernel.h>
  13#include <linux/delay.h>
  14#include <linux/platform_device.h>
  15#include <linux/input.h>
  16#include <linux/workqueue.h>
  17#include <linux/mfd/da903x.h>
  18#include <linux/slab.h>
  19
  20#define DA9034_MANUAL_CTRL      0x50
  21#define DA9034_LDO_ADC_EN       (1 << 4)
  22
  23#define DA9034_AUTO_CTRL1       0x51
  24
  25#define DA9034_AUTO_CTRL2       0x52
  26#define DA9034_AUTO_TSI_EN      (1 << 3)
  27#define DA9034_PEN_DETECT       (1 << 4)
  28
  29#define DA9034_TSI_CTRL1        0x53
  30#define DA9034_TSI_CTRL2        0x54
  31#define DA9034_TSI_X_MSB        0x6c
  32#define DA9034_TSI_Y_MSB        0x6d
  33#define DA9034_TSI_XY_LSB       0x6e
  34
  35enum {
  36        STATE_IDLE,     /* wait for pendown */
  37        STATE_BUSY,     /* TSI busy sampling */
  38        STATE_STOP,     /* sample available */
  39        STATE_WAIT,     /* Wait to start next sample */
  40};
  41
  42enum {
  43        EVENT_PEN_DOWN,
  44        EVENT_PEN_UP,
  45        EVENT_TSI_READY,
  46        EVENT_TIMEDOUT,
  47};
  48
  49struct da9034_touch {
  50        struct device           *da9034_dev;
  51        struct input_dev        *input_dev;
  52
  53        struct delayed_work     tsi_work;
  54        struct notifier_block   notifier;
  55
  56        int     state;
  57
  58        int     interval_ms;
  59        int     x_inverted;
  60        int     y_inverted;
  61
  62        int     last_x;
  63        int     last_y;
  64};
  65
  66static inline int is_pen_down(struct da9034_touch *touch)
  67{
  68        return da903x_query_status(touch->da9034_dev, DA9034_STATUS_PEN_DOWN);
  69}
  70
  71static inline int detect_pen_down(struct da9034_touch *touch, int on)
  72{
  73        if (on)
  74                return da903x_set_bits(touch->da9034_dev,
  75                                DA9034_AUTO_CTRL2, DA9034_PEN_DETECT);
  76        else
  77                return da903x_clr_bits(touch->da9034_dev,
  78                                DA9034_AUTO_CTRL2, DA9034_PEN_DETECT);
  79}
  80
  81static int read_tsi(struct da9034_touch *touch)
  82{
  83        uint8_t _x, _y, _v;
  84        int ret;
  85
  86        ret = da903x_read(touch->da9034_dev, DA9034_TSI_X_MSB, &_x);
  87        if (ret)
  88                return ret;
  89
  90        ret = da903x_read(touch->da9034_dev, DA9034_TSI_Y_MSB, &_y);
  91        if (ret)
  92                return ret;
  93
  94        ret = da903x_read(touch->da9034_dev, DA9034_TSI_XY_LSB, &_v);
  95        if (ret)
  96                return ret;
  97
  98        touch->last_x = ((_x << 2) & 0x3fc) | (_v & 0x3);
  99        touch->last_y = ((_y << 2) & 0x3fc) | ((_v & 0xc) >> 2);
 100
 101        return 0;
 102}
 103
 104static inline int start_tsi(struct da9034_touch *touch)
 105{
 106        return da903x_set_bits(touch->da9034_dev,
 107                        DA9034_AUTO_CTRL2, DA9034_AUTO_TSI_EN);
 108}
 109
 110static inline int stop_tsi(struct da9034_touch *touch)
 111{
 112        return da903x_clr_bits(touch->da9034_dev,
 113                        DA9034_AUTO_CTRL2, DA9034_AUTO_TSI_EN);
 114}
 115
 116static inline void report_pen_down(struct da9034_touch *touch)
 117{
 118        int x = touch->last_x;
 119        int y = touch->last_y;
 120
 121        x &= 0xfff;
 122        if (touch->x_inverted)
 123                x = 1024 - x;
 124        y &= 0xfff;
 125        if (touch->y_inverted)
 126                y = 1024 - y;
 127
 128        input_report_abs(touch->input_dev, ABS_X, x);
 129        input_report_abs(touch->input_dev, ABS_Y, y);
 130        input_report_key(touch->input_dev, BTN_TOUCH, 1);
 131
 132        input_sync(touch->input_dev);
 133}
 134
 135static inline void report_pen_up(struct da9034_touch *touch)
 136{
 137        input_report_key(touch->input_dev, BTN_TOUCH, 0);
 138        input_sync(touch->input_dev);
 139}
 140
 141static void da9034_event_handler(struct da9034_touch *touch, int event)
 142{
 143        int err;
 144
 145        switch (touch->state) {
 146        case STATE_IDLE:
 147                if (event != EVENT_PEN_DOWN)
 148                        break;
 149
 150                /* Enable auto measurement of the TSI, this will
 151                 * automatically disable pen down detection
 152                 */
 153                err = start_tsi(touch);
 154                if (err)
 155                        goto err_reset;
 156
 157                touch->state = STATE_BUSY;
 158                break;
 159
 160        case STATE_BUSY:
 161                if (event != EVENT_TSI_READY)
 162                        break;
 163
 164                err = read_tsi(touch);
 165                if (err)
 166                        goto err_reset;
 167
 168                /* Disable auto measurement of the TSI, so that
 169                 * pen down status will be available
 170                 */
 171                err = stop_tsi(touch);
 172                if (err)
 173                        goto err_reset;
 174
 175                touch->state = STATE_STOP;
 176
 177                /* FIXME: PEN_{UP/DOWN} events are expected to be
 178                 * available by stopping TSI, but this is found not
 179                 * always true, delay and simulate such an event
 180                 * here is more reliable
 181                 */
 182                mdelay(1);
 183                da9034_event_handler(touch,
 184                                     is_pen_down(touch) ? EVENT_PEN_DOWN :
 185                                                          EVENT_PEN_UP);
 186                break;
 187
 188        case STATE_STOP:
 189                if (event == EVENT_PEN_DOWN) {
 190                        report_pen_down(touch);
 191                        schedule_delayed_work(&touch->tsi_work,
 192                                msecs_to_jiffies(touch->interval_ms));
 193                        touch->state = STATE_WAIT;
 194                }
 195
 196                if (event == EVENT_PEN_UP) {
 197                        report_pen_up(touch);
 198                        touch->state = STATE_IDLE;
 199                }
 200                break;
 201
 202        case STATE_WAIT:
 203                if (event != EVENT_TIMEDOUT)
 204                        break;
 205
 206                if (is_pen_down(touch)) {
 207                        start_tsi(touch);
 208                        touch->state = STATE_BUSY;
 209                } else {
 210                        report_pen_up(touch);
 211                        touch->state = STATE_IDLE;
 212                }
 213                break;
 214        }
 215        return;
 216
 217err_reset:
 218        touch->state = STATE_IDLE;
 219        stop_tsi(touch);
 220        detect_pen_down(touch, 1);
 221}
 222
 223static void da9034_tsi_work(struct work_struct *work)
 224{
 225        struct da9034_touch *touch =
 226                container_of(work, struct da9034_touch, tsi_work.work);
 227
 228        da9034_event_handler(touch, EVENT_TIMEDOUT);
 229}
 230
 231static int da9034_touch_notifier(struct notifier_block *nb,
 232                                 unsigned long event, void *data)
 233{
 234        struct da9034_touch *touch =
 235                container_of(nb, struct da9034_touch, notifier);
 236
 237        if (event & DA9034_EVENT_TSI_READY)
 238                da9034_event_handler(touch, EVENT_TSI_READY);
 239
 240        if ((event & DA9034_EVENT_PEN_DOWN) && touch->state == STATE_IDLE)
 241                da9034_event_handler(touch, EVENT_PEN_DOWN);
 242
 243        return 0;
 244}
 245
 246static int da9034_touch_open(struct input_dev *dev)
 247{
 248        struct da9034_touch *touch = input_get_drvdata(dev);
 249        int ret;
 250
 251        ret = da903x_register_notifier(touch->da9034_dev, &touch->notifier,
 252                        DA9034_EVENT_PEN_DOWN | DA9034_EVENT_TSI_READY);
 253        if (ret)
 254                return -EBUSY;
 255
 256        /* Enable ADC LDO */
 257        ret = da903x_set_bits(touch->da9034_dev,
 258                        DA9034_MANUAL_CTRL, DA9034_LDO_ADC_EN);
 259        if (ret)
 260                return ret;
 261
 262        /* TSI_DELAY: 3 slots, TSI_SKIP: 3 slots */
 263        ret = da903x_write(touch->da9034_dev, DA9034_TSI_CTRL1, 0x1b);
 264        if (ret)
 265                return ret;
 266
 267        ret = da903x_write(touch->da9034_dev, DA9034_TSI_CTRL2, 0x00);
 268        if (ret)
 269                return ret;
 270
 271        touch->state = STATE_IDLE;
 272        detect_pen_down(touch, 1);
 273
 274        return 0;
 275}
 276
 277static void da9034_touch_close(struct input_dev *dev)
 278{
 279        struct da9034_touch *touch = input_get_drvdata(dev);
 280
 281        da903x_unregister_notifier(touch->da9034_dev, &touch->notifier,
 282                        DA9034_EVENT_PEN_DOWN | DA9034_EVENT_TSI_READY);
 283
 284        cancel_delayed_work_sync(&touch->tsi_work);
 285
 286        touch->state = STATE_IDLE;
 287        stop_tsi(touch);
 288        detect_pen_down(touch, 0);
 289
 290        /* Disable ADC LDO */
 291        da903x_clr_bits(touch->da9034_dev,
 292                        DA9034_MANUAL_CTRL, DA9034_LDO_ADC_EN);
 293}
 294
 295
 296static int da9034_touch_probe(struct platform_device *pdev)
 297{
 298        struct da9034_touch_pdata *pdata = dev_get_platdata(&pdev->dev);
 299        struct da9034_touch *touch;
 300        struct input_dev *input_dev;
 301        int error;
 302
 303        touch = devm_kzalloc(&pdev->dev, sizeof(struct da9034_touch),
 304                             GFP_KERNEL);
 305        if (!touch) {
 306                dev_err(&pdev->dev, "failed to allocate driver data\n");
 307                return -ENOMEM;
 308        }
 309
 310        touch->da9034_dev = pdev->dev.parent;
 311
 312        if (pdata) {
 313                touch->interval_ms      = pdata->interval_ms;
 314                touch->x_inverted       = pdata->x_inverted;
 315                touch->y_inverted       = pdata->y_inverted;
 316        } else {
 317                /* fallback into default */
 318                touch->interval_ms      = 10;
 319        }
 320
 321        INIT_DELAYED_WORK(&touch->tsi_work, da9034_tsi_work);
 322        touch->notifier.notifier_call = da9034_touch_notifier;
 323
 324        input_dev = devm_input_allocate_device(&pdev->dev);
 325        if (!input_dev) {
 326                dev_err(&pdev->dev, "failed to allocate input device\n");
 327                return -ENOMEM;
 328        }
 329
 330        input_dev->name         = pdev->name;
 331        input_dev->open         = da9034_touch_open;
 332        input_dev->close        = da9034_touch_close;
 333        input_dev->dev.parent   = &pdev->dev;
 334
 335        __set_bit(EV_ABS, input_dev->evbit);
 336        __set_bit(ABS_X, input_dev->absbit);
 337        __set_bit(ABS_Y, input_dev->absbit);
 338        input_set_abs_params(input_dev, ABS_X, 0, 1023, 0, 0);
 339        input_set_abs_params(input_dev, ABS_Y, 0, 1023, 0, 0);
 340
 341        __set_bit(EV_KEY, input_dev->evbit);
 342        __set_bit(BTN_TOUCH, input_dev->keybit);
 343
 344        touch->input_dev = input_dev;
 345        input_set_drvdata(input_dev, touch);
 346
 347        error = input_register_device(input_dev);
 348        if (error)
 349                return error;
 350
 351        return 0;
 352}
 353
 354static struct platform_driver da9034_touch_driver = {
 355        .driver = {
 356                .name   = "da9034-touch",
 357        },
 358        .probe          = da9034_touch_probe,
 359};
 360module_platform_driver(da9034_touch_driver);
 361
 362MODULE_DESCRIPTION("Touchscreen driver for Dialog Semiconductor DA9034");
 363MODULE_AUTHOR("Eric Miao <eric.miao@marvell.com>, Bin Yang <bin.yang@marvell.com>");
 364MODULE_LICENSE("GPL");
 365MODULE_ALIAS("platform:da9034-touch");
 366