linux/drivers/input/touchscreen/exc3000.c
<<
>>
Prefs
   1/*
   2 * Driver for I2C connected EETI EXC3000 multiple touch controller
   3 *
   4 * Copyright (C) 2017 Ahmet Inan <inan@distec.de>
   5 *
   6 * minimal implementation based on egalax_ts.c and egalax_i2c.c
   7 *
   8 * This program is free software; you can redistribute it and/or modify
   9 * it under the terms of the GNU General Public License version 2 as
  10 * published by the Free Software Foundation.
  11 */
  12
  13#include <linux/bitops.h>
  14#include <linux/device.h>
  15#include <linux/i2c.h>
  16#include <linux/input.h>
  17#include <linux/input/mt.h>
  18#include <linux/input/touchscreen.h>
  19#include <linux/interrupt.h>
  20#include <linux/module.h>
  21#include <linux/of.h>
  22#include <linux/timer.h>
  23#include <asm/unaligned.h>
  24
  25#define EXC3000_NUM_SLOTS               10
  26#define EXC3000_SLOTS_PER_FRAME         5
  27#define EXC3000_LEN_FRAME               66
  28#define EXC3000_LEN_POINT               10
  29#define EXC3000_MT_EVENT                6
  30#define EXC3000_TIMEOUT_MS              100
  31
  32struct exc3000_data {
  33        struct i2c_client *client;
  34        struct input_dev *input;
  35        struct touchscreen_properties prop;
  36        struct timer_list timer;
  37        u8 buf[2 * EXC3000_LEN_FRAME];
  38};
  39
  40static void exc3000_report_slots(struct input_dev *input,
  41                                 struct touchscreen_properties *prop,
  42                                 const u8 *buf, int num)
  43{
  44        for (; num--; buf += EXC3000_LEN_POINT) {
  45                if (buf[0] & BIT(0)) {
  46                        input_mt_slot(input, buf[1]);
  47                        input_mt_report_slot_state(input, MT_TOOL_FINGER, true);
  48                        touchscreen_report_pos(input, prop,
  49                                               get_unaligned_le16(buf + 2),
  50                                               get_unaligned_le16(buf + 4),
  51                                               true);
  52                }
  53        }
  54}
  55
  56static void exc3000_timer(struct timer_list *t)
  57{
  58        struct exc3000_data *data = from_timer(data, t, timer);
  59
  60        input_mt_sync_frame(data->input);
  61        input_sync(data->input);
  62}
  63
  64static int exc3000_read_frame(struct i2c_client *client, u8 *buf)
  65{
  66        int ret;
  67
  68        ret = i2c_master_send(client, "'", 2);
  69        if (ret < 0)
  70                return ret;
  71
  72        if (ret != 2)
  73                return -EIO;
  74
  75        ret = i2c_master_recv(client, buf, EXC3000_LEN_FRAME);
  76        if (ret < 0)
  77                return ret;
  78
  79        if (ret != EXC3000_LEN_FRAME)
  80                return -EIO;
  81
  82        if (get_unaligned_le16(buf) != EXC3000_LEN_FRAME ||
  83                        buf[2] != EXC3000_MT_EVENT)
  84                return -EINVAL;
  85
  86        return 0;
  87}
  88
  89static int exc3000_read_data(struct i2c_client *client,
  90                             u8 *buf, int *n_slots)
  91{
  92        int error;
  93
  94        error = exc3000_read_frame(client, buf);
  95        if (error)
  96                return error;
  97
  98        *n_slots = buf[3];
  99        if (!*n_slots || *n_slots > EXC3000_NUM_SLOTS)
 100                return -EINVAL;
 101
 102        if (*n_slots > EXC3000_SLOTS_PER_FRAME) {
 103                /* Read 2nd frame to get the rest of the contacts. */
 104                error = exc3000_read_frame(client, buf + EXC3000_LEN_FRAME);
 105                if (error)
 106                        return error;
 107
 108                /* 2nd chunk must have number of contacts set to 0. */
 109                if (buf[EXC3000_LEN_FRAME + 3] != 0)
 110                        return -EINVAL;
 111        }
 112
 113        return 0;
 114}
 115
 116static irqreturn_t exc3000_interrupt(int irq, void *dev_id)
 117{
 118        struct exc3000_data *data = dev_id;
 119        struct input_dev *input = data->input;
 120        u8 *buf = data->buf;
 121        int slots, total_slots;
 122        int error;
 123
 124        error = exc3000_read_data(data->client, buf, &total_slots);
 125        if (error) {
 126                /* Schedule a timer to release "stuck" contacts */
 127                mod_timer(&data->timer,
 128                          jiffies + msecs_to_jiffies(EXC3000_TIMEOUT_MS));
 129                goto out;
 130        }
 131
 132        /*
 133         * We read full state successfully, no contacts will be "stuck".
 134         */
 135        del_timer_sync(&data->timer);
 136
 137        while (total_slots > 0) {
 138                slots = min(total_slots, EXC3000_SLOTS_PER_FRAME);
 139                exc3000_report_slots(input, &data->prop, buf + 4, slots);
 140                total_slots -= slots;
 141                buf += EXC3000_LEN_FRAME;
 142        }
 143
 144        input_mt_sync_frame(input);
 145        input_sync(input);
 146
 147out:
 148        return IRQ_HANDLED;
 149}
 150
 151static int exc3000_probe(struct i2c_client *client,
 152                         const struct i2c_device_id *id)
 153{
 154        struct exc3000_data *data;
 155        struct input_dev *input;
 156        int error;
 157
 158        data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);
 159        if (!data)
 160                return -ENOMEM;
 161
 162        data->client = client;
 163        timer_setup(&data->timer, exc3000_timer, 0);
 164
 165        input = devm_input_allocate_device(&client->dev);
 166        if (!input)
 167                return -ENOMEM;
 168
 169        data->input = input;
 170
 171        input->name = "EETI EXC3000 Touch Screen";
 172        input->id.bustype = BUS_I2C;
 173
 174        input_set_abs_params(input, ABS_MT_POSITION_X, 0, 4095, 0, 0);
 175        input_set_abs_params(input, ABS_MT_POSITION_Y, 0, 4095, 0, 0);
 176        touchscreen_parse_properties(input, true, &data->prop);
 177
 178        error = input_mt_init_slots(input, EXC3000_NUM_SLOTS,
 179                                    INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED);
 180        if (error)
 181                return error;
 182
 183        error = input_register_device(input);
 184        if (error)
 185                return error;
 186
 187        error = devm_request_threaded_irq(&client->dev, client->irq,
 188                                          NULL, exc3000_interrupt, IRQF_ONESHOT,
 189                                          client->name, data);
 190        if (error)
 191                return error;
 192
 193        return 0;
 194}
 195
 196static const struct i2c_device_id exc3000_id[] = {
 197        { "exc3000", 0 },
 198        { }
 199};
 200MODULE_DEVICE_TABLE(i2c, exc3000_id);
 201
 202#ifdef CONFIG_OF
 203static const struct of_device_id exc3000_of_match[] = {
 204        { .compatible = "eeti,exc3000" },
 205        { }
 206};
 207MODULE_DEVICE_TABLE(of, exc3000_of_match);
 208#endif
 209
 210static struct i2c_driver exc3000_driver = {
 211        .driver = {
 212                .name   = "exc3000",
 213                .of_match_table = of_match_ptr(exc3000_of_match),
 214        },
 215        .id_table       = exc3000_id,
 216        .probe          = exc3000_probe,
 217};
 218
 219module_i2c_driver(exc3000_driver);
 220
 221MODULE_AUTHOR("Ahmet Inan <inan@distec.de>");
 222MODULE_DESCRIPTION("I2C connected EETI EXC3000 multiple touch controller driver");
 223MODULE_LICENSE("GPL v2");
 224