1 | /* |
2 | * TI Touch Screen driver |
3 | * |
4 | * Copyright (C) 2011 Texas Instruments Incorporated - http://www.ti.com/ |
5 | * |
6 | * This program is free software; you can redistribute it and/or |
7 | * modify it under the terms of the GNU General Public License as |
8 | * published by the Free Software Foundation version 2. |
9 | * |
10 | * This program is distributed "as is" WITHOUT ANY WARRANTY of any |
11 | * kind, whether express or implied; without even the implied warranty |
12 | * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
13 | * GNU General Public License for more details. |
14 | */ |
15 | |
16 | |
17 | #include <linux/kernel.h> |
18 | #include <linux/err.h> |
19 | #include <linux/module.h> |
20 | #include <linux/input.h> |
21 | #include <linux/slab.h> |
22 | #include <linux/interrupt.h> |
23 | #include <linux/clk.h> |
24 | #include <linux/platform_device.h> |
25 | #include <linux/io.h> |
26 | #include <linux/delay.h> |
27 | #include <linux/of.h> |
28 | #include <linux/sort.h> |
29 | #include <linux/pm_wakeirq.h> |
30 | |
31 | #include <linux/mfd/ti_am335x_tscadc.h> |
32 | |
33 | #define ADCFSM_STEPID 0x10 |
34 | #define SEQ_SETTLE 275 |
35 | #define MAX_12BIT ((1 << 12) - 1) |
36 | |
37 | #define TSC_IRQENB_MASK (IRQENB_FIFO0THRES | IRQENB_EOS | IRQENB_HW_PEN) |
38 | |
39 | static const int config_pins[] = { |
40 | STEPCONFIG_XPP, |
41 | STEPCONFIG_XNN, |
42 | STEPCONFIG_YPP, |
43 | STEPCONFIG_YNN, |
44 | }; |
45 | |
46 | struct titsc { |
47 | struct input_dev *input; |
48 | struct ti_tscadc_dev *mfd_tscadc; |
49 | struct device *dev; |
50 | unsigned int irq; |
51 | unsigned int wires; |
52 | unsigned int x_plate_resistance; |
53 | bool pen_down; |
54 | int coordinate_readouts; |
55 | u32 config_inp[4]; |
56 | u32 bit_xp, bit_xn, bit_yp, bit_yn; |
57 | u32 inp_xp, inp_xn, inp_yp, inp_yn; |
58 | u32 step_mask; |
59 | u32 charge_delay; |
60 | }; |
61 | |
62 | static unsigned int titsc_readl(struct titsc *ts, unsigned int reg) |
63 | { |
64 | return readl(addr: ts->mfd_tscadc->tscadc_base + reg); |
65 | } |
66 | |
67 | static void titsc_writel(struct titsc *tsc, unsigned int reg, |
68 | unsigned int val) |
69 | { |
70 | writel(val, addr: tsc->mfd_tscadc->tscadc_base + reg); |
71 | } |
72 | |
73 | static int titsc_config_wires(struct titsc *ts_dev) |
74 | { |
75 | u32 analog_line[4]; |
76 | u32 wire_order[4]; |
77 | int i, bit_cfg; |
78 | |
79 | for (i = 0; i < 4; i++) { |
80 | /* |
81 | * Get the order in which TSC wires are attached |
82 | * w.r.t. each of the analog input lines on the EVM. |
83 | */ |
84 | analog_line[i] = (ts_dev->config_inp[i] & 0xF0) >> 4; |
85 | wire_order[i] = ts_dev->config_inp[i] & 0x0F; |
86 | if (WARN_ON(analog_line[i] > 7)) |
87 | return -EINVAL; |
88 | if (WARN_ON(wire_order[i] > ARRAY_SIZE(config_pins))) |
89 | return -EINVAL; |
90 | } |
91 | |
92 | for (i = 0; i < 4; i++) { |
93 | int an_line; |
94 | int wi_order; |
95 | |
96 | an_line = analog_line[i]; |
97 | wi_order = wire_order[i]; |
98 | bit_cfg = config_pins[wi_order]; |
99 | if (bit_cfg == 0) |
100 | return -EINVAL; |
101 | switch (wi_order) { |
102 | case 0: |
103 | ts_dev->bit_xp = bit_cfg; |
104 | ts_dev->inp_xp = an_line; |
105 | break; |
106 | |
107 | case 1: |
108 | ts_dev->bit_xn = bit_cfg; |
109 | ts_dev->inp_xn = an_line; |
110 | break; |
111 | |
112 | case 2: |
113 | ts_dev->bit_yp = bit_cfg; |
114 | ts_dev->inp_yp = an_line; |
115 | break; |
116 | case 3: |
117 | ts_dev->bit_yn = bit_cfg; |
118 | ts_dev->inp_yn = an_line; |
119 | break; |
120 | } |
121 | } |
122 | return 0; |
123 | } |
124 | |
125 | static void titsc_step_config(struct titsc *ts_dev) |
126 | { |
127 | unsigned int config; |
128 | int i, n; |
129 | int end_step, first_step, tsc_steps; |
130 | u32 stepenable; |
131 | |
132 | config = STEPCONFIG_MODE_HWSYNC | |
133 | STEPCONFIG_AVG_16 | ts_dev->bit_xp | |
134 | STEPCONFIG_INM_ADCREFM; |
135 | switch (ts_dev->wires) { |
136 | case 4: |
137 | config |= STEPCONFIG_INP(ts_dev->inp_yp) | ts_dev->bit_xn; |
138 | break; |
139 | case 5: |
140 | config |= ts_dev->bit_yn | |
141 | STEPCONFIG_INP_AN4 | ts_dev->bit_xn | |
142 | ts_dev->bit_yp; |
143 | break; |
144 | case 8: |
145 | config |= STEPCONFIG_INP(ts_dev->inp_yp) | ts_dev->bit_xn; |
146 | break; |
147 | } |
148 | |
149 | tsc_steps = ts_dev->coordinate_readouts * 2 + 2; |
150 | first_step = TOTAL_STEPS - tsc_steps; |
151 | /* Steps 16 to 16-coordinate_readouts is for X */ |
152 | end_step = first_step + tsc_steps; |
153 | n = 0; |
154 | for (i = end_step - ts_dev->coordinate_readouts; i < end_step; i++) { |
155 | titsc_writel(tsc: ts_dev, REG_STEPCONFIG(i), val: config); |
156 | titsc_writel(tsc: ts_dev, REG_STEPDELAY(i), |
157 | val: n++ == 0 ? STEPCONFIG_OPENDLY : 0); |
158 | } |
159 | |
160 | config = 0; |
161 | config = STEPCONFIG_MODE_HWSYNC | |
162 | STEPCONFIG_AVG_16 | ts_dev->bit_yn | |
163 | STEPCONFIG_INM_ADCREFM; |
164 | switch (ts_dev->wires) { |
165 | case 4: |
166 | config |= ts_dev->bit_yp | STEPCONFIG_INP(ts_dev->inp_xp); |
167 | break; |
168 | case 5: |
169 | config |= ts_dev->bit_xp | STEPCONFIG_INP_AN4 | |
170 | STEPCONFIG_XNP | STEPCONFIG_YPN; |
171 | break; |
172 | case 8: |
173 | config |= ts_dev->bit_yp | STEPCONFIG_INP(ts_dev->inp_xp); |
174 | break; |
175 | } |
176 | |
177 | /* 1 ... coordinate_readouts is for Y */ |
178 | end_step = first_step + ts_dev->coordinate_readouts; |
179 | n = 0; |
180 | for (i = first_step; i < end_step; i++) { |
181 | titsc_writel(tsc: ts_dev, REG_STEPCONFIG(i), val: config); |
182 | titsc_writel(tsc: ts_dev, REG_STEPDELAY(i), |
183 | val: n++ == 0 ? STEPCONFIG_OPENDLY : 0); |
184 | } |
185 | |
186 | /* Make CHARGECONFIG same as IDLECONFIG */ |
187 | |
188 | config = titsc_readl(ts: ts_dev, REG_IDLECONFIG); |
189 | titsc_writel(tsc: ts_dev, REG_CHARGECONFIG, val: config); |
190 | titsc_writel(tsc: ts_dev, REG_CHARGEDELAY, val: ts_dev->charge_delay); |
191 | |
192 | /* coordinate_readouts + 1 ... coordinate_readouts + 2 is for Z */ |
193 | config = STEPCONFIG_MODE_HWSYNC | |
194 | STEPCONFIG_AVG_16 | ts_dev->bit_yp | |
195 | ts_dev->bit_xn | STEPCONFIG_INM_ADCREFM | |
196 | STEPCONFIG_INP(ts_dev->inp_xp); |
197 | titsc_writel(tsc: ts_dev, REG_STEPCONFIG(end_step), val: config); |
198 | titsc_writel(tsc: ts_dev, REG_STEPDELAY(end_step), |
199 | STEPCONFIG_OPENDLY); |
200 | |
201 | end_step++; |
202 | config = STEPCONFIG_MODE_HWSYNC | |
203 | STEPCONFIG_AVG_16 | ts_dev->bit_yp | |
204 | ts_dev->bit_xn | STEPCONFIG_INM_ADCREFM | |
205 | STEPCONFIG_INP(ts_dev->inp_yn); |
206 | titsc_writel(tsc: ts_dev, REG_STEPCONFIG(end_step), val: config); |
207 | titsc_writel(tsc: ts_dev, REG_STEPDELAY(end_step), |
208 | STEPCONFIG_OPENDLY); |
209 | |
210 | /* The steps end ... end - readouts * 2 + 2 and bit 0 for TS_Charge */ |
211 | stepenable = 1; |
212 | for (i = 0; i < tsc_steps; i++) |
213 | stepenable |= 1 << (first_step + i + 1); |
214 | |
215 | ts_dev->step_mask = stepenable; |
216 | am335x_tsc_se_set_cache(tsadc: ts_dev->mfd_tscadc, val: ts_dev->step_mask); |
217 | } |
218 | |
219 | static int titsc_cmp_coord(const void *a, const void *b) |
220 | { |
221 | return *(int *)a - *(int *)b; |
222 | } |
223 | |
224 | static void titsc_read_coordinates(struct titsc *ts_dev, |
225 | u32 *x, u32 *y, u32 *z1, u32 *z2) |
226 | { |
227 | unsigned int yvals[7], xvals[7]; |
228 | unsigned int i, xsum = 0, ysum = 0; |
229 | unsigned int creads = ts_dev->coordinate_readouts; |
230 | |
231 | for (i = 0; i < creads; i++) { |
232 | yvals[i] = titsc_readl(ts: ts_dev, REG_FIFO0); |
233 | yvals[i] &= 0xfff; |
234 | } |
235 | |
236 | *z1 = titsc_readl(ts: ts_dev, REG_FIFO0); |
237 | *z1 &= 0xfff; |
238 | *z2 = titsc_readl(ts: ts_dev, REG_FIFO0); |
239 | *z2 &= 0xfff; |
240 | |
241 | for (i = 0; i < creads; i++) { |
242 | xvals[i] = titsc_readl(ts: ts_dev, REG_FIFO0); |
243 | xvals[i] &= 0xfff; |
244 | } |
245 | |
246 | /* |
247 | * If co-ordinates readouts is less than 4 then |
248 | * report the average. In case of 4 or more |
249 | * readouts, sort the co-ordinate samples, drop |
250 | * min and max values and report the average of |
251 | * remaining values. |
252 | */ |
253 | if (creads <= 3) { |
254 | for (i = 0; i < creads; i++) { |
255 | ysum += yvals[i]; |
256 | xsum += xvals[i]; |
257 | } |
258 | ysum /= creads; |
259 | xsum /= creads; |
260 | } else { |
261 | sort(base: yvals, num: creads, size: sizeof(unsigned int), |
262 | cmp_func: titsc_cmp_coord, NULL); |
263 | sort(base: xvals, num: creads, size: sizeof(unsigned int), |
264 | cmp_func: titsc_cmp_coord, NULL); |
265 | for (i = 1; i < creads - 1; i++) { |
266 | ysum += yvals[i]; |
267 | xsum += xvals[i]; |
268 | } |
269 | ysum /= creads - 2; |
270 | xsum /= creads - 2; |
271 | } |
272 | *y = ysum; |
273 | *x = xsum; |
274 | } |
275 | |
276 | static irqreturn_t titsc_irq(int irq, void *dev) |
277 | { |
278 | struct titsc *ts_dev = dev; |
279 | struct input_dev *input_dev = ts_dev->input; |
280 | unsigned int fsm, status, irqclr = 0; |
281 | unsigned int x = 0, y = 0; |
282 | unsigned int z1, z2, z; |
283 | |
284 | status = titsc_readl(ts: ts_dev, REG_RAWIRQSTATUS); |
285 | if (status & IRQENB_HW_PEN) { |
286 | ts_dev->pen_down = true; |
287 | irqclr |= IRQENB_HW_PEN; |
288 | pm_stay_awake(dev: ts_dev->dev); |
289 | } |
290 | |
291 | if (status & IRQENB_PENUP) { |
292 | fsm = titsc_readl(ts: ts_dev, REG_ADCFSM); |
293 | if (fsm == ADCFSM_STEPID) { |
294 | ts_dev->pen_down = false; |
295 | input_report_key(dev: input_dev, BTN_TOUCH, value: 0); |
296 | input_report_abs(dev: input_dev, ABS_PRESSURE, value: 0); |
297 | input_sync(dev: input_dev); |
298 | pm_relax(dev: ts_dev->dev); |
299 | } else { |
300 | ts_dev->pen_down = true; |
301 | } |
302 | irqclr |= IRQENB_PENUP; |
303 | } |
304 | |
305 | if (status & IRQENB_EOS) |
306 | irqclr |= IRQENB_EOS; |
307 | |
308 | /* |
309 | * ADC and touchscreen share the IRQ line. |
310 | * FIFO1 interrupts are used by ADC. Handle FIFO0 IRQs here only |
311 | */ |
312 | if (status & IRQENB_FIFO0THRES) { |
313 | |
314 | titsc_read_coordinates(ts_dev, x: &x, y: &y, z1: &z1, z2: &z2); |
315 | |
316 | if (ts_dev->pen_down && z1 != 0 && z2 != 0) { |
317 | /* |
318 | * Calculate pressure using formula |
319 | * Resistance(touch) = x plate resistance * |
320 | * x position/4096 * ((z2 / z1) - 1) |
321 | */ |
322 | z = z1 - z2; |
323 | z *= x; |
324 | z *= ts_dev->x_plate_resistance; |
325 | z /= z2; |
326 | z = (z + 2047) >> 12; |
327 | |
328 | if (z <= MAX_12BIT) { |
329 | input_report_abs(dev: input_dev, ABS_X, value: x); |
330 | input_report_abs(dev: input_dev, ABS_Y, value: y); |
331 | input_report_abs(dev: input_dev, ABS_PRESSURE, value: z); |
332 | input_report_key(dev: input_dev, BTN_TOUCH, value: 1); |
333 | input_sync(dev: input_dev); |
334 | } |
335 | } |
336 | irqclr |= IRQENB_FIFO0THRES; |
337 | } |
338 | if (irqclr) { |
339 | titsc_writel(tsc: ts_dev, REG_IRQSTATUS, val: irqclr); |
340 | if (status & IRQENB_EOS) |
341 | am335x_tsc_se_set_cache(tsadc: ts_dev->mfd_tscadc, |
342 | val: ts_dev->step_mask); |
343 | return IRQ_HANDLED; |
344 | } |
345 | return IRQ_NONE; |
346 | } |
347 | |
348 | static int titsc_parse_dt(struct platform_device *pdev, |
349 | struct titsc *ts_dev) |
350 | { |
351 | struct device_node *node = pdev->dev.of_node; |
352 | int err; |
353 | |
354 | if (!node) |
355 | return -EINVAL; |
356 | |
357 | err = of_property_read_u32(np: node, propname: "ti,wires" , out_value: &ts_dev->wires); |
358 | if (err < 0) |
359 | return err; |
360 | switch (ts_dev->wires) { |
361 | case 4: |
362 | case 5: |
363 | case 8: |
364 | break; |
365 | default: |
366 | return -EINVAL; |
367 | } |
368 | |
369 | err = of_property_read_u32(np: node, propname: "ti,x-plate-resistance" , |
370 | out_value: &ts_dev->x_plate_resistance); |
371 | if (err < 0) |
372 | return err; |
373 | |
374 | /* |
375 | * Try with the new binding first. If it fails, try again with |
376 | * bogus, miss-spelled version. |
377 | */ |
378 | err = of_property_read_u32(np: node, propname: "ti,coordinate-readouts" , |
379 | out_value: &ts_dev->coordinate_readouts); |
380 | if (err < 0) { |
381 | dev_warn(&pdev->dev, "please use 'ti,coordinate-readouts' instead\n" ); |
382 | err = of_property_read_u32(np: node, propname: "ti,coordiante-readouts" , |
383 | out_value: &ts_dev->coordinate_readouts); |
384 | } |
385 | |
386 | if (err < 0) |
387 | return err; |
388 | |
389 | if (ts_dev->coordinate_readouts <= 0) { |
390 | dev_warn(&pdev->dev, |
391 | "invalid co-ordinate readouts, resetting it to 5\n" ); |
392 | ts_dev->coordinate_readouts = 5; |
393 | } |
394 | |
395 | err = of_property_read_u32(np: node, propname: "ti,charge-delay" , |
396 | out_value: &ts_dev->charge_delay); |
397 | /* |
398 | * If ti,charge-delay value is not specified, then use |
399 | * CHARGEDLY_OPENDLY as the default value. |
400 | */ |
401 | if (err < 0) { |
402 | ts_dev->charge_delay = CHARGEDLY_OPENDLY; |
403 | dev_warn(&pdev->dev, "ti,charge-delay not specified\n" ); |
404 | } |
405 | |
406 | return of_property_read_u32_array(np: node, propname: "ti,wire-config" , |
407 | out_values: ts_dev->config_inp, ARRAY_SIZE(ts_dev->config_inp)); |
408 | } |
409 | |
410 | /* |
411 | * The functions for inserting/removing driver as a module. |
412 | */ |
413 | |
414 | static int titsc_probe(struct platform_device *pdev) |
415 | { |
416 | struct titsc *ts_dev; |
417 | struct input_dev *input_dev; |
418 | struct ti_tscadc_dev *tscadc_dev = ti_tscadc_dev_get(p: pdev); |
419 | int err; |
420 | |
421 | /* Allocate memory for device */ |
422 | ts_dev = kzalloc(size: sizeof(*ts_dev), GFP_KERNEL); |
423 | input_dev = input_allocate_device(); |
424 | if (!ts_dev || !input_dev) { |
425 | dev_err(&pdev->dev, "failed to allocate memory.\n" ); |
426 | err = -ENOMEM; |
427 | goto err_free_mem; |
428 | } |
429 | |
430 | tscadc_dev->tsc = ts_dev; |
431 | ts_dev->mfd_tscadc = tscadc_dev; |
432 | ts_dev->input = input_dev; |
433 | ts_dev->irq = tscadc_dev->irq; |
434 | ts_dev->dev = &pdev->dev; |
435 | |
436 | err = titsc_parse_dt(pdev, ts_dev); |
437 | if (err) { |
438 | dev_err(&pdev->dev, "Could not find valid DT data.\n" ); |
439 | goto err_free_mem; |
440 | } |
441 | |
442 | err = request_irq(irq: ts_dev->irq, handler: titsc_irq, |
443 | IRQF_SHARED, name: pdev->dev.driver->name, dev: ts_dev); |
444 | if (err) { |
445 | dev_err(&pdev->dev, "failed to allocate irq.\n" ); |
446 | goto err_free_mem; |
447 | } |
448 | |
449 | device_init_wakeup(dev: &pdev->dev, enable: true); |
450 | err = dev_pm_set_wake_irq(dev: &pdev->dev, irq: ts_dev->irq); |
451 | if (err) |
452 | dev_err(&pdev->dev, "irq wake enable failed.\n" ); |
453 | |
454 | titsc_writel(tsc: ts_dev, REG_IRQSTATUS, TSC_IRQENB_MASK); |
455 | titsc_writel(tsc: ts_dev, REG_IRQENABLE, IRQENB_FIFO0THRES); |
456 | titsc_writel(tsc: ts_dev, REG_IRQENABLE, IRQENB_EOS); |
457 | err = titsc_config_wires(ts_dev); |
458 | if (err) { |
459 | dev_err(&pdev->dev, "wrong i/p wire configuration\n" ); |
460 | goto err_free_irq; |
461 | } |
462 | titsc_step_config(ts_dev); |
463 | titsc_writel(tsc: ts_dev, REG_FIFO0THR, |
464 | val: ts_dev->coordinate_readouts * 2 + 2 - 1); |
465 | |
466 | input_dev->name = "ti-tsc" ; |
467 | input_dev->dev.parent = &pdev->dev; |
468 | |
469 | input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); |
470 | input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); |
471 | |
472 | input_set_abs_params(dev: input_dev, ABS_X, min: 0, MAX_12BIT, fuzz: 0, flat: 0); |
473 | input_set_abs_params(dev: input_dev, ABS_Y, min: 0, MAX_12BIT, fuzz: 0, flat: 0); |
474 | input_set_abs_params(dev: input_dev, ABS_PRESSURE, min: 0, MAX_12BIT, fuzz: 0, flat: 0); |
475 | |
476 | /* register to the input system */ |
477 | err = input_register_device(input_dev); |
478 | if (err) |
479 | goto err_free_irq; |
480 | |
481 | platform_set_drvdata(pdev, data: ts_dev); |
482 | return 0; |
483 | |
484 | err_free_irq: |
485 | dev_pm_clear_wake_irq(dev: &pdev->dev); |
486 | device_init_wakeup(dev: &pdev->dev, enable: false); |
487 | free_irq(ts_dev->irq, ts_dev); |
488 | err_free_mem: |
489 | input_free_device(dev: input_dev); |
490 | kfree(objp: ts_dev); |
491 | return err; |
492 | } |
493 | |
494 | static int titsc_remove(struct platform_device *pdev) |
495 | { |
496 | struct titsc *ts_dev = platform_get_drvdata(pdev); |
497 | u32 steps; |
498 | |
499 | dev_pm_clear_wake_irq(dev: &pdev->dev); |
500 | device_init_wakeup(dev: &pdev->dev, enable: false); |
501 | free_irq(ts_dev->irq, ts_dev); |
502 | |
503 | /* total steps followed by the enable mask */ |
504 | steps = 2 * ts_dev->coordinate_readouts + 2; |
505 | steps = (1 << steps) - 1; |
506 | am335x_tsc_se_clr(tsadc: ts_dev->mfd_tscadc, val: steps); |
507 | |
508 | input_unregister_device(ts_dev->input); |
509 | |
510 | kfree(objp: ts_dev); |
511 | return 0; |
512 | } |
513 | |
514 | static int titsc_suspend(struct device *dev) |
515 | { |
516 | struct titsc *ts_dev = dev_get_drvdata(dev); |
517 | unsigned int idle; |
518 | |
519 | if (device_may_wakeup(dev)) { |
520 | titsc_writel(tsc: ts_dev, REG_IRQSTATUS, TSC_IRQENB_MASK); |
521 | idle = titsc_readl(ts: ts_dev, REG_IRQENABLE); |
522 | titsc_writel(tsc: ts_dev, REG_IRQENABLE, |
523 | val: (idle | IRQENB_HW_PEN)); |
524 | titsc_writel(tsc: ts_dev, REG_IRQWAKEUP, IRQWKUP_ENB); |
525 | } |
526 | return 0; |
527 | } |
528 | |
529 | static int titsc_resume(struct device *dev) |
530 | { |
531 | struct titsc *ts_dev = dev_get_drvdata(dev); |
532 | |
533 | if (device_may_wakeup(dev)) { |
534 | titsc_writel(tsc: ts_dev, REG_IRQWAKEUP, |
535 | val: 0x00); |
536 | titsc_writel(tsc: ts_dev, REG_IRQCLR, IRQENB_HW_PEN); |
537 | pm_relax(dev); |
538 | } |
539 | titsc_step_config(ts_dev); |
540 | titsc_writel(tsc: ts_dev, REG_FIFO0THR, |
541 | val: ts_dev->coordinate_readouts * 2 + 2 - 1); |
542 | return 0; |
543 | } |
544 | |
545 | static DEFINE_SIMPLE_DEV_PM_OPS(titsc_pm_ops, titsc_suspend, titsc_resume); |
546 | |
547 | static const struct of_device_id ti_tsc_dt_ids[] = { |
548 | { .compatible = "ti,am3359-tsc" , }, |
549 | { } |
550 | }; |
551 | MODULE_DEVICE_TABLE(of, ti_tsc_dt_ids); |
552 | |
553 | static struct platform_driver ti_tsc_driver = { |
554 | .probe = titsc_probe, |
555 | .remove = titsc_remove, |
556 | .driver = { |
557 | .name = "TI-am335x-tsc" , |
558 | .pm = pm_sleep_ptr(&titsc_pm_ops), |
559 | .of_match_table = ti_tsc_dt_ids, |
560 | }, |
561 | }; |
562 | module_platform_driver(ti_tsc_driver); |
563 | |
564 | MODULE_DESCRIPTION("TI touchscreen controller driver" ); |
565 | MODULE_AUTHOR("Rachna Patil <rachna@ti.com>" ); |
566 | MODULE_LICENSE("GPL" ); |
567 | |