| 1 | |
| 2 | #include <linux/crc-ccitt.h>
|
| 3 | #include <linux/delay.h>
|
| 4 | #include <linux/gpio/consumer.h>
|
| 5 | #include <linux/i2c.h>
|
| 6 | #include <linux/ihex.h>
|
| 7 | #include <linux/input.h>
|
| 8 | #include <linux/input/mt.h>
|
| 9 | #include <linux/input/touchscreen.h>
|
| 10 | #include <linux/interrupt.h>
|
| 11 | #include <linux/module.h>
|
| 12 | #include <linux/sizes.h>
|
| 13 | #include <linux/slab.h>
|
| 14 | #include <linux/unaligned.h>
|
| 15 |
|
| 16 | #define ILI2XXX_POLL_PERIOD 15
|
| 17 |
|
| 18 | #define ILI210X_DATA_SIZE 64
|
| 19 | #define ILI211X_DATA_SIZE 43
|
| 20 | #define ILI251X_DATA_SIZE1 31
|
| 21 | #define ILI251X_DATA_SIZE2 20
|
| 22 |
|
| 23 |
|
| 24 | #define REG_TOUCHDATA 0x10
|
| 25 | #define REG_PANEL_INFO 0x20
|
| 26 | #define REG_FIRMWARE_VERSION 0x40
|
| 27 | #define REG_PROTOCOL_VERSION 0x42
|
| 28 | #define REG_KERNEL_VERSION 0x61
|
| 29 | #define REG_IC_BUSY 0x80
|
| 30 | #define REG_IC_BUSY_NOT_BUSY 0x50
|
| 31 | #define REG_GET_MODE 0xc0
|
| 32 | #define REG_GET_MODE_AP 0x5a
|
| 33 | #define REG_GET_MODE_BL 0x55
|
| 34 | #define REG_SET_MODE_AP 0xc1
|
| 35 | #define REG_SET_MODE_BL 0xc2
|
| 36 | #define REG_WRITE_DATA 0xc3
|
| 37 | #define REG_WRITE_ENABLE 0xc4
|
| 38 | #define REG_READ_DATA_CRC 0xc7
|
| 39 | #define REG_CALIBRATE 0xcc
|
| 40 |
|
| 41 | #define ILI251X_FW_FILENAME "ilitek/ili251x.bin"
|
| 42 |
|
| 43 | struct ili2xxx_chip {
|
| 44 | int (*read_reg)(struct i2c_client *client, u8 reg,
|
| 45 | void *buf, size_t len);
|
| 46 | int (*get_touch_data)(struct i2c_client *client, u8 *data);
|
| 47 | bool (*parse_touch_data)(const u8 *data, unsigned int finger,
|
| 48 | unsigned int *x, unsigned int *y,
|
| 49 | unsigned int *z);
|
| 50 | bool (*continue_polling)(const u8 *data, bool touch);
|
| 51 | unsigned int max_touches;
|
| 52 | unsigned int resolution;
|
| 53 | bool has_calibrate_reg;
|
| 54 | bool has_firmware_proto;
|
| 55 | bool has_pressure_reg;
|
| 56 | };
|
| 57 |
|
| 58 | struct ili210x {
|
| 59 | struct i2c_client *client;
|
| 60 | struct input_dev *input;
|
| 61 | struct gpio_desc *reset_gpio;
|
| 62 | struct touchscreen_properties prop;
|
| 63 | const struct ili2xxx_chip *chip;
|
| 64 | u8 version_firmware[8];
|
| 65 | u8 version_kernel[5];
|
| 66 | u8 version_proto[2];
|
| 67 | u8 ic_mode[2];
|
| 68 | bool stop;
|
| 69 | };
|
| 70 |
|
| 71 | static int ili210x_read_reg(struct i2c_client *client,
|
| 72 | u8 reg, void *buf, size_t len)
|
| 73 | {
|
| 74 | struct i2c_msg msg[] = {
|
| 75 | {
|
| 76 | .addr = client->addr,
|
| 77 | .flags = 0,
|
| 78 | .len = 1,
|
| 79 | .buf = ®,
|
| 80 | },
|
| 81 | {
|
| 82 | .addr = client->addr,
|
| 83 | .flags = I2C_M_RD,
|
| 84 | .len = len,
|
| 85 | .buf = buf,
|
| 86 | }
|
| 87 | };
|
| 88 | int error, ret;
|
| 89 |
|
| 90 | ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));
|
| 91 | if (ret != ARRAY_SIZE(msg)) {
|
| 92 | error = ret < 0 ? ret : -EIO;
|
| 93 | dev_err(&client->dev, "%s failed: %d\n", __func__, error);
|
| 94 | return error;
|
| 95 | }
|
| 96 |
|
| 97 | return 0;
|
| 98 | }
|
| 99 |
|
| 100 | static int ili210x_read_touch_data(struct i2c_client *client, u8 *data)
|
| 101 | {
|
| 102 | return ili210x_read_reg(client, REG_TOUCHDATA,
|
| 103 | data, ILI210X_DATA_SIZE);
|
| 104 | }
|
| 105 |
|
| 106 | static bool ili210x_touchdata_to_coords(const u8 *touchdata,
|
| 107 | unsigned int finger,
|
| 108 | unsigned int *x, unsigned int *y,
|
| 109 | unsigned int *z)
|
| 110 | {
|
| 111 | if (!(touchdata[0] & BIT(finger)))
|
| 112 | return false;
|
| 113 |
|
| 114 | *x = get_unaligned_be16(touchdata + 1 + (finger * 4) + 0);
|
| 115 | *y = get_unaligned_be16(touchdata + 1 + (finger * 4) + 2);
|
| 116 |
|
| 117 | return true;
|
| 118 | }
|
| 119 |
|
| 120 | static bool ili210x_check_continue_polling(const u8 *data, bool touch)
|
| 121 | {
|
| 122 | return data[0] & 0xf3;
|
| 123 | }
|
| 124 |
|
| 125 | static const struct ili2xxx_chip ili210x_chip = {
|
| 126 | .read_reg = ili210x_read_reg,
|
| 127 | .get_touch_data = ili210x_read_touch_data,
|
| 128 | .parse_touch_data = ili210x_touchdata_to_coords,
|
| 129 | .continue_polling = ili210x_check_continue_polling,
|
| 130 | .max_touches = 2,
|
| 131 | .has_calibrate_reg = true,
|
| 132 | };
|
| 133 |
|
| 134 | static int ili211x_read_touch_data(struct i2c_client *client, u8 *data)
|
| 135 | {
|
| 136 | s16 sum = 0;
|
| 137 | int error;
|
| 138 | int ret;
|
| 139 | int i;
|
| 140 |
|
| 141 | ret = i2c_master_recv(client, data, ILI211X_DATA_SIZE);
|
| 142 | if (ret != ILI211X_DATA_SIZE) {
|
| 143 | error = ret < 0 ? ret : -EIO;
|
| 144 | dev_err(&client->dev, "%s failed: %d\n", __func__, error);
|
| 145 | return error;
|
| 146 | }
|
| 147 |
|
| 148 |
|
| 149 | for (i = 0; i < ILI211X_DATA_SIZE - 1; i++)
|
| 150 | sum = (sum + data[i]) & 0xff;
|
| 151 |
|
| 152 | if ((-sum & 0xff) != data[ILI211X_DATA_SIZE - 1]) {
|
| 153 | dev_err(&client->dev,
|
| 154 | "CRC error (crc=0x%02x expected=0x%02x)\n",
|
| 155 | sum, data[ILI211X_DATA_SIZE - 1]);
|
| 156 | return -EIO;
|
| 157 | }
|
| 158 |
|
| 159 | return 0;
|
| 160 | }
|
| 161 |
|
| 162 | static bool ili211x_touchdata_to_coords(const u8 *touchdata,
|
| 163 | unsigned int finger,
|
| 164 | unsigned int *x, unsigned int *y,
|
| 165 | unsigned int *z)
|
| 166 | {
|
| 167 | u32 data;
|
| 168 |
|
| 169 | data = get_unaligned_be32(touchdata + 1 + (finger * 4) + 0);
|
| 170 | if (data == 0xffffffff)
|
| 171 | return false;
|
| 172 |
|
| 173 | *x = ((touchdata[1 + (finger * 4) + 0] & 0xf0) << 4) |
|
| 174 | touchdata[1 + (finger * 4) + 1];
|
| 175 | *y = ((touchdata[1 + (finger * 4) + 0] & 0x0f) << 8) |
|
| 176 | touchdata[1 + (finger * 4) + 2];
|
| 177 |
|
| 178 | return true;
|
| 179 | }
|
| 180 |
|
| 181 | static bool ili211x_decline_polling(const u8 *data, bool touch)
|
| 182 | {
|
| 183 | return false;
|
| 184 | }
|
| 185 |
|
| 186 | static const struct ili2xxx_chip ili211x_chip = {
|
| 187 | .read_reg = ili210x_read_reg,
|
| 188 | .get_touch_data = ili211x_read_touch_data,
|
| 189 | .parse_touch_data = ili211x_touchdata_to_coords,
|
| 190 | .continue_polling = ili211x_decline_polling,
|
| 191 | .max_touches = 10,
|
| 192 | .resolution = 2048,
|
| 193 | };
|
| 194 |
|
| 195 | static bool ili212x_touchdata_to_coords(const u8 *touchdata,
|
| 196 | unsigned int finger,
|
| 197 | unsigned int *x, unsigned int *y,
|
| 198 | unsigned int *z)
|
| 199 | {
|
| 200 | u16 val;
|
| 201 |
|
| 202 | val = get_unaligned_be16(touchdata + 3 + (finger * 5) + 0);
|
| 203 | if (!(val & BIT(15)))
|
| 204 | return false;
|
| 205 |
|
| 206 | *x = val & 0x3fff;
|
| 207 | *y = get_unaligned_be16(touchdata + 3 + (finger * 5) + 2);
|
| 208 |
|
| 209 | return true;
|
| 210 | }
|
| 211 |
|
| 212 | static bool ili212x_check_continue_polling(const u8 *data, bool touch)
|
| 213 | {
|
| 214 | return touch;
|
| 215 | }
|
| 216 |
|
| 217 | static const struct ili2xxx_chip ili212x_chip = {
|
| 218 | .read_reg = ili210x_read_reg,
|
| 219 | .get_touch_data = ili210x_read_touch_data,
|
| 220 | .parse_touch_data = ili212x_touchdata_to_coords,
|
| 221 | .continue_polling = ili212x_check_continue_polling,
|
| 222 | .max_touches = 10,
|
| 223 | .has_calibrate_reg = true,
|
| 224 | };
|
| 225 |
|
| 226 | static int ili251x_read_reg_common(struct i2c_client *client,
|
| 227 | u8 reg, void *buf, size_t len,
|
| 228 | unsigned int delay)
|
| 229 | {
|
| 230 | int error;
|
| 231 | int ret;
|
| 232 |
|
| 233 | ret = i2c_master_send(client, ®, 1);
|
| 234 | if (ret == 1) {
|
| 235 | if (delay)
|
| 236 | usleep_range(delay, delay + 500);
|
| 237 |
|
| 238 | ret = i2c_master_recv(client, buf, len);
|
| 239 | if (ret == len)
|
| 240 | return 0;
|
| 241 | }
|
| 242 |
|
| 243 | error = ret < 0 ? ret : -EIO;
|
| 244 | dev_err(&client->dev, "%s failed: %d\n", __func__, error);
|
| 245 | return ret;
|
| 246 | }
|
| 247 |
|
| 248 | static int ili251x_read_reg(struct i2c_client *client,
|
| 249 | u8 reg, void *buf, size_t len)
|
| 250 | {
|
| 251 | return ili251x_read_reg_common(client, reg, buf, len, 5000);
|
| 252 | }
|
| 253 |
|
| 254 | static int ili251x_read_touch_data(struct i2c_client *client, u8 *data)
|
| 255 | {
|
| 256 | int error;
|
| 257 |
|
| 258 | error = ili251x_read_reg_common(client, REG_TOUCHDATA,
|
| 259 | data, ILI251X_DATA_SIZE1, 0);
|
| 260 | if (!error && data[0] == 2) {
|
| 261 | error = i2c_master_recv(client, data + ILI251X_DATA_SIZE1,
|
| 262 | ILI251X_DATA_SIZE2);
|
| 263 | if (error >= 0)
|
| 264 | error = error == ILI251X_DATA_SIZE2 ? 0 : -EIO;
|
| 265 | }
|
| 266 |
|
| 267 | return error;
|
| 268 | }
|
| 269 |
|
| 270 | static bool ili251x_touchdata_to_coords(const u8 *touchdata,
|
| 271 | unsigned int finger,
|
| 272 | unsigned int *x, unsigned int *y,
|
| 273 | unsigned int *z)
|
| 274 | {
|
| 275 | u16 val;
|
| 276 |
|
| 277 | val = get_unaligned_be16(touchdata + 1 + (finger * 5) + 0);
|
| 278 | if (!(val & BIT(15)))
|
| 279 | return false;
|
| 280 |
|
| 281 | *x = val & 0x3fff;
|
| 282 | *y = get_unaligned_be16(touchdata + 1 + (finger * 5) + 2);
|
| 283 | *z = touchdata[1 + (finger * 5) + 4];
|
| 284 |
|
| 285 | return true;
|
| 286 | }
|
| 287 |
|
| 288 | static bool ili251x_check_continue_polling(const u8 *data, bool touch)
|
| 289 | {
|
| 290 | return touch;
|
| 291 | }
|
| 292 |
|
| 293 | static const struct ili2xxx_chip ili251x_chip = {
|
| 294 | .read_reg = ili251x_read_reg,
|
| 295 | .get_touch_data = ili251x_read_touch_data,
|
| 296 | .parse_touch_data = ili251x_touchdata_to_coords,
|
| 297 | .continue_polling = ili251x_check_continue_polling,
|
| 298 | .max_touches = 10,
|
| 299 | .has_calibrate_reg = true,
|
| 300 | .has_firmware_proto = true,
|
| 301 | .has_pressure_reg = true,
|
| 302 | };
|
| 303 |
|
| 304 | static bool ili210x_report_events(struct ili210x *priv, u8 *touchdata)
|
| 305 | {
|
| 306 | struct input_dev *input = priv->input;
|
| 307 | int i;
|
| 308 | bool contact = false, touch;
|
| 309 | unsigned int x = 0, y = 0, z = 0;
|
| 310 |
|
| 311 | for (i = 0; i < priv->chip->max_touches; i++) {
|
| 312 | touch = priv->chip->parse_touch_data(touchdata, i, &x, &y, &z);
|
| 313 |
|
| 314 | input_mt_slot(input, i);
|
| 315 | if (input_mt_report_slot_state(input, MT_TOOL_FINGER, touch)) {
|
| 316 | touchscreen_report_pos(input, &priv->prop, x, y, true);
|
| 317 | if (priv->chip->has_pressure_reg)
|
| 318 | input_report_abs(input, ABS_MT_PRESSURE, z);
|
| 319 | contact = true;
|
| 320 | }
|
| 321 | }
|
| 322 |
|
| 323 | input_mt_report_pointer_emulation(input, false);
|
| 324 | input_sync(input);
|
| 325 |
|
| 326 | return contact;
|
| 327 | }
|
| 328 |
|
| 329 | static void ili210x_process_events(struct ili210x *priv)
|
| 330 | {
|
| 331 | struct i2c_client *client = priv->client;
|
| 332 | const struct ili2xxx_chip *chip = priv->chip;
|
| 333 | u8 touchdata[ILI210X_DATA_SIZE] = { 0 };
|
| 334 | bool keep_polling;
|
| 335 | ktime_t time_next;
|
| 336 | s64 time_delta;
|
| 337 | bool touch;
|
| 338 | int error;
|
| 339 |
|
| 340 | do {
|
| 341 | time_next = ktime_add_ms(ktime_get(), ILI2XXX_POLL_PERIOD);
|
| 342 | error = chip->get_touch_data(client, touchdata);
|
| 343 | if (error) {
|
| 344 | dev_err(&client->dev,
|
| 345 | "Unable to get touch data: %d\n", error);
|
| 346 | break;
|
| 347 | }
|
| 348 |
|
| 349 | touch = ili210x_report_events(priv, touchdata);
|
| 350 | keep_polling = chip->continue_polling(touchdata, touch);
|
| 351 | if (keep_polling) {
|
| 352 | time_delta = ktime_us_delta(time_next, ktime_get());
|
| 353 | if (time_delta > 0)
|
| 354 | usleep_range(time_delta, time_delta + 1000);
|
| 355 | }
|
| 356 | } while (!priv->stop && keep_polling);
|
| 357 | }
|
| 358 |
|
| 359 | static irqreturn_t ili210x_irq(int irq, void *irq_data)
|
| 360 | {
|
| 361 | struct ili210x *priv = irq_data;
|
| 362 |
|
| 363 | ili210x_process_events(priv);
|
| 364 |
|
| 365 | return IRQ_HANDLED;
|
| 366 | };
|
| 367 |
|
| 368 | static void ili210x_work_i2c_poll(struct input_dev *input)
|
| 369 | {
|
| 370 | struct ili210x *priv = input_get_drvdata(input);
|
| 371 |
|
| 372 | ili210x_process_events(priv);
|
| 373 | }
|
| 374 |
|
| 375 | static int ili251x_firmware_update_resolution(struct device *dev)
|
| 376 | {
|
| 377 | struct i2c_client *client = to_i2c_client(dev);
|
| 378 | struct ili210x *priv = i2c_get_clientdata(client);
|
| 379 | u16 resx, resy;
|
| 380 | u8 rs[10];
|
| 381 | int error;
|
| 382 |
|
| 383 |
|
| 384 | error = priv->chip->read_reg(client, REG_PANEL_INFO, &rs, sizeof(rs));
|
| 385 | if (!error) {
|
| 386 | resx = le16_to_cpup((__le16 *)rs);
|
| 387 | resy = le16_to_cpup((__le16 *)(rs + 2));
|
| 388 |
|
| 389 |
|
| 390 | if (!resx || resx == 0xffff || !resy || resy == 0xffff)
|
| 391 | error = -EINVAL;
|
| 392 | }
|
| 393 |
|
| 394 | |
| 395 | * In case of error, the firmware might be stuck in bootloader mode,
|
| 396 | * e.g. after a failed firmware update. Set maximum resolution, but
|
| 397 | * do not fail to probe, so the user can re-trigger the firmware
|
| 398 | * update and recover the touch controller.
|
| 399 |
|
| 400 | if (error) {
|
| 401 | dev_warn(dev, "Invalid resolution reported by controller.\n");
|
| 402 | resx = 16384;
|
| 403 | resy = 16384;
|
| 404 | }
|
| 405 |
|
| 406 | input_abs_set_max(priv->input, ABS_X, resx - 1);
|
| 407 | input_abs_set_max(priv->input, ABS_Y, resy - 1);
|
| 408 | input_abs_set_max(priv->input, ABS_MT_POSITION_X, resx - 1);
|
| 409 | input_abs_set_max(priv->input, ABS_MT_POSITION_Y, resy - 1);
|
| 410 |
|
| 411 | return error;
|
| 412 | }
|
| 413 |
|
| 414 | static ssize_t ili251x_firmware_update_firmware_version(struct device *dev)
|
| 415 | {
|
| 416 | struct i2c_client *client = to_i2c_client(dev);
|
| 417 | struct ili210x *priv = i2c_get_clientdata(client);
|
| 418 | int error;
|
| 419 | u8 fw[8];
|
| 420 |
|
| 421 |
|
| 422 | error = priv->chip->read_reg(client, REG_FIRMWARE_VERSION,
|
| 423 | &fw, sizeof(fw));
|
| 424 | if (!error)
|
| 425 | memcpy(priv->version_firmware, fw, sizeof(fw));
|
| 426 |
|
| 427 | return error;
|
| 428 | }
|
| 429 |
|
| 430 | static ssize_t ili251x_firmware_update_kernel_version(struct device *dev)
|
| 431 | {
|
| 432 | struct i2c_client *client = to_i2c_client(dev);
|
| 433 | struct ili210x *priv = i2c_get_clientdata(client);
|
| 434 | int error;
|
| 435 | u8 kv[5];
|
| 436 |
|
| 437 |
|
| 438 | error = priv->chip->read_reg(client, REG_KERNEL_VERSION,
|
| 439 | &kv, sizeof(kv));
|
| 440 | if (!error)
|
| 441 | memcpy(priv->version_kernel, kv, sizeof(kv));
|
| 442 |
|
| 443 | return error;
|
| 444 | }
|
| 445 |
|
| 446 | static ssize_t ili251x_firmware_update_protocol_version(struct device *dev)
|
| 447 | {
|
| 448 | struct i2c_client *client = to_i2c_client(dev);
|
| 449 | struct ili210x *priv = i2c_get_clientdata(client);
|
| 450 | int error;
|
| 451 | u8 pv[2];
|
| 452 |
|
| 453 |
|
| 454 | error = priv->chip->read_reg(client, REG_PROTOCOL_VERSION,
|
| 455 | &pv, sizeof(pv));
|
| 456 | if (!error)
|
| 457 | memcpy(priv->version_proto, pv, sizeof(pv));
|
| 458 |
|
| 459 | return error;
|
| 460 | }
|
| 461 |
|
| 462 | static ssize_t ili251x_firmware_update_ic_mode(struct device *dev)
|
| 463 | {
|
| 464 | struct i2c_client *client = to_i2c_client(dev);
|
| 465 | struct ili210x *priv = i2c_get_clientdata(client);
|
| 466 | int error;
|
| 467 | u8 md[2];
|
| 468 |
|
| 469 |
|
| 470 | error = priv->chip->read_reg(client, REG_GET_MODE, &md, sizeof(md));
|
| 471 | if (!error)
|
| 472 | memcpy(priv->ic_mode, md, sizeof(md));
|
| 473 |
|
| 474 | return error;
|
| 475 | }
|
| 476 |
|
| 477 | static int ili251x_firmware_update_cached_state(struct device *dev)
|
| 478 | {
|
| 479 | struct i2c_client *client = to_i2c_client(dev);
|
| 480 | struct ili210x *priv = i2c_get_clientdata(client);
|
| 481 | int error;
|
| 482 |
|
| 483 | if (!priv->chip->has_firmware_proto)
|
| 484 | return 0;
|
| 485 |
|
| 486 |
|
| 487 | msleep(200);
|
| 488 |
|
| 489 |
|
| 490 | error = ili251x_firmware_update_resolution(dev);
|
| 491 | if (error)
|
| 492 | return error;
|
| 493 |
|
| 494 | error = ili251x_firmware_update_firmware_version(dev);
|
| 495 | if (error)
|
| 496 | return error;
|
| 497 |
|
| 498 | error = ili251x_firmware_update_kernel_version(dev);
|
| 499 | if (error)
|
| 500 | return error;
|
| 501 |
|
| 502 | error = ili251x_firmware_update_protocol_version(dev);
|
| 503 | if (error)
|
| 504 | return error;
|
| 505 |
|
| 506 | error = ili251x_firmware_update_ic_mode(dev);
|
| 507 | if (error)
|
| 508 | return error;
|
| 509 |
|
| 510 | return 0;
|
| 511 | }
|
| 512 |
|
| 513 | static ssize_t ili251x_firmware_version_show(struct device *dev,
|
| 514 | struct device_attribute *attr,
|
| 515 | char *buf)
|
| 516 | {
|
| 517 | struct i2c_client *client = to_i2c_client(dev);
|
| 518 | struct ili210x *priv = i2c_get_clientdata(client);
|
| 519 | u8 *fw = priv->version_firmware;
|
| 520 |
|
| 521 | return sysfs_emit(buf, "%02x%02x.%02x%02x.%02x%02x.%02x%02x\n",
|
| 522 | fw[0], fw[1], fw[2], fw[3],
|
| 523 | fw[4], fw[5], fw[6], fw[7]);
|
| 524 | }
|
| 525 | static DEVICE_ATTR(firmware_version, 0444, ili251x_firmware_version_show, NULL);
|
| 526 |
|
| 527 | static ssize_t ili251x_kernel_version_show(struct device *dev,
|
| 528 | struct device_attribute *attr,
|
| 529 | char *buf)
|
| 530 | {
|
| 531 | struct i2c_client *client = to_i2c_client(dev);
|
| 532 | struct ili210x *priv = i2c_get_clientdata(client);
|
| 533 | u8 *kv = priv->version_kernel;
|
| 534 |
|
| 535 | return sysfs_emit(buf, "%02x.%02x.%02x.%02x.%02x\n",
|
| 536 | kv[0], kv[1], kv[2], kv[3], kv[4]);
|
| 537 | }
|
| 538 | static DEVICE_ATTR(kernel_version, 0444, ili251x_kernel_version_show, NULL);
|
| 539 |
|
| 540 | static ssize_t ili251x_protocol_version_show(struct device *dev,
|
| 541 | struct device_attribute *attr,
|
| 542 | char *buf)
|
| 543 | {
|
| 544 | struct i2c_client *client = to_i2c_client(dev);
|
| 545 | struct ili210x *priv = i2c_get_clientdata(client);
|
| 546 | u8 *pv = priv->version_proto;
|
| 547 |
|
| 548 | return sysfs_emit(buf, "%02x.%02x\n", pv[0], pv[1]);
|
| 549 | }
|
| 550 | static DEVICE_ATTR(protocol_version, 0444, ili251x_protocol_version_show, NULL);
|
| 551 |
|
| 552 | static ssize_t ili251x_mode_show(struct device *dev,
|
| 553 | struct device_attribute *attr, char *buf)
|
| 554 | {
|
| 555 | struct i2c_client *client = to_i2c_client(dev);
|
| 556 | struct ili210x *priv = i2c_get_clientdata(client);
|
| 557 | u8 *md = priv->ic_mode;
|
| 558 | char *mode = "AP";
|
| 559 |
|
| 560 | if (md[0] == REG_GET_MODE_AP)
|
| 561 | mode = "AP";
|
| 562 | else if (md[0] == REG_GET_MODE_BL)
|
| 563 | mode = "BL";
|
| 564 | else
|
| 565 | mode = "??";
|
| 566 |
|
| 567 | return sysfs_emit(buf, "%02x.%02x:%s\n", md[0], md[1], mode);
|
| 568 | }
|
| 569 | static DEVICE_ATTR(mode, 0444, ili251x_mode_show, NULL);
|
| 570 |
|
| 571 | static ssize_t ili210x_calibrate(struct device *dev,
|
| 572 | struct device_attribute *attr,
|
| 573 | const char *buf, size_t count)
|
| 574 | {
|
| 575 | struct i2c_client *client = to_i2c_client(dev);
|
| 576 | struct ili210x *priv = i2c_get_clientdata(client);
|
| 577 | unsigned long calibrate;
|
| 578 | int rc;
|
| 579 | u8 cmd = REG_CALIBRATE;
|
| 580 |
|
| 581 | if (kstrtoul(buf, 10, &calibrate))
|
| 582 | return -EINVAL;
|
| 583 |
|
| 584 | if (calibrate > 1)
|
| 585 | return -EINVAL;
|
| 586 |
|
| 587 | if (calibrate) {
|
| 588 | rc = i2c_master_send(priv->client, &cmd, sizeof(cmd));
|
| 589 | if (rc != sizeof(cmd))
|
| 590 | return -EIO;
|
| 591 | }
|
| 592 |
|
| 593 | return count;
|
| 594 | }
|
| 595 | static DEVICE_ATTR(calibrate, S_IWUSR, NULL, ili210x_calibrate);
|
| 596 |
|
| 597 | static const u8 *ili251x_firmware_to_buffer(const struct firmware *fw,
|
| 598 | u16 *ac_end, u16 *df_end)
|
| 599 | {
|
| 600 | const struct ihex_binrec *rec;
|
| 601 | u32 fw_addr, fw_last_addr = 0;
|
| 602 | u16 fw_len;
|
| 603 |
|
| 604 | |
| 605 | * The firmware ihex blob can never be bigger than 64 kiB, so make this
|
| 606 | * simple -- allocate a 64 kiB buffer, iterate over the ihex blob records
|
| 607 | * once, copy them all into this buffer at the right locations, and then
|
| 608 | * do all operations on this linear buffer.
|
| 609 |
|
| 610 | u8* fw_buf __free(kvfree) = kvmalloc(SZ_64K, GFP_KERNEL);
|
| 611 | if (!fw_buf)
|
| 612 | return ERR_PTR(-ENOMEM);
|
| 613 |
|
| 614 | rec = (const struct ihex_binrec *)fw->data;
|
| 615 | while (rec) {
|
| 616 | fw_addr = be32_to_cpu(rec->addr);
|
| 617 | fw_len = be16_to_cpu(rec->len);
|
| 618 |
|
| 619 |
|
| 620 | if (fw_addr + fw_len > SZ_64K || fw_addr > SZ_64K - 32)
|
| 621 | return ERR_PTR(-EFBIG);
|
| 622 |
|
| 623 |
|
| 624 | if (fw_addr == 0xf000)
|
| 625 | *ac_end = fw_last_addr;
|
| 626 | fw_last_addr = fw_addr + fw_len;
|
| 627 |
|
| 628 | memcpy(fw_buf + fw_addr, rec->data, fw_len);
|
| 629 | rec = ihex_next_binrec(rec);
|
| 630 | }
|
| 631 |
|
| 632 |
|
| 633 | *df_end = fw_addr + fw_len;
|
| 634 |
|
| 635 | return_ptr(fw_buf);
|
| 636 | }
|
| 637 |
|
| 638 |
|
| 639 | static int ili251x_switch_ic_mode(struct i2c_client *client, u8 cmd_mode)
|
| 640 | {
|
| 641 | struct ili210x *priv = i2c_get_clientdata(client);
|
| 642 | u8 cmd_wren[3] = { REG_WRITE_ENABLE, 0x5a, 0xa5 };
|
| 643 | u8 md[2];
|
| 644 | int error;
|
| 645 |
|
| 646 | error = priv->chip->read_reg(client, REG_GET_MODE, md, sizeof(md));
|
| 647 | if (error)
|
| 648 | return error;
|
| 649 |
|
| 650 | if ((cmd_mode == REG_SET_MODE_AP && md[0] == REG_GET_MODE_AP) ||
|
| 651 | (cmd_mode == REG_SET_MODE_BL && md[0] == REG_GET_MODE_BL))
|
| 652 | return 0;
|
| 653 |
|
| 654 |
|
| 655 | error = i2c_master_send(client, cmd_wren, sizeof(cmd_wren));
|
| 656 | if (error != sizeof(cmd_wren))
|
| 657 | return -EINVAL;
|
| 658 |
|
| 659 | mdelay(20);
|
| 660 |
|
| 661 |
|
| 662 | error = i2c_master_send(client, &cmd_mode, 1);
|
| 663 | if (error != 1)
|
| 664 | return -EINVAL;
|
| 665 |
|
| 666 | mdelay(200);
|
| 667 |
|
| 668 |
|
| 669 | error = priv->chip->read_reg(client, REG_GET_MODE, md, sizeof(md));
|
| 670 | if (error)
|
| 671 | return error;
|
| 672 |
|
| 673 | if ((cmd_mode == REG_SET_MODE_AP && md[0] == REG_GET_MODE_AP) ||
|
| 674 | (cmd_mode == REG_SET_MODE_BL && md[0] == REG_GET_MODE_BL))
|
| 675 | return 0;
|
| 676 |
|
| 677 | return -EINVAL;
|
| 678 | }
|
| 679 |
|
| 680 | static int ili251x_firmware_busy(struct i2c_client *client)
|
| 681 | {
|
| 682 | struct ili210x *priv = i2c_get_clientdata(client);
|
| 683 | int error, i = 0;
|
| 684 | u8 data;
|
| 685 |
|
| 686 | do {
|
| 687 |
|
| 688 | error = priv->chip->read_reg(client, REG_IC_BUSY, &data, 1);
|
| 689 | if (error)
|
| 690 | return error;
|
| 691 | if (i++ == 100000)
|
| 692 | return -ETIMEDOUT;
|
| 693 | } while (data != REG_IC_BUSY_NOT_BUSY);
|
| 694 |
|
| 695 | return 0;
|
| 696 | }
|
| 697 |
|
| 698 | static int ili251x_firmware_write_to_ic(struct device *dev, const u8 *fwbuf,
|
| 699 | u16 start, u16 end, u8 dataflash)
|
| 700 | {
|
| 701 | struct i2c_client *client = to_i2c_client(dev);
|
| 702 | struct ili210x *priv = i2c_get_clientdata(client);
|
| 703 | u8 cmd_crc = REG_READ_DATA_CRC;
|
| 704 | u8 crcrb[4] = { 0 };
|
| 705 | u8 fw_data[33];
|
| 706 | u16 fw_addr;
|
| 707 | int error;
|
| 708 |
|
| 709 | |
| 710 | * The DF (dataflash) needs 2 bytes offset for unknown reasons,
|
| 711 | * the AC (application) has 2 bytes CRC16-CCITT at the end.
|
| 712 |
|
| 713 | u16 crc = crc_ccitt(0, fwbuf + start + (dataflash ? 2 : 0),
|
| 714 | end - start - 2);
|
| 715 |
|
| 716 |
|
| 717 | u8 cmd_wr[10] = {
|
| 718 | REG_WRITE_ENABLE, 0x5a, 0xa5, dataflash,
|
| 719 | (end >> 16) & 0xff, (end >> 8) & 0xff, end & 0xff,
|
| 720 | (crc >> 16) & 0xff, (crc >> 8) & 0xff, crc & 0xff
|
| 721 | };
|
| 722 |
|
| 723 | error = i2c_master_send(client, cmd_wr, sizeof(cmd_wr));
|
| 724 | if (error != sizeof(cmd_wr))
|
| 725 | return -EINVAL;
|
| 726 |
|
| 727 | error = ili251x_firmware_busy(client);
|
| 728 | if (error)
|
| 729 | return error;
|
| 730 |
|
| 731 | for (fw_addr = start; fw_addr < end; fw_addr += 32) {
|
| 732 | fw_data[0] = REG_WRITE_DATA;
|
| 733 | memcpy(&(fw_data[1]), fwbuf + fw_addr, 32);
|
| 734 | error = i2c_master_send(client, fw_data, 33);
|
| 735 | if (error != sizeof(fw_data))
|
| 736 | return error;
|
| 737 | error = ili251x_firmware_busy(client);
|
| 738 | if (error)
|
| 739 | return error;
|
| 740 | }
|
| 741 |
|
| 742 | error = i2c_master_send(client, &cmd_crc, 1);
|
| 743 | if (error != 1)
|
| 744 | return -EINVAL;
|
| 745 |
|
| 746 | error = ili251x_firmware_busy(client);
|
| 747 | if (error)
|
| 748 | return error;
|
| 749 |
|
| 750 | error = priv->chip->read_reg(client, REG_READ_DATA_CRC,
|
| 751 | &crcrb, sizeof(crcrb));
|
| 752 | if (error)
|
| 753 | return error;
|
| 754 |
|
| 755 |
|
| 756 | if ((crcrb[0] != (crc & 0xff)) || crcrb[1] != ((crc >> 8) & 0xff))
|
| 757 | return -EINVAL;
|
| 758 |
|
| 759 | return 0;
|
| 760 | }
|
| 761 |
|
| 762 | static int ili251x_firmware_reset(struct i2c_client *client)
|
| 763 | {
|
| 764 | u8 cmd_reset[2] = { 0xf2, 0x01 };
|
| 765 | int error;
|
| 766 |
|
| 767 | error = i2c_master_send(client, cmd_reset, sizeof(cmd_reset));
|
| 768 | if (error != sizeof(cmd_reset))
|
| 769 | return -EINVAL;
|
| 770 |
|
| 771 | return ili251x_firmware_busy(client);
|
| 772 | }
|
| 773 |
|
| 774 | static void ili210x_hardware_reset(struct gpio_desc *reset_gpio)
|
| 775 | {
|
| 776 |
|
| 777 | gpiod_set_value_cansleep(reset_gpio, 1);
|
| 778 | usleep_range(12000, 15000);
|
| 779 | gpiod_set_value_cansleep(reset_gpio, 0);
|
| 780 | msleep(300);
|
| 781 | }
|
| 782 |
|
| 783 | static int ili210x_do_firmware_update(struct ili210x *priv,
|
| 784 | const u8 *fwbuf, u16 ac_end, u16 df_end)
|
| 785 | {
|
| 786 | struct i2c_client *client = priv->client;
|
| 787 | struct device *dev = &client->dev;
|
| 788 | int error;
|
| 789 | int i;
|
| 790 |
|
| 791 | error = ili251x_firmware_reset(client);
|
| 792 | if (error)
|
| 793 | return error;
|
| 794 |
|
| 795 |
|
| 796 | for (i = 0; i < 5; i++) {
|
| 797 | error = ili251x_switch_ic_mode(client, REG_SET_MODE_BL);
|
| 798 | if (!error)
|
| 799 | break;
|
| 800 | }
|
| 801 |
|
| 802 | if (error)
|
| 803 | return error;
|
| 804 |
|
| 805 | dev_dbg(dev, "IC is now in BootLoader mode\n");
|
| 806 |
|
| 807 | msleep(200);
|
| 808 |
|
| 809 | error = ili251x_firmware_write_to_ic(dev, fwbuf, 0xf000, df_end, 1);
|
| 810 | if (error) {
|
| 811 | dev_err(dev, "DF firmware update failed, error=%d\n", error);
|
| 812 | return error;
|
| 813 | }
|
| 814 |
|
| 815 | dev_dbg(dev, "DataFlash firmware written\n");
|
| 816 |
|
| 817 | error = ili251x_firmware_write_to_ic(dev, fwbuf, 0x2000, ac_end, 0);
|
| 818 | if (error) {
|
| 819 | dev_err(dev, "AC firmware update failed, error=%d\n", error);
|
| 820 | return error;
|
| 821 | }
|
| 822 |
|
| 823 | dev_dbg(dev, "Application firmware written\n");
|
| 824 |
|
| 825 |
|
| 826 | for (i = 0; i < 5; i++) {
|
| 827 | error = ili251x_switch_ic_mode(client, REG_SET_MODE_AP);
|
| 828 | if (!error)
|
| 829 | break;
|
| 830 | }
|
| 831 |
|
| 832 | if (error)
|
| 833 | return error;
|
| 834 |
|
| 835 | dev_dbg(dev, "IC is now in Application mode\n");
|
| 836 |
|
| 837 | error = ili251x_firmware_update_cached_state(dev);
|
| 838 | if (error)
|
| 839 | return error;
|
| 840 |
|
| 841 | return 0;
|
| 842 | }
|
| 843 |
|
| 844 | static ssize_t ili210x_firmware_update(struct device *dev, const u8 *fwbuf,
|
| 845 | u16 ac_end, u16 df_end)
|
| 846 | {
|
| 847 | struct i2c_client *client = to_i2c_client(dev);
|
| 848 | struct ili210x *priv = i2c_get_clientdata(client);
|
| 849 | const char *fwname = ILI251X_FW_FILENAME;
|
| 850 | int error;
|
| 851 |
|
| 852 | dev_dbg(dev, "Firmware update started, firmware=%s\n", fwname);
|
| 853 |
|
| 854 | ili210x_hardware_reset(priv->reset_gpio);
|
| 855 |
|
| 856 | error = ili210x_do_firmware_update(priv, fwbuf, ac_end, df_end);
|
| 857 |
|
| 858 | ili210x_hardware_reset(priv->reset_gpio);
|
| 859 |
|
| 860 | dev_dbg(dev, "Firmware update ended, error=%i\n", error);
|
| 861 |
|
| 862 | return error;
|
| 863 | }
|
| 864 |
|
| 865 | static ssize_t ili210x_firmware_update_store(struct device *dev,
|
| 866 | struct device_attribute *attr,
|
| 867 | const char *buf, size_t count)
|
| 868 | {
|
| 869 | struct i2c_client *client = to_i2c_client(dev);
|
| 870 | const char *fwname = ILI251X_FW_FILENAME;
|
| 871 | u16 ac_end, df_end;
|
| 872 | int error;
|
| 873 |
|
| 874 | const struct firmware *fw __free(firmware) = NULL;
|
| 875 | error = request_ihex_firmware(&fw, fwname, dev);
|
| 876 | if (error) {
|
| 877 | dev_err(dev, "Failed to request firmware %s, error=%d\n",
|
| 878 | fwname, error);
|
| 879 | return error;
|
| 880 | }
|
| 881 |
|
| 882 | const u8* fwbuf __free(kvfree) =
|
| 883 | ili251x_firmware_to_buffer(fw, &ac_end, &df_end);
|
| 884 | error = PTR_ERR_OR_ZERO(fwbuf);
|
| 885 | if (error)
|
| 886 | return error;
|
| 887 |
|
| 888 | |
| 889 | * Disable touchscreen IRQ, so that we would not get spurious touch
|
| 890 | * interrupt during firmware update, and so that the IRQ handler won't
|
| 891 | * trigger and interfere with the firmware update. There is no bit in
|
| 892 | * the touch controller to disable the IRQs during update, so we have
|
| 893 | * to do it this way here.
|
| 894 |
|
| 895 | if (client->irq > 0) {
|
| 896 | guard(disable_irq)(&client->irq);
|
| 897 | error = ili210x_firmware_update(dev, fwbuf, ac_end, df_end);
|
| 898 | } else {
|
| 899 | error = ili210x_firmware_update(dev, fwbuf, ac_end, df_end);
|
| 900 | }
|
| 901 |
|
| 902 | return error ?: count;
|
| 903 | }
|
| 904 |
|
| 905 | static DEVICE_ATTR(firmware_update, 0200, NULL, ili210x_firmware_update_store);
|
| 906 |
|
| 907 | static struct attribute *ili210x_attrs[] = {
|
| 908 | &dev_attr_calibrate.attr,
|
| 909 | &dev_attr_firmware_update.attr,
|
| 910 | &dev_attr_firmware_version.attr,
|
| 911 | &dev_attr_kernel_version.attr,
|
| 912 | &dev_attr_protocol_version.attr,
|
| 913 | &dev_attr_mode.attr,
|
| 914 | NULL,
|
| 915 | };
|
| 916 |
|
| 917 | static umode_t ili210x_attributes_visible(struct kobject *kobj,
|
| 918 | struct attribute *attr, int index)
|
| 919 | {
|
| 920 | struct device *dev = kobj_to_dev(kobj);
|
| 921 | struct i2c_client *client = to_i2c_client(dev);
|
| 922 | struct ili210x *priv = i2c_get_clientdata(client);
|
| 923 |
|
| 924 |
|
| 925 | if (attr == &dev_attr_calibrate.attr)
|
| 926 | return priv->chip->has_calibrate_reg ? attr->mode : 0;
|
| 927 |
|
| 928 |
|
| 929 | if (!priv->chip->has_firmware_proto)
|
| 930 | return 0;
|
| 931 |
|
| 932 | return attr->mode;
|
| 933 | }
|
| 934 |
|
| 935 | static const struct attribute_group ili210x_group = {
|
| 936 | .attrs = ili210x_attrs,
|
| 937 | .is_visible = ili210x_attributes_visible,
|
| 938 | };
|
| 939 | __ATTRIBUTE_GROUPS(ili210x);
|
| 940 |
|
| 941 | static void ili210x_power_down(void *data)
|
| 942 | {
|
| 943 | struct gpio_desc *reset_gpio = data;
|
| 944 |
|
| 945 | gpiod_set_value_cansleep(reset_gpio, 1);
|
| 946 | }
|
| 947 |
|
| 948 | static void ili210x_stop(void *data)
|
| 949 | {
|
| 950 | struct ili210x *priv = data;
|
| 951 |
|
| 952 |
|
| 953 | priv->stop = true;
|
| 954 | }
|
| 955 |
|
| 956 | static int ili210x_i2c_probe(struct i2c_client *client)
|
| 957 | {
|
| 958 | const struct i2c_device_id *id = i2c_client_get_device_id(client);
|
| 959 | struct device *dev = &client->dev;
|
| 960 | const struct ili2xxx_chip *chip;
|
| 961 | struct ili210x *priv;
|
| 962 | struct gpio_desc *reset_gpio;
|
| 963 | struct input_dev *input;
|
| 964 | int error;
|
| 965 | unsigned int max_xy;
|
| 966 |
|
| 967 | dev_dbg(dev, "Probing for ILI210X I2C Touschreen driver");
|
| 968 |
|
| 969 | chip = device_get_match_data(dev);
|
| 970 | if (!chip && id)
|
| 971 | chip = (const struct ili2xxx_chip *)id->driver_data;
|
| 972 | if (!chip)
|
| 973 | return dev_err_probe(&client->dev, -ENODEV, "unknown device model\n");
|
| 974 |
|
| 975 | reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
|
| 976 | if (IS_ERR(reset_gpio))
|
| 977 | return PTR_ERR(reset_gpio);
|
| 978 |
|
| 979 | if (reset_gpio) {
|
| 980 | error = devm_add_action_or_reset(dev, ili210x_power_down,
|
| 981 | reset_gpio);
|
| 982 | if (error)
|
| 983 | return error;
|
| 984 |
|
| 985 | ili210x_hardware_reset(reset_gpio);
|
| 986 | }
|
| 987 |
|
| 988 | priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
| 989 | if (!priv)
|
| 990 | return -ENOMEM;
|
| 991 |
|
| 992 | input = devm_input_allocate_device(dev);
|
| 993 | if (!input)
|
| 994 | return -ENOMEM;
|
| 995 |
|
| 996 | priv->client = client;
|
| 997 | priv->input = input;
|
| 998 | priv->reset_gpio = reset_gpio;
|
| 999 | priv->chip = chip;
|
| 1000 | i2c_set_clientdata(client, priv);
|
| 1001 |
|
| 1002 |
|
| 1003 | input->name = "ILI210x Touchscreen";
|
| 1004 | input->id.bustype = BUS_I2C;
|
| 1005 |
|
| 1006 |
|
| 1007 | max_xy = (chip->resolution ?: SZ_64K) - 1;
|
| 1008 | input_set_abs_params(input, ABS_MT_POSITION_X, 0, max_xy, 0, 0);
|
| 1009 | input_set_abs_params(input, ABS_MT_POSITION_Y, 0, max_xy, 0, 0);
|
| 1010 | if (priv->chip->has_pressure_reg)
|
| 1011 | input_set_abs_params(input, ABS_MT_PRESSURE, 0, 0xa, 0, 0);
|
| 1012 | error = ili251x_firmware_update_cached_state(dev);
|
| 1013 | if (error)
|
| 1014 | dev_warn(dev, "Unable to cache firmware information, err: %d\n",
|
| 1015 | error);
|
| 1016 |
|
| 1017 | touchscreen_parse_properties(input, true, &priv->prop);
|
| 1018 |
|
| 1019 | error = input_mt_init_slots(input, priv->chip->max_touches,
|
| 1020 | INPUT_MT_DIRECT);
|
| 1021 | if (error)
|
| 1022 | return dev_err_probe(dev, error, "Unable to set up slots\n");
|
| 1023 |
|
| 1024 | input_set_drvdata(input, priv);
|
| 1025 |
|
| 1026 | if (client->irq > 0) {
|
| 1027 | error = devm_request_threaded_irq(dev, client->irq, NULL, ili210x_irq,
|
| 1028 | IRQF_ONESHOT, client->name, priv);
|
| 1029 | if (error)
|
| 1030 | return dev_err_probe(dev, error, "Unable to request touchscreen IRQ\n");
|
| 1031 | } else {
|
| 1032 | error = input_setup_polling(input, ili210x_work_i2c_poll);
|
| 1033 | if (error)
|
| 1034 | return dev_err_probe(dev, error, "Could not set up polling mode\n");
|
| 1035 |
|
| 1036 | input_set_poll_interval(input, ILI2XXX_POLL_PERIOD);
|
| 1037 | }
|
| 1038 |
|
| 1039 | error = devm_add_action_or_reset(dev, ili210x_stop, priv);
|
| 1040 | if (error)
|
| 1041 | return error;
|
| 1042 |
|
| 1043 | error = input_register_device(priv->input);
|
| 1044 | if (error)
|
| 1045 | return dev_err_probe(dev, error, "Cannot register input device\n");
|
| 1046 |
|
| 1047 | return 0;
|
| 1048 | }
|
| 1049 |
|
| 1050 | static const struct i2c_device_id ili210x_i2c_id[] = {
|
| 1051 | { .name = "ili210x", .driver_data = (long)&ili210x_chip },
|
| 1052 | { .name = "ili2117", .driver_data = (long)&ili211x_chip },
|
| 1053 | { .name = "ili2120", .driver_data = (long)&ili212x_chip },
|
| 1054 | { .name = "ili251x", .driver_data = (long)&ili251x_chip },
|
| 1055 | { }
|
| 1056 | };
|
| 1057 | MODULE_DEVICE_TABLE(i2c, ili210x_i2c_id);
|
| 1058 |
|
| 1059 | static const struct of_device_id ili210x_dt_ids[] = {
|
| 1060 | { .compatible = "ilitek,ili210x", .data = &ili210x_chip },
|
| 1061 | { .compatible = "ilitek,ili2117", .data = &ili211x_chip },
|
| 1062 | { .compatible = "ilitek,ili2120", .data = &ili212x_chip },
|
| 1063 | { .compatible = "ilitek,ili251x", .data = &ili251x_chip },
|
| 1064 | { }
|
| 1065 | };
|
| 1066 | MODULE_DEVICE_TABLE(of, ili210x_dt_ids);
|
| 1067 |
|
| 1068 | static struct i2c_driver ili210x_ts_driver = {
|
| 1069 | .driver = {
|
| 1070 | .name = "ili210x_i2c",
|
| 1071 | .dev_groups = ili210x_groups,
|
| 1072 | .of_match_table = ili210x_dt_ids,
|
| 1073 | },
|
| 1074 | .id_table = ili210x_i2c_id,
|
| 1075 | .probe = ili210x_i2c_probe,
|
| 1076 | };
|
| 1077 |
|
| 1078 | module_i2c_driver(ili210x_ts_driver);
|
| 1079 |
|
| 1080 | MODULE_AUTHOR("Olivier Sobrie <[email protected]>");
|
| 1081 | MODULE_DESCRIPTION("ILI210X I2C Touchscreen Driver");
|
| 1082 | MODULE_LICENSE("GPL");
|