109c434b8SThomas Gleixner // SPDX-License-Identifier: GPL-2.0-only 271f8e38aSDmitry Torokhov #include <linux/delay.h> 371f8e38aSDmitry Torokhov #include <linux/gpio/consumer.h> 45c6a7a62SOlivier Sobrie #include <linux/i2c.h> 55c6a7a62SOlivier Sobrie #include <linux/input.h> 65c6a7a62SOlivier Sobrie #include <linux/input/mt.h> 7f67cc3e9SMarek Vasut #include <linux/input/touchscreen.h> 871f8e38aSDmitry Torokhov #include <linux/interrupt.h> 971f8e38aSDmitry Torokhov #include <linux/module.h> 1049588917SMarek Vasut #include <linux/of_device.h> 11b32fbeaeSSven Van Asbroeck #include <linux/sizes.h> 1271f8e38aSDmitry Torokhov #include <linux/slab.h> 13e3559442SMarek Vasut #include <asm/unaligned.h> 145c6a7a62SOlivier Sobrie 1571f8e38aSDmitry Torokhov #define ILI2XXX_POLL_PERIOD 20 165c6a7a62SOlivier Sobrie 17ef536abdSDmitry Torokhov #define ILI210X_DATA_SIZE 64 18ef536abdSDmitry Torokhov #define ILI211X_DATA_SIZE 43 19ef536abdSDmitry Torokhov #define ILI251X_DATA_SIZE1 31 20ef536abdSDmitry Torokhov #define ILI251X_DATA_SIZE2 20 21ef536abdSDmitry Torokhov 225c6a7a62SOlivier Sobrie /* Touchscreen commands */ 235c6a7a62SOlivier Sobrie #define REG_TOUCHDATA 0x10 245c6a7a62SOlivier Sobrie #define REG_PANEL_INFO 0x20 25*70a7681dSMarek Vasut #define REG_FIRMWARE_VERSION 0x40 26*70a7681dSMarek Vasut #define REG_PROTOCOL_VERSION 0x42 27*70a7681dSMarek Vasut #define REG_KERNEL_VERSION 0x61 28*70a7681dSMarek Vasut #define REG_GET_MODE 0xc0 29*70a7681dSMarek Vasut #define REG_GET_MODE_AP 0x5a 30*70a7681dSMarek Vasut #define REG_GET_MODE_BL 0x55 315c6a7a62SOlivier Sobrie #define REG_CALIBRATE 0xcc 325c6a7a62SOlivier Sobrie 33ef536abdSDmitry Torokhov struct ili2xxx_chip { 34ef536abdSDmitry Torokhov int (*read_reg)(struct i2c_client *client, u8 reg, 35ef536abdSDmitry Torokhov void *buf, size_t len); 36ef536abdSDmitry Torokhov int (*get_touch_data)(struct i2c_client *client, u8 *data); 37ef536abdSDmitry Torokhov bool (*parse_touch_data)(const u8 *data, unsigned int finger, 3860159e9eSMarek Vasut unsigned int *x, unsigned int *y, 3960159e9eSMarek Vasut unsigned int *z); 40ef536abdSDmitry Torokhov bool (*continue_polling)(const u8 *data, bool touch); 41ef536abdSDmitry Torokhov unsigned int max_touches; 42b32fbeaeSSven Van Asbroeck unsigned int resolution; 43cc12ba18SSven Van Asbroeck bool has_calibrate_reg; 44235300edSMarek Vasut bool has_firmware_proto; 4560159e9eSMarek Vasut bool has_pressure_reg; 4649588917SMarek Vasut }; 4749588917SMarek Vasut 485c6a7a62SOlivier Sobrie struct ili210x { 495c6a7a62SOlivier Sobrie struct i2c_client *client; 505c6a7a62SOlivier Sobrie struct input_dev *input; 51201f3c80SMarek Vasut struct gpio_desc *reset_gpio; 52f67cc3e9SMarek Vasut struct touchscreen_properties prop; 53ef536abdSDmitry Torokhov const struct ili2xxx_chip *chip; 54*70a7681dSMarek Vasut u8 version_firmware[8]; 55*70a7681dSMarek Vasut u8 version_kernel[5]; 56*70a7681dSMarek Vasut u8 version_proto[2]; 57*70a7681dSMarek Vasut u8 ic_mode[2]; 5871f8e38aSDmitry Torokhov bool stop; 595c6a7a62SOlivier Sobrie }; 605c6a7a62SOlivier Sobrie 61ef536abdSDmitry Torokhov static int ili210x_read_reg(struct i2c_client *client, 62ef536abdSDmitry Torokhov u8 reg, void *buf, size_t len) 635c6a7a62SOlivier Sobrie { 64ef536abdSDmitry Torokhov struct i2c_msg msg[] = { 655c6a7a62SOlivier Sobrie { 665c6a7a62SOlivier Sobrie .addr = client->addr, 675c6a7a62SOlivier Sobrie .flags = 0, 685c6a7a62SOlivier Sobrie .len = 1, 695c6a7a62SOlivier Sobrie .buf = ®, 705c6a7a62SOlivier Sobrie }, 715c6a7a62SOlivier Sobrie { 725c6a7a62SOlivier Sobrie .addr = client->addr, 735c6a7a62SOlivier Sobrie .flags = I2C_M_RD, 745c6a7a62SOlivier Sobrie .len = len, 755c6a7a62SOlivier Sobrie .buf = buf, 765c6a7a62SOlivier Sobrie } 775c6a7a62SOlivier Sobrie }; 78ef536abdSDmitry Torokhov int error, ret; 795c6a7a62SOlivier Sobrie 80ef536abdSDmitry Torokhov ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg)); 81ef536abdSDmitry Torokhov if (ret != ARRAY_SIZE(msg)) { 82ef536abdSDmitry Torokhov error = ret < 0 ? ret : -EIO; 83ef536abdSDmitry Torokhov dev_err(&client->dev, "%s failed: %d\n", __func__, error); 84ef536abdSDmitry Torokhov return error; 8549588917SMarek Vasut } 8649588917SMarek Vasut 8749588917SMarek Vasut return 0; 8849588917SMarek Vasut } 8949588917SMarek Vasut 90ef536abdSDmitry Torokhov static int ili210x_read_touch_data(struct i2c_client *client, u8 *data) 9149588917SMarek Vasut { 92ef536abdSDmitry Torokhov return ili210x_read_reg(client, REG_TOUCHDATA, 93ef536abdSDmitry Torokhov data, ILI210X_DATA_SIZE); 9449588917SMarek Vasut } 955c6a7a62SOlivier Sobrie 96ef536abdSDmitry Torokhov static bool ili210x_touchdata_to_coords(const u8 *touchdata, 97e3559442SMarek Vasut unsigned int finger, 9860159e9eSMarek Vasut unsigned int *x, unsigned int *y, 9960159e9eSMarek Vasut unsigned int *z) 1005c6a7a62SOlivier Sobrie { 101ac05a8a9SHansem Ro if (!(touchdata[0] & BIT(finger))) 102e3559442SMarek Vasut return false; 103e3559442SMarek Vasut 104e3559442SMarek Vasut *x = get_unaligned_be16(touchdata + 1 + (finger * 4) + 0); 105e3559442SMarek Vasut *y = get_unaligned_be16(touchdata + 1 + (finger * 4) + 2); 106e3559442SMarek Vasut 107e3559442SMarek Vasut return true; 108e3559442SMarek Vasut } 109e3559442SMarek Vasut 110ef536abdSDmitry Torokhov static bool ili210x_check_continue_polling(const u8 *data, bool touch) 111ef536abdSDmitry Torokhov { 112ef536abdSDmitry Torokhov return data[0] & 0xf3; 113ef536abdSDmitry Torokhov } 114ef536abdSDmitry Torokhov 115ef536abdSDmitry Torokhov static const struct ili2xxx_chip ili210x_chip = { 116ef536abdSDmitry Torokhov .read_reg = ili210x_read_reg, 117ef536abdSDmitry Torokhov .get_touch_data = ili210x_read_touch_data, 118ef536abdSDmitry Torokhov .parse_touch_data = ili210x_touchdata_to_coords, 119ef536abdSDmitry Torokhov .continue_polling = ili210x_check_continue_polling, 120ef536abdSDmitry Torokhov .max_touches = 2, 121cc12ba18SSven Van Asbroeck .has_calibrate_reg = true, 122ef536abdSDmitry Torokhov }; 123ef536abdSDmitry Torokhov 124ef536abdSDmitry Torokhov static int ili211x_read_touch_data(struct i2c_client *client, u8 *data) 125ef536abdSDmitry Torokhov { 126ef536abdSDmitry Torokhov s16 sum = 0; 127ef536abdSDmitry Torokhov int error; 128ef536abdSDmitry Torokhov int ret; 129ef536abdSDmitry Torokhov int i; 130ef536abdSDmitry Torokhov 131ef536abdSDmitry Torokhov ret = i2c_master_recv(client, data, ILI211X_DATA_SIZE); 132ef536abdSDmitry Torokhov if (ret != ILI211X_DATA_SIZE) { 133ef536abdSDmitry Torokhov error = ret < 0 ? ret : -EIO; 134ef536abdSDmitry Torokhov dev_err(&client->dev, "%s failed: %d\n", __func__, error); 135ef536abdSDmitry Torokhov return error; 136ef536abdSDmitry Torokhov } 137ef536abdSDmitry Torokhov 138ef536abdSDmitry Torokhov /* This chip uses custom checksum at the end of data */ 139ef536abdSDmitry Torokhov for (i = 0; i < ILI211X_DATA_SIZE - 1; i++) 140ef536abdSDmitry Torokhov sum = (sum + data[i]) & 0xff; 141ef536abdSDmitry Torokhov 142ef536abdSDmitry Torokhov if ((-sum & 0xff) != data[ILI211X_DATA_SIZE - 1]) { 143ef536abdSDmitry Torokhov dev_err(&client->dev, 144ef536abdSDmitry Torokhov "CRC error (crc=0x%02x expected=0x%02x)\n", 145ef536abdSDmitry Torokhov sum, data[ILI211X_DATA_SIZE - 1]); 146ef536abdSDmitry Torokhov return -EIO; 147ef536abdSDmitry Torokhov } 148ef536abdSDmitry Torokhov 149ef536abdSDmitry Torokhov return 0; 150ef536abdSDmitry Torokhov } 151ef536abdSDmitry Torokhov 152ef536abdSDmitry Torokhov static bool ili211x_touchdata_to_coords(const u8 *touchdata, 153eb91ecc9SMarek Vasut unsigned int finger, 15460159e9eSMarek Vasut unsigned int *x, unsigned int *y, 15560159e9eSMarek Vasut unsigned int *z) 156eb91ecc9SMarek Vasut { 157eb91ecc9SMarek Vasut u32 data; 158eb91ecc9SMarek Vasut 159eb91ecc9SMarek Vasut data = get_unaligned_be32(touchdata + 1 + (finger * 4) + 0); 160eb91ecc9SMarek Vasut if (data == 0xffffffff) /* Finger up */ 161eb91ecc9SMarek Vasut return false; 162eb91ecc9SMarek Vasut 163eb91ecc9SMarek Vasut *x = ((touchdata[1 + (finger * 4) + 0] & 0xf0) << 4) | 164eb91ecc9SMarek Vasut touchdata[1 + (finger * 4) + 1]; 165eb91ecc9SMarek Vasut *y = ((touchdata[1 + (finger * 4) + 0] & 0x0f) << 8) | 166eb91ecc9SMarek Vasut touchdata[1 + (finger * 4) + 2]; 167eb91ecc9SMarek Vasut 168eb91ecc9SMarek Vasut return true; 169eb91ecc9SMarek Vasut } 170eb91ecc9SMarek Vasut 171ef536abdSDmitry Torokhov static bool ili211x_decline_polling(const u8 *data, bool touch) 172ef536abdSDmitry Torokhov { 173ef536abdSDmitry Torokhov return false; 174ef536abdSDmitry Torokhov } 175ef536abdSDmitry Torokhov 176ef536abdSDmitry Torokhov static const struct ili2xxx_chip ili211x_chip = { 177ef536abdSDmitry Torokhov .read_reg = ili210x_read_reg, 178ef536abdSDmitry Torokhov .get_touch_data = ili211x_read_touch_data, 179ef536abdSDmitry Torokhov .parse_touch_data = ili211x_touchdata_to_coords, 180ef536abdSDmitry Torokhov .continue_polling = ili211x_decline_polling, 181ef536abdSDmitry Torokhov .max_touches = 10, 182b32fbeaeSSven Van Asbroeck .resolution = 2048, 183ef536abdSDmitry Torokhov }; 184ef536abdSDmitry Torokhov 185d0c5e7d4SLuca Weiss static bool ili212x_touchdata_to_coords(const u8 *touchdata, 186d0c5e7d4SLuca Weiss unsigned int finger, 18760159e9eSMarek Vasut unsigned int *x, unsigned int *y, 18860159e9eSMarek Vasut unsigned int *z) 189d0c5e7d4SLuca Weiss { 190d0c5e7d4SLuca Weiss u16 val; 191d0c5e7d4SLuca Weiss 192d0c5e7d4SLuca Weiss val = get_unaligned_be16(touchdata + 3 + (finger * 5) + 0); 193d0c5e7d4SLuca Weiss if (!(val & BIT(15))) /* Touch indication */ 194d0c5e7d4SLuca Weiss return false; 195d0c5e7d4SLuca Weiss 196d0c5e7d4SLuca Weiss *x = val & 0x3fff; 197d0c5e7d4SLuca Weiss *y = get_unaligned_be16(touchdata + 3 + (finger * 5) + 2); 198d0c5e7d4SLuca Weiss 199d0c5e7d4SLuca Weiss return true; 200d0c5e7d4SLuca Weiss } 201d0c5e7d4SLuca Weiss 202d0c5e7d4SLuca Weiss static bool ili212x_check_continue_polling(const u8 *data, bool touch) 203d0c5e7d4SLuca Weiss { 204d0c5e7d4SLuca Weiss return touch; 205d0c5e7d4SLuca Weiss } 206d0c5e7d4SLuca Weiss 207d0c5e7d4SLuca Weiss static const struct ili2xxx_chip ili212x_chip = { 208d0c5e7d4SLuca Weiss .read_reg = ili210x_read_reg, 209d0c5e7d4SLuca Weiss .get_touch_data = ili210x_read_touch_data, 210d0c5e7d4SLuca Weiss .parse_touch_data = ili212x_touchdata_to_coords, 211d0c5e7d4SLuca Weiss .continue_polling = ili212x_check_continue_polling, 212d0c5e7d4SLuca Weiss .max_touches = 10, 213d0c5e7d4SLuca Weiss .has_calibrate_reg = true, 214d0c5e7d4SLuca Weiss }; 215d0c5e7d4SLuca Weiss 216ef536abdSDmitry Torokhov static int ili251x_read_reg(struct i2c_client *client, 217ef536abdSDmitry Torokhov u8 reg, void *buf, size_t len) 218ef536abdSDmitry Torokhov { 219ef536abdSDmitry Torokhov int error; 220ef536abdSDmitry Torokhov int ret; 221ef536abdSDmitry Torokhov 222ef536abdSDmitry Torokhov ret = i2c_master_send(client, ®, 1); 223ef536abdSDmitry Torokhov if (ret == 1) { 224ef536abdSDmitry Torokhov usleep_range(5000, 5500); 225ef536abdSDmitry Torokhov 226ef536abdSDmitry Torokhov ret = i2c_master_recv(client, buf, len); 227ef536abdSDmitry Torokhov if (ret == len) 228ef536abdSDmitry Torokhov return 0; 229ef536abdSDmitry Torokhov } 230ef536abdSDmitry Torokhov 231ef536abdSDmitry Torokhov error = ret < 0 ? ret : -EIO; 232ef536abdSDmitry Torokhov dev_err(&client->dev, "%s failed: %d\n", __func__, error); 233ef536abdSDmitry Torokhov return ret; 234ef536abdSDmitry Torokhov } 235ef536abdSDmitry Torokhov 236ef536abdSDmitry Torokhov static int ili251x_read_touch_data(struct i2c_client *client, u8 *data) 237ef536abdSDmitry Torokhov { 238ef536abdSDmitry Torokhov int error; 239ef536abdSDmitry Torokhov 240ef536abdSDmitry Torokhov error = ili251x_read_reg(client, REG_TOUCHDATA, 241ef536abdSDmitry Torokhov data, ILI251X_DATA_SIZE1); 242ef536abdSDmitry Torokhov if (!error && data[0] == 2) { 243ef536abdSDmitry Torokhov error = i2c_master_recv(client, data + ILI251X_DATA_SIZE1, 244ef536abdSDmitry Torokhov ILI251X_DATA_SIZE2); 245ef536abdSDmitry Torokhov if (error >= 0 && error != ILI251X_DATA_SIZE2) 246ef536abdSDmitry Torokhov error = -EIO; 247ef536abdSDmitry Torokhov } 248ef536abdSDmitry Torokhov 249ef536abdSDmitry Torokhov return error; 250ef536abdSDmitry Torokhov } 251ef536abdSDmitry Torokhov 252ef536abdSDmitry Torokhov static bool ili251x_touchdata_to_coords(const u8 *touchdata, 25349588917SMarek Vasut unsigned int finger, 25460159e9eSMarek Vasut unsigned int *x, unsigned int *y, 25560159e9eSMarek Vasut unsigned int *z) 25649588917SMarek Vasut { 257ef536abdSDmitry Torokhov u16 val; 258ef536abdSDmitry Torokhov 259ef536abdSDmitry Torokhov val = get_unaligned_be16(touchdata + 1 + (finger * 5) + 0); 260ef536abdSDmitry Torokhov if (!(val & BIT(15))) /* Touch indication */ 26149588917SMarek Vasut return false; 26249588917SMarek Vasut 263ef536abdSDmitry Torokhov *x = val & 0x3fff; 26449588917SMarek Vasut *y = get_unaligned_be16(touchdata + 1 + (finger * 5) + 2); 26560159e9eSMarek Vasut *z = touchdata[1 + (finger * 5) + 4]; 26649588917SMarek Vasut 26749588917SMarek Vasut return true; 26849588917SMarek Vasut } 26949588917SMarek Vasut 270ef536abdSDmitry Torokhov static bool ili251x_check_continue_polling(const u8 *data, bool touch) 271ef536abdSDmitry Torokhov { 272ef536abdSDmitry Torokhov return touch; 273ef536abdSDmitry Torokhov } 274ef536abdSDmitry Torokhov 275ef536abdSDmitry Torokhov static const struct ili2xxx_chip ili251x_chip = { 276ef536abdSDmitry Torokhov .read_reg = ili251x_read_reg, 277ef536abdSDmitry Torokhov .get_touch_data = ili251x_read_touch_data, 278ef536abdSDmitry Torokhov .parse_touch_data = ili251x_touchdata_to_coords, 279ef536abdSDmitry Torokhov .continue_polling = ili251x_check_continue_polling, 280ef536abdSDmitry Torokhov .max_touches = 10, 281cc12ba18SSven Van Asbroeck .has_calibrate_reg = true, 282235300edSMarek Vasut .has_firmware_proto = true, 28360159e9eSMarek Vasut .has_pressure_reg = true, 284ef536abdSDmitry Torokhov }; 285ef536abdSDmitry Torokhov 286e3559442SMarek Vasut static bool ili210x_report_events(struct ili210x *priv, u8 *touchdata) 287e3559442SMarek Vasut { 288e3559442SMarek Vasut struct input_dev *input = priv->input; 2895c6a7a62SOlivier Sobrie int i; 290ef536abdSDmitry Torokhov bool contact = false, touch; 29160159e9eSMarek Vasut unsigned int x = 0, y = 0, z = 0; 2925c6a7a62SOlivier Sobrie 293ef536abdSDmitry Torokhov for (i = 0; i < priv->chip->max_touches; i++) { 29460159e9eSMarek Vasut touch = priv->chip->parse_touch_data(touchdata, i, &x, &y, &z); 29549588917SMarek Vasut 296f67cc3e9SMarek Vasut input_mt_slot(input, i); 297ef536abdSDmitry Torokhov if (input_mt_report_slot_state(input, MT_TOOL_FINGER, touch)) { 298ef536abdSDmitry Torokhov touchscreen_report_pos(input, &priv->prop, x, y, true); 29960159e9eSMarek Vasut if (priv->chip->has_pressure_reg) 30060159e9eSMarek Vasut input_report_abs(input, ABS_MT_PRESSURE, z); 301ef536abdSDmitry Torokhov contact = true; 302ef536abdSDmitry Torokhov } 3035c6a7a62SOlivier Sobrie } 3045c6a7a62SOlivier Sobrie 3055c6a7a62SOlivier Sobrie input_mt_report_pointer_emulation(input, false); 3065c6a7a62SOlivier Sobrie input_sync(input); 307e3559442SMarek Vasut 30849588917SMarek Vasut return contact; 3095c6a7a62SOlivier Sobrie } 3105c6a7a62SOlivier Sobrie 31171f8e38aSDmitry Torokhov static irqreturn_t ili210x_irq(int irq, void *irq_data) 3125c6a7a62SOlivier Sobrie { 31371f8e38aSDmitry Torokhov struct ili210x *priv = irq_data; 3145c6a7a62SOlivier Sobrie struct i2c_client *client = priv->client; 315ef536abdSDmitry Torokhov const struct ili2xxx_chip *chip = priv->chip; 316ef536abdSDmitry Torokhov u8 touchdata[ILI210X_DATA_SIZE] = { 0 }; 317ef536abdSDmitry Torokhov bool keep_polling; 318e3559442SMarek Vasut bool touch; 31971f8e38aSDmitry Torokhov int error; 3205c6a7a62SOlivier Sobrie 32171f8e38aSDmitry Torokhov do { 322ef536abdSDmitry Torokhov error = chip->get_touch_data(client, touchdata); 3235c6a7a62SOlivier Sobrie if (error) { 3245c6a7a62SOlivier Sobrie dev_err(&client->dev, 325ef536abdSDmitry Torokhov "Unable to get touch data: %d\n", error); 32671f8e38aSDmitry Torokhov break; 3275c6a7a62SOlivier Sobrie } 3285c6a7a62SOlivier Sobrie 329e3559442SMarek Vasut touch = ili210x_report_events(priv, touchdata); 330ef536abdSDmitry Torokhov keep_polling = chip->continue_polling(touchdata, touch); 331ef536abdSDmitry Torokhov if (keep_polling) 33271f8e38aSDmitry Torokhov msleep(ILI2XXX_POLL_PERIOD); 333ef536abdSDmitry Torokhov } while (!priv->stop && keep_polling); 3345c6a7a62SOlivier Sobrie 3355c6a7a62SOlivier Sobrie return IRQ_HANDLED; 3365c6a7a62SOlivier Sobrie } 3375c6a7a62SOlivier Sobrie 338235300edSMarek Vasut static int ili251x_firmware_update_resolution(struct device *dev) 339235300edSMarek Vasut { 340235300edSMarek Vasut struct i2c_client *client = to_i2c_client(dev); 341235300edSMarek Vasut struct ili210x *priv = i2c_get_clientdata(client); 342235300edSMarek Vasut u16 resx, resy; 343235300edSMarek Vasut u8 rs[10]; 344235300edSMarek Vasut int error; 345235300edSMarek Vasut 346235300edSMarek Vasut /* The firmware update blob might have changed the resolution. */ 347235300edSMarek Vasut error = priv->chip->read_reg(client, REG_PANEL_INFO, &rs, sizeof(rs)); 348235300edSMarek Vasut if (error) 349235300edSMarek Vasut return error; 350235300edSMarek Vasut 351235300edSMarek Vasut resx = le16_to_cpup((__le16 *)rs); 352235300edSMarek Vasut resy = le16_to_cpup((__le16 *)(rs + 2)); 353235300edSMarek Vasut 354235300edSMarek Vasut /* The value reported by the firmware is invalid. */ 355235300edSMarek Vasut if (!resx || resx == 0xffff || !resy || resy == 0xffff) 356235300edSMarek Vasut return -EINVAL; 357235300edSMarek Vasut 358235300edSMarek Vasut input_abs_set_max(priv->input, ABS_X, resx - 1); 359235300edSMarek Vasut input_abs_set_max(priv->input, ABS_Y, resy - 1); 360235300edSMarek Vasut input_abs_set_max(priv->input, ABS_MT_POSITION_X, resx - 1); 361235300edSMarek Vasut input_abs_set_max(priv->input, ABS_MT_POSITION_Y, resy - 1); 362235300edSMarek Vasut 363235300edSMarek Vasut return 0; 364235300edSMarek Vasut } 365235300edSMarek Vasut 366*70a7681dSMarek Vasut static ssize_t ili251x_firmware_update_firmware_version(struct device *dev) 367*70a7681dSMarek Vasut { 368*70a7681dSMarek Vasut struct i2c_client *client = to_i2c_client(dev); 369*70a7681dSMarek Vasut struct ili210x *priv = i2c_get_clientdata(client); 370*70a7681dSMarek Vasut int error; 371*70a7681dSMarek Vasut u8 fw[8]; 372*70a7681dSMarek Vasut 373*70a7681dSMarek Vasut /* Get firmware version */ 374*70a7681dSMarek Vasut error = priv->chip->read_reg(client, REG_FIRMWARE_VERSION, 375*70a7681dSMarek Vasut &fw, sizeof(fw)); 376*70a7681dSMarek Vasut if (!error) 377*70a7681dSMarek Vasut memcpy(priv->version_firmware, fw, sizeof(fw)); 378*70a7681dSMarek Vasut 379*70a7681dSMarek Vasut return error; 380*70a7681dSMarek Vasut } 381*70a7681dSMarek Vasut 382*70a7681dSMarek Vasut static ssize_t ili251x_firmware_update_kernel_version(struct device *dev) 383*70a7681dSMarek Vasut { 384*70a7681dSMarek Vasut struct i2c_client *client = to_i2c_client(dev); 385*70a7681dSMarek Vasut struct ili210x *priv = i2c_get_clientdata(client); 386*70a7681dSMarek Vasut int error; 387*70a7681dSMarek Vasut u8 kv[5]; 388*70a7681dSMarek Vasut 389*70a7681dSMarek Vasut /* Get kernel version */ 390*70a7681dSMarek Vasut error = priv->chip->read_reg(client, REG_KERNEL_VERSION, 391*70a7681dSMarek Vasut &kv, sizeof(kv)); 392*70a7681dSMarek Vasut if (!error) 393*70a7681dSMarek Vasut memcpy(priv->version_kernel, kv, sizeof(kv)); 394*70a7681dSMarek Vasut 395*70a7681dSMarek Vasut return error; 396*70a7681dSMarek Vasut } 397*70a7681dSMarek Vasut 398*70a7681dSMarek Vasut static ssize_t ili251x_firmware_update_protocol_version(struct device *dev) 399*70a7681dSMarek Vasut { 400*70a7681dSMarek Vasut struct i2c_client *client = to_i2c_client(dev); 401*70a7681dSMarek Vasut struct ili210x *priv = i2c_get_clientdata(client); 402*70a7681dSMarek Vasut int error; 403*70a7681dSMarek Vasut u8 pv[2]; 404*70a7681dSMarek Vasut 405*70a7681dSMarek Vasut /* Get protocol version */ 406*70a7681dSMarek Vasut error = priv->chip->read_reg(client, REG_PROTOCOL_VERSION, 407*70a7681dSMarek Vasut &pv, sizeof(pv)); 408*70a7681dSMarek Vasut if (!error) 409*70a7681dSMarek Vasut memcpy(priv->version_proto, pv, sizeof(pv)); 410*70a7681dSMarek Vasut 411*70a7681dSMarek Vasut return error; 412*70a7681dSMarek Vasut } 413*70a7681dSMarek Vasut 414*70a7681dSMarek Vasut static ssize_t ili251x_firmware_update_ic_mode(struct device *dev) 415*70a7681dSMarek Vasut { 416*70a7681dSMarek Vasut struct i2c_client *client = to_i2c_client(dev); 417*70a7681dSMarek Vasut struct ili210x *priv = i2c_get_clientdata(client); 418*70a7681dSMarek Vasut int error; 419*70a7681dSMarek Vasut u8 md[2]; 420*70a7681dSMarek Vasut 421*70a7681dSMarek Vasut /* Get chip boot mode */ 422*70a7681dSMarek Vasut error = priv->chip->read_reg(client, REG_GET_MODE, &md, sizeof(md)); 423*70a7681dSMarek Vasut if (!error) 424*70a7681dSMarek Vasut memcpy(priv->ic_mode, md, sizeof(md)); 425*70a7681dSMarek Vasut 426*70a7681dSMarek Vasut return error; 427*70a7681dSMarek Vasut } 428*70a7681dSMarek Vasut 429235300edSMarek Vasut static int ili251x_firmware_update_cached_state(struct device *dev) 430235300edSMarek Vasut { 431235300edSMarek Vasut struct i2c_client *client = to_i2c_client(dev); 432235300edSMarek Vasut struct ili210x *priv = i2c_get_clientdata(client); 433235300edSMarek Vasut int error; 434235300edSMarek Vasut 435235300edSMarek Vasut if (!priv->chip->has_firmware_proto) 436235300edSMarek Vasut return 0; 437235300edSMarek Vasut 438235300edSMarek Vasut /* Wait for firmware to boot and stabilize itself. */ 439235300edSMarek Vasut msleep(200); 440235300edSMarek Vasut 441235300edSMarek Vasut /* Firmware does report valid information. */ 442235300edSMarek Vasut error = ili251x_firmware_update_resolution(dev); 443235300edSMarek Vasut if (error) 444235300edSMarek Vasut return error; 445235300edSMarek Vasut 446*70a7681dSMarek Vasut error = ili251x_firmware_update_firmware_version(dev); 447*70a7681dSMarek Vasut if (error) 448*70a7681dSMarek Vasut return error; 449*70a7681dSMarek Vasut 450*70a7681dSMarek Vasut error = ili251x_firmware_update_kernel_version(dev); 451*70a7681dSMarek Vasut if (error) 452*70a7681dSMarek Vasut return error; 453*70a7681dSMarek Vasut 454*70a7681dSMarek Vasut error = ili251x_firmware_update_protocol_version(dev); 455*70a7681dSMarek Vasut if (error) 456*70a7681dSMarek Vasut return error; 457*70a7681dSMarek Vasut 458*70a7681dSMarek Vasut error = ili251x_firmware_update_ic_mode(dev); 459*70a7681dSMarek Vasut if (error) 460*70a7681dSMarek Vasut return error; 461*70a7681dSMarek Vasut 462235300edSMarek Vasut return 0; 463235300edSMarek Vasut } 464235300edSMarek Vasut 465*70a7681dSMarek Vasut static ssize_t ili251x_firmware_version_show(struct device *dev, 466*70a7681dSMarek Vasut struct device_attribute *attr, 467*70a7681dSMarek Vasut char *buf) 468*70a7681dSMarek Vasut { 469*70a7681dSMarek Vasut struct i2c_client *client = to_i2c_client(dev); 470*70a7681dSMarek Vasut struct ili210x *priv = i2c_get_clientdata(client); 471*70a7681dSMarek Vasut u8 *fw = priv->version_firmware; 472*70a7681dSMarek Vasut 473*70a7681dSMarek Vasut return sysfs_emit(buf, "%02x%02x.%02x%02x.%02x%02x.%02x%02x\n", 474*70a7681dSMarek Vasut fw[0], fw[1], fw[2], fw[3], 475*70a7681dSMarek Vasut fw[4], fw[5], fw[6], fw[7]); 476*70a7681dSMarek Vasut } 477*70a7681dSMarek Vasut static DEVICE_ATTR(firmware_version, 0444, ili251x_firmware_version_show, NULL); 478*70a7681dSMarek Vasut 479*70a7681dSMarek Vasut static ssize_t ili251x_kernel_version_show(struct device *dev, 480*70a7681dSMarek Vasut struct device_attribute *attr, 481*70a7681dSMarek Vasut char *buf) 482*70a7681dSMarek Vasut { 483*70a7681dSMarek Vasut struct i2c_client *client = to_i2c_client(dev); 484*70a7681dSMarek Vasut struct ili210x *priv = i2c_get_clientdata(client); 485*70a7681dSMarek Vasut u8 *kv = priv->version_kernel; 486*70a7681dSMarek Vasut 487*70a7681dSMarek Vasut return sysfs_emit(buf, "%02x.%02x.%02x.%02x.%02x\n", 488*70a7681dSMarek Vasut kv[0], kv[1], kv[2], kv[3], kv[4]); 489*70a7681dSMarek Vasut } 490*70a7681dSMarek Vasut static DEVICE_ATTR(kernel_version, 0444, ili251x_kernel_version_show, NULL); 491*70a7681dSMarek Vasut 492*70a7681dSMarek Vasut static ssize_t ili251x_protocol_version_show(struct device *dev, 493*70a7681dSMarek Vasut struct device_attribute *attr, 494*70a7681dSMarek Vasut char *buf) 495*70a7681dSMarek Vasut { 496*70a7681dSMarek Vasut struct i2c_client *client = to_i2c_client(dev); 497*70a7681dSMarek Vasut struct ili210x *priv = i2c_get_clientdata(client); 498*70a7681dSMarek Vasut u8 *pv = priv->version_proto; 499*70a7681dSMarek Vasut 500*70a7681dSMarek Vasut return sysfs_emit(buf, "%02x.%02x\n", pv[0], pv[1]); 501*70a7681dSMarek Vasut } 502*70a7681dSMarek Vasut static DEVICE_ATTR(protocol_version, 0444, ili251x_protocol_version_show, NULL); 503*70a7681dSMarek Vasut 504*70a7681dSMarek Vasut static ssize_t ili251x_mode_show(struct device *dev, 505*70a7681dSMarek Vasut struct device_attribute *attr, char *buf) 506*70a7681dSMarek Vasut { 507*70a7681dSMarek Vasut struct i2c_client *client = to_i2c_client(dev); 508*70a7681dSMarek Vasut struct ili210x *priv = i2c_get_clientdata(client); 509*70a7681dSMarek Vasut u8 *md = priv->ic_mode; 510*70a7681dSMarek Vasut char *mode = "AP"; 511*70a7681dSMarek Vasut 512*70a7681dSMarek Vasut if (md[0] == REG_GET_MODE_AP) /* Application Mode */ 513*70a7681dSMarek Vasut mode = "AP"; 514*70a7681dSMarek Vasut else if (md[0] == REG_GET_MODE_BL) /* BootLoader Mode */ 515*70a7681dSMarek Vasut mode = "BL"; 516*70a7681dSMarek Vasut else /* Unknown Mode */ 517*70a7681dSMarek Vasut mode = "??"; 518*70a7681dSMarek Vasut 519*70a7681dSMarek Vasut return sysfs_emit(buf, "%02x.%02x:%s\n", md[0], md[1], mode); 520*70a7681dSMarek Vasut } 521*70a7681dSMarek Vasut static DEVICE_ATTR(mode, 0444, ili251x_mode_show, NULL); 522*70a7681dSMarek Vasut 5235c6a7a62SOlivier Sobrie static ssize_t ili210x_calibrate(struct device *dev, 5245c6a7a62SOlivier Sobrie struct device_attribute *attr, 5255c6a7a62SOlivier Sobrie const char *buf, size_t count) 5265c6a7a62SOlivier Sobrie { 5275c6a7a62SOlivier Sobrie struct i2c_client *client = to_i2c_client(dev); 5285c6a7a62SOlivier Sobrie struct ili210x *priv = i2c_get_clientdata(client); 5295c6a7a62SOlivier Sobrie unsigned long calibrate; 5305c6a7a62SOlivier Sobrie int rc; 5315c6a7a62SOlivier Sobrie u8 cmd = REG_CALIBRATE; 5325c6a7a62SOlivier Sobrie 5335c6a7a62SOlivier Sobrie if (kstrtoul(buf, 10, &calibrate)) 5345c6a7a62SOlivier Sobrie return -EINVAL; 5355c6a7a62SOlivier Sobrie 5365c6a7a62SOlivier Sobrie if (calibrate > 1) 5375c6a7a62SOlivier Sobrie return -EINVAL; 5385c6a7a62SOlivier Sobrie 5395c6a7a62SOlivier Sobrie if (calibrate) { 5405c6a7a62SOlivier Sobrie rc = i2c_master_send(priv->client, &cmd, sizeof(cmd)); 5415c6a7a62SOlivier Sobrie if (rc != sizeof(cmd)) 5425c6a7a62SOlivier Sobrie return -EIO; 5435c6a7a62SOlivier Sobrie } 5445c6a7a62SOlivier Sobrie 5455c6a7a62SOlivier Sobrie return count; 5465c6a7a62SOlivier Sobrie } 547b27c0d0cSDmitry Torokhov static DEVICE_ATTR(calibrate, S_IWUSR, NULL, ili210x_calibrate); 5485c6a7a62SOlivier Sobrie 5495c6a7a62SOlivier Sobrie static struct attribute *ili210x_attributes[] = { 5505c6a7a62SOlivier Sobrie &dev_attr_calibrate.attr, 551*70a7681dSMarek Vasut &dev_attr_firmware_version.attr, 552*70a7681dSMarek Vasut &dev_attr_kernel_version.attr, 553*70a7681dSMarek Vasut &dev_attr_protocol_version.attr, 554*70a7681dSMarek Vasut &dev_attr_mode.attr, 5555c6a7a62SOlivier Sobrie NULL, 5565c6a7a62SOlivier Sobrie }; 5575c6a7a62SOlivier Sobrie 558*70a7681dSMarek Vasut static umode_t ili210x_attributes_visible(struct kobject *kobj, 559cc12ba18SSven Van Asbroeck struct attribute *attr, int index) 560cc12ba18SSven Van Asbroeck { 561cc12ba18SSven Van Asbroeck struct device *dev = kobj_to_dev(kobj); 562cc12ba18SSven Van Asbroeck struct i2c_client *client = to_i2c_client(dev); 563cc12ba18SSven Van Asbroeck struct ili210x *priv = i2c_get_clientdata(client); 564cc12ba18SSven Van Asbroeck 565*70a7681dSMarek Vasut /* Calibrate is present on all ILI2xxx which have calibrate register */ 566*70a7681dSMarek Vasut if (attr == &dev_attr_calibrate.attr) 567fbd1ec00SLuca Weiss return priv->chip->has_calibrate_reg ? attr->mode : 0; 568*70a7681dSMarek Vasut 569*70a7681dSMarek Vasut /* Firmware/Kernel/Protocol/BootMode is implememted only for ILI251x */ 570*70a7681dSMarek Vasut if (!priv->chip->has_firmware_proto) 571*70a7681dSMarek Vasut return 0; 572*70a7681dSMarek Vasut 573*70a7681dSMarek Vasut return attr->mode; 574cc12ba18SSven Van Asbroeck } 575cc12ba18SSven Van Asbroeck 5765c6a7a62SOlivier Sobrie static const struct attribute_group ili210x_attr_group = { 5775c6a7a62SOlivier Sobrie .attrs = ili210x_attributes, 578*70a7681dSMarek Vasut .is_visible = ili210x_attributes_visible, 5795c6a7a62SOlivier Sobrie }; 5805c6a7a62SOlivier Sobrie 581201f3c80SMarek Vasut static void ili210x_power_down(void *data) 582201f3c80SMarek Vasut { 583201f3c80SMarek Vasut struct gpio_desc *reset_gpio = data; 584201f3c80SMarek Vasut 585201f3c80SMarek Vasut gpiod_set_value_cansleep(reset_gpio, 1); 586201f3c80SMarek Vasut } 587201f3c80SMarek Vasut 58871f8e38aSDmitry Torokhov static void ili210x_stop(void *data) 5891bdec5d9SMarek Vasut { 5901bdec5d9SMarek Vasut struct ili210x *priv = data; 5911bdec5d9SMarek Vasut 59271f8e38aSDmitry Torokhov /* Tell ISR to quit even if there is a contact. */ 59371f8e38aSDmitry Torokhov priv->stop = true; 5941bdec5d9SMarek Vasut } 5951bdec5d9SMarek Vasut 5965298cc4cSBill Pemberton static int ili210x_i2c_probe(struct i2c_client *client, 5975c6a7a62SOlivier Sobrie const struct i2c_device_id *id) 5985c6a7a62SOlivier Sobrie { 5995c6a7a62SOlivier Sobrie struct device *dev = &client->dev; 600ef536abdSDmitry Torokhov const struct ili2xxx_chip *chip; 6015c6a7a62SOlivier Sobrie struct ili210x *priv; 602201f3c80SMarek Vasut struct gpio_desc *reset_gpio; 6035c6a7a62SOlivier Sobrie struct input_dev *input; 6045c6a7a62SOlivier Sobrie int error; 605b32fbeaeSSven Van Asbroeck unsigned int max_xy; 6065c6a7a62SOlivier Sobrie 6075c6a7a62SOlivier Sobrie dev_dbg(dev, "Probing for ILI210X I2C Touschreen driver"); 6085c6a7a62SOlivier Sobrie 609ef536abdSDmitry Torokhov chip = device_get_match_data(dev); 610ef536abdSDmitry Torokhov if (!chip && id) 611ef536abdSDmitry Torokhov chip = (const struct ili2xxx_chip *)id->driver_data; 612ef536abdSDmitry Torokhov if (!chip) { 613ef536abdSDmitry Torokhov dev_err(&client->dev, "unknown device model\n"); 614ef536abdSDmitry Torokhov return -ENODEV; 615ef536abdSDmitry Torokhov } 616ef536abdSDmitry Torokhov 6175c6a7a62SOlivier Sobrie if (client->irq <= 0) { 6185c6a7a62SOlivier Sobrie dev_err(dev, "No IRQ!\n"); 6195c6a7a62SOlivier Sobrie return -EINVAL; 6205c6a7a62SOlivier Sobrie } 6215c6a7a62SOlivier Sobrie 622201f3c80SMarek Vasut reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); 623201f3c80SMarek Vasut if (IS_ERR(reset_gpio)) 624201f3c80SMarek Vasut return PTR_ERR(reset_gpio); 625201f3c80SMarek Vasut 626201f3c80SMarek Vasut if (reset_gpio) { 627201f3c80SMarek Vasut error = devm_add_action_or_reset(dev, ili210x_power_down, 628201f3c80SMarek Vasut reset_gpio); 629201f3c80SMarek Vasut if (error) 630201f3c80SMarek Vasut return error; 631201f3c80SMarek Vasut 632201f3c80SMarek Vasut usleep_range(50, 100); 633201f3c80SMarek Vasut gpiod_set_value_cansleep(reset_gpio, 0); 634201f3c80SMarek Vasut msleep(100); 635201f3c80SMarek Vasut } 636201f3c80SMarek Vasut 63712294577SMarek Vasut priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); 63812294577SMarek Vasut if (!priv) 63912294577SMarek Vasut return -ENOMEM; 64012294577SMarek Vasut 64112294577SMarek Vasut input = devm_input_allocate_device(dev); 64212294577SMarek Vasut if (!input) 64312294577SMarek Vasut return -ENOMEM; 64412294577SMarek Vasut 64512294577SMarek Vasut priv->client = client; 64612294577SMarek Vasut priv->input = input; 64712294577SMarek Vasut priv->reset_gpio = reset_gpio; 648ef536abdSDmitry Torokhov priv->chip = chip; 64912294577SMarek Vasut i2c_set_clientdata(client, priv); 65012294577SMarek Vasut 6515c6a7a62SOlivier Sobrie /* Setup input device */ 6525c6a7a62SOlivier Sobrie input->name = "ILI210x Touchscreen"; 6535c6a7a62SOlivier Sobrie input->id.bustype = BUS_I2C; 6545c6a7a62SOlivier Sobrie 6555c6a7a62SOlivier Sobrie /* Multi touch */ 656b32fbeaeSSven Van Asbroeck max_xy = (chip->resolution ?: SZ_64K) - 1; 657b32fbeaeSSven Van Asbroeck input_set_abs_params(input, ABS_MT_POSITION_X, 0, max_xy, 0, 0); 658b32fbeaeSSven Van Asbroeck input_set_abs_params(input, ABS_MT_POSITION_Y, 0, max_xy, 0, 0); 65960159e9eSMarek Vasut if (priv->chip->has_pressure_reg) 66060159e9eSMarek Vasut input_set_abs_params(input, ABS_MT_PRESSURE, 0, 0xa, 0, 0); 661235300edSMarek Vasut error = ili251x_firmware_update_cached_state(dev); 662235300edSMarek Vasut if (error) { 663235300edSMarek Vasut dev_err(dev, "Unable to cache firmware information, err: %d\n", 664235300edSMarek Vasut error); 665235300edSMarek Vasut return error; 666235300edSMarek Vasut } 667f67cc3e9SMarek Vasut touchscreen_parse_properties(input, true, &priv->prop); 66843f06a4cSDmitry Torokhov 669ef536abdSDmitry Torokhov error = input_mt_init_slots(input, priv->chip->max_touches, 670ef536abdSDmitry Torokhov INPUT_MT_DIRECT); 67143f06a4cSDmitry Torokhov if (error) { 67243f06a4cSDmitry Torokhov dev_err(dev, "Unable to set up slots, err: %d\n", error); 67343f06a4cSDmitry Torokhov return error; 67443f06a4cSDmitry Torokhov } 6755c6a7a62SOlivier Sobrie 67671f8e38aSDmitry Torokhov error = devm_request_threaded_irq(dev, client->irq, NULL, ili210x_irq, 67771f8e38aSDmitry Torokhov IRQF_ONESHOT, client->name, priv); 6785c6a7a62SOlivier Sobrie if (error) { 6795c6a7a62SOlivier Sobrie dev_err(dev, "Unable to request touchscreen IRQ, err: %d\n", 6805c6a7a62SOlivier Sobrie error); 6811bdec5d9SMarek Vasut return error; 6825c6a7a62SOlivier Sobrie } 6835c6a7a62SOlivier Sobrie 68471f8e38aSDmitry Torokhov error = devm_add_action_or_reset(dev, ili210x_stop, priv); 68571f8e38aSDmitry Torokhov if (error) 68671f8e38aSDmitry Torokhov return error; 68771f8e38aSDmitry Torokhov 688576057bfSDmitry Torokhov error = devm_device_add_group(dev, &ili210x_attr_group); 6895c6a7a62SOlivier Sobrie if (error) { 6905c6a7a62SOlivier Sobrie dev_err(dev, "Unable to create sysfs attributes, err: %d\n", 6915c6a7a62SOlivier Sobrie error); 6921bdec5d9SMarek Vasut return error; 6935c6a7a62SOlivier Sobrie } 6945c6a7a62SOlivier Sobrie 6955c6a7a62SOlivier Sobrie error = input_register_device(priv->input); 6965c6a7a62SOlivier Sobrie if (error) { 697971bd8faSMasanari Iida dev_err(dev, "Cannot register input device, err: %d\n", error); 698576057bfSDmitry Torokhov return error; 6995c6a7a62SOlivier Sobrie } 7005c6a7a62SOlivier Sobrie 7015c6a7a62SOlivier Sobrie return 0; 7025c6a7a62SOlivier Sobrie } 7035c6a7a62SOlivier Sobrie 7045c6a7a62SOlivier Sobrie static const struct i2c_device_id ili210x_i2c_id[] = { 705ef536abdSDmitry Torokhov { "ili210x", (long)&ili210x_chip }, 706ef536abdSDmitry Torokhov { "ili2117", (long)&ili211x_chip }, 707d0c5e7d4SLuca Weiss { "ili2120", (long)&ili212x_chip }, 708ef536abdSDmitry Torokhov { "ili251x", (long)&ili251x_chip }, 7095c6a7a62SOlivier Sobrie { } 7105c6a7a62SOlivier Sobrie }; 7115c6a7a62SOlivier Sobrie MODULE_DEVICE_TABLE(i2c, ili210x_i2c_id); 7125c6a7a62SOlivier Sobrie 713c5d0e4b5SMarek Vasut static const struct of_device_id ili210x_dt_ids[] = { 714ef536abdSDmitry Torokhov { .compatible = "ilitek,ili210x", .data = &ili210x_chip }, 715ef536abdSDmitry Torokhov { .compatible = "ilitek,ili2117", .data = &ili211x_chip }, 716d0c5e7d4SLuca Weiss { .compatible = "ilitek,ili2120", .data = &ili212x_chip }, 717ef536abdSDmitry Torokhov { .compatible = "ilitek,ili251x", .data = &ili251x_chip }, 718ef536abdSDmitry Torokhov { } 719c5d0e4b5SMarek Vasut }; 720c5d0e4b5SMarek Vasut MODULE_DEVICE_TABLE(of, ili210x_dt_ids); 721c5d0e4b5SMarek Vasut 7225c6a7a62SOlivier Sobrie static struct i2c_driver ili210x_ts_driver = { 7235c6a7a62SOlivier Sobrie .driver = { 7245c6a7a62SOlivier Sobrie .name = "ili210x_i2c", 725c5d0e4b5SMarek Vasut .of_match_table = ili210x_dt_ids, 7265c6a7a62SOlivier Sobrie }, 7275c6a7a62SOlivier Sobrie .id_table = ili210x_i2c_id, 7285c6a7a62SOlivier Sobrie .probe = ili210x_i2c_probe, 7295c6a7a62SOlivier Sobrie }; 7305c6a7a62SOlivier Sobrie 7315c6a7a62SOlivier Sobrie module_i2c_driver(ili210x_ts_driver); 7325c6a7a62SOlivier Sobrie 7335c6a7a62SOlivier Sobrie MODULE_AUTHOR("Olivier Sobrie <olivier@sobrie.be>"); 7345c6a7a62SOlivier Sobrie MODULE_DESCRIPTION("ILI210X I2C Touchscreen Driver"); 7355c6a7a62SOlivier Sobrie MODULE_LICENSE("GPL"); 736