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