Commit | Line | Data |
---|---|---|
1b8be32e RP |
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/init.h> | |
18 | #include <linux/kernel.h> | |
19 | #include <linux/err.h> | |
20 | #include <linux/module.h> | |
21 | #include <linux/input.h> | |
22 | #include <linux/slab.h> | |
23 | #include <linux/interrupt.h> | |
24 | #include <linux/clk.h> | |
25 | #include <linux/platform_device.h> | |
26 | #include <linux/io.h> | |
27 | #include <linux/input/ti_tscadc.h> | |
28 | #include <linux/delay.h> | |
29 | ||
30 | #define REG_IRQEOI 0x020 | |
31 | #define REG_RAWIRQSTATUS 0x024 | |
32 | #define REG_IRQSTATUS 0x028 | |
33 | #define REG_IRQENABLE 0x02C | |
34 | #define REG_IRQWAKEUP 0x034 | |
35 | #define REG_CTRL 0x040 | |
36 | #define REG_ADCFSM 0x044 | |
37 | #define REG_CLKDIV 0x04C | |
38 | #define REG_SE 0x054 | |
39 | #define REG_IDLECONFIG 0x058 | |
40 | #define REG_CHARGECONFIG 0x05C | |
41 | #define REG_CHARGEDELAY 0x060 | |
42 | #define REG_STEPCONFIG(n) (0x64 + ((n - 1) * 8)) | |
43 | #define REG_STEPDELAY(n) (0x68 + ((n - 1) * 8)) | |
1b8be32e RP |
44 | #define REG_FIFO0CNT 0xE4 |
45 | #define REG_FIFO1THR 0xF4 | |
46 | #define REG_FIFO0 0x100 | |
47 | #define REG_FIFO1 0x200 | |
48 | ||
49 | /* Register Bitfields */ | |
50 | #define IRQWKUP_ENB BIT(0) | |
33f5cc60 PR |
51 | |
52 | /* Step Enable */ | |
53 | #define STEPENB_MASK (0x1FFFF << 0) | |
54 | #define STEPENB(val) (val << 0) | |
55 | #define STPENB_STEPENB STEPENB(0x7FFF) | |
56 | ||
57 | /* IRQ enable */ | |
1b8be32e RP |
58 | #define IRQENB_FIFO1THRES BIT(5) |
59 | #define IRQENB_PENUP BIT(9) | |
1b8be32e | 60 | |
33f5cc60 PR |
61 | /* Step Configuration */ |
62 | #define STEPCONFIG_MODE_MASK (3 << 0) | |
63 | #define STEPCONFIG_MODE(val) (val << 0) | |
64 | #define STEPCONFIG_MODE_HWSYNC STEPCONFIG_MODE(2) | |
65 | #define STEPCONFIG_AVG_MASK (7 << 2) | |
66 | #define STEPCONFIG_AVG(val) (val << 2) | |
67 | #define STEPCONFIG_AVG_16 STEPCONFIG_AVG(4) | |
68 | #define STEPCONFIG_XPP BIT(5) | |
69 | #define STEPCONFIG_XNN BIT(6) | |
70 | #define STEPCONFIG_YPP BIT(7) | |
71 | #define STEPCONFIG_YNN BIT(8) | |
72 | #define STEPCONFIG_XNP BIT(9) | |
73 | #define STEPCONFIG_YPN BIT(10) | |
74 | #define STEPCONFIG_INM_MASK (0xF << 15) | |
75 | #define STEPCONFIG_INM(val) (val << 15) | |
76 | #define STEPCONFIG_INM_ADCREFM STEPCONFIG_INM(8) | |
77 | #define STEPCONFIG_INP_MASK (0xF << 19) | |
78 | #define STEPCONFIG_INP(val) (val << 19) | |
79 | #define STEPCONFIG_INP_AN2 STEPCONFIG_INP(2) | |
80 | #define STEPCONFIG_INP_AN3 STEPCONFIG_INP(3) | |
81 | #define STEPCONFIG_INP_AN4 STEPCONFIG_INP(4) | |
82 | #define STEPCONFIG_INP_ADCREFM STEPCONFIG_INP(8) | |
83 | #define STEPCONFIG_FIFO1 BIT(26) | |
84 | ||
85 | /* Delay register */ | |
86 | #define STEPDELAY_OPEN_MASK (0x3FFFF << 0) | |
87 | #define STEPDELAY_OPEN(val) (val << 0) | |
88 | #define STEPCONFIG_OPENDLY STEPDELAY_OPEN(0x098) | |
89 | ||
90 | /* Charge Config */ | |
91 | #define STEPCHARGE_RFP_MASK (7 << 12) | |
92 | #define STEPCHARGE_RFP(val) (val << 12) | |
93 | #define STEPCHARGE_RFP_XPUL STEPCHARGE_RFP(1) | |
94 | #define STEPCHARGE_INM_MASK (0xF << 15) | |
95 | #define STEPCHARGE_INM(val) (val << 15) | |
96 | #define STEPCHARGE_INM_AN1 STEPCHARGE_INM(1) | |
97 | #define STEPCHARGE_INP_MASK (0xF << 19) | |
98 | #define STEPCHARGE_INP(val) (val << 19) | |
99 | #define STEPCHARGE_INP_AN1 STEPCHARGE_INP(1) | |
100 | #define STEPCHARGE_RFM_MASK (3 << 23) | |
101 | #define STEPCHARGE_RFM(val) (val << 23) | |
102 | #define STEPCHARGE_RFM_XNUR STEPCHARGE_RFM(1) | |
103 | ||
104 | /* Charge delay */ | |
105 | #define CHARGEDLY_OPEN_MASK (0x3FFFF << 0) | |
106 | #define CHARGEDLY_OPEN(val) (val << 0) | |
107 | #define CHARGEDLY_OPENDLY CHARGEDLY_OPEN(1) | |
108 | ||
109 | /* Control register */ | |
110 | #define CNTRLREG_TSCSSENB BIT(0) | |
111 | #define CNTRLREG_STEPID BIT(1) | |
112 | #define CNTRLREG_STEPCONFIGWRT BIT(2) | |
113 | #define CNTRLREG_AFE_CTRL_MASK (3 << 5) | |
114 | #define CNTRLREG_AFE_CTRL(val) (val << 5) | |
115 | #define CNTRLREG_4WIRE CNTRLREG_AFE_CTRL(1) | |
116 | #define CNTRLREG_5WIRE CNTRLREG_AFE_CTRL(2) | |
117 | #define CNTRLREG_8WIRE CNTRLREG_AFE_CTRL(3) | |
118 | #define CNTRLREG_TSCENB BIT(7) | |
119 | ||
120 | #define ADCFSM_STEPID 0x10 | |
1b8be32e RP |
121 | #define SEQ_SETTLE 275 |
122 | #define ADC_CLK 3000000 | |
123 | #define MAX_12BIT ((1 << 12) - 1) | |
1b8be32e RP |
124 | |
125 | struct tscadc { | |
126 | struct input_dev *input; | |
127 | struct clk *tsc_ick; | |
128 | void __iomem *tsc_base; | |
129 | unsigned int irq; | |
130 | unsigned int wires; | |
131 | unsigned int x_plate_resistance; | |
132 | bool pen_down; | |
d1fb5743 | 133 | int steps_to_configure; |
1b8be32e RP |
134 | }; |
135 | ||
136 | static unsigned int tscadc_readl(struct tscadc *ts, unsigned int reg) | |
137 | { | |
138 | return readl(ts->tsc_base + reg); | |
139 | } | |
140 | ||
141 | static void tscadc_writel(struct tscadc *tsc, unsigned int reg, | |
142 | unsigned int val) | |
143 | { | |
144 | writel(val, tsc->tsc_base + reg); | |
145 | } | |
146 | ||
147 | static void tscadc_step_config(struct tscadc *ts_dev) | |
148 | { | |
149 | unsigned int config; | |
d1fb5743 | 150 | int i, total_steps; |
1b8be32e RP |
151 | |
152 | /* Configure the Step registers */ | |
d1fb5743 | 153 | total_steps = 2 * ts_dev->steps_to_configure; |
1b8be32e RP |
154 | |
155 | config = STEPCONFIG_MODE_HWSYNC | | |
33f5cc60 | 156 | STEPCONFIG_AVG_16 | STEPCONFIG_XPP; |
1b8be32e RP |
157 | switch (ts_dev->wires) { |
158 | case 4: | |
33f5cc60 | 159 | config |= STEPCONFIG_INP_AN2 | STEPCONFIG_XNN; |
1b8be32e RP |
160 | break; |
161 | case 5: | |
162 | config |= STEPCONFIG_YNN | | |
33f5cc60 | 163 | STEPCONFIG_INP_AN4 | STEPCONFIG_XNN | |
1b8be32e RP |
164 | STEPCONFIG_YPP; |
165 | break; | |
166 | case 8: | |
33f5cc60 | 167 | config |= STEPCONFIG_INP_AN2 | STEPCONFIG_XNN; |
1b8be32e RP |
168 | break; |
169 | } | |
170 | ||
d1fb5743 | 171 | for (i = 1; i <= ts_dev->steps_to_configure; i++) { |
1b8be32e RP |
172 | tscadc_writel(ts_dev, REG_STEPCONFIG(i), config); |
173 | tscadc_writel(ts_dev, REG_STEPDELAY(i), STEPCONFIG_OPENDLY); | |
174 | } | |
175 | ||
176 | config = 0; | |
177 | config = STEPCONFIG_MODE_HWSYNC | | |
33f5cc60 PR |
178 | STEPCONFIG_AVG_16 | STEPCONFIG_YNN | |
179 | STEPCONFIG_INM_ADCREFM | STEPCONFIG_FIFO1; | |
1b8be32e RP |
180 | switch (ts_dev->wires) { |
181 | case 4: | |
182 | config |= STEPCONFIG_YPP; | |
183 | break; | |
184 | case 5: | |
33f5cc60 | 185 | config |= STEPCONFIG_XPP | STEPCONFIG_INP_AN4 | |
1b8be32e RP |
186 | STEPCONFIG_XNP | STEPCONFIG_YPN; |
187 | break; | |
188 | case 8: | |
189 | config |= STEPCONFIG_YPP; | |
190 | break; | |
191 | } | |
192 | ||
d1fb5743 | 193 | for (i = (ts_dev->steps_to_configure + 1); i <= total_steps; i++) { |
1b8be32e RP |
194 | tscadc_writel(ts_dev, REG_STEPCONFIG(i), config); |
195 | tscadc_writel(ts_dev, REG_STEPDELAY(i), STEPCONFIG_OPENDLY); | |
196 | } | |
197 | ||
198 | config = 0; | |
199 | /* Charge step configuration */ | |
200 | config = STEPCONFIG_XPP | STEPCONFIG_YNN | | |
33f5cc60 PR |
201 | STEPCHARGE_RFP_XPUL | STEPCHARGE_RFM_XNUR | |
202 | STEPCHARGE_INM_AN1 | STEPCHARGE_INP_AN1; | |
1b8be32e RP |
203 | |
204 | tscadc_writel(ts_dev, REG_CHARGECONFIG, config); | |
33f5cc60 | 205 | tscadc_writel(ts_dev, REG_CHARGEDELAY, CHARGEDLY_OPENDLY); |
1b8be32e RP |
206 | |
207 | config = 0; | |
208 | /* Configure to calculate pressure */ | |
209 | config = STEPCONFIG_MODE_HWSYNC | | |
33f5cc60 PR |
210 | STEPCONFIG_AVG_16 | STEPCONFIG_YPP | |
211 | STEPCONFIG_XNN | STEPCONFIG_INM_ADCREFM; | |
d1fb5743 PR |
212 | tscadc_writel(ts_dev, REG_STEPCONFIG(total_steps + 1), config); |
213 | tscadc_writel(ts_dev, REG_STEPDELAY(total_steps + 1), | |
214 | STEPCONFIG_OPENDLY); | |
1b8be32e | 215 | |
33f5cc60 | 216 | config |= STEPCONFIG_INP_AN3 | STEPCONFIG_FIFO1; |
d1fb5743 PR |
217 | tscadc_writel(ts_dev, REG_STEPCONFIG(total_steps + 2), config); |
218 | tscadc_writel(ts_dev, REG_STEPDELAY(total_steps + 2), | |
219 | STEPCONFIG_OPENDLY); | |
1b8be32e RP |
220 | |
221 | tscadc_writel(ts_dev, REG_SE, STPENB_STEPENB); | |
222 | } | |
223 | ||
224 | static void tscadc_idle_config(struct tscadc *ts_config) | |
225 | { | |
226 | unsigned int idleconfig; | |
227 | ||
228 | idleconfig = STEPCONFIG_YNN | | |
33f5cc60 PR |
229 | STEPCONFIG_INM_ADCREFM | |
230 | STEPCONFIG_YPN | STEPCONFIG_INP_ADCREFM; | |
1b8be32e RP |
231 | tscadc_writel(ts_config, REG_IDLECONFIG, idleconfig); |
232 | } | |
233 | ||
234 | static void tscadc_read_coordinates(struct tscadc *ts_dev, | |
235 | unsigned int *x, unsigned int *y) | |
236 | { | |
237 | unsigned int fifocount = tscadc_readl(ts_dev, REG_FIFO0CNT); | |
238 | unsigned int prev_val_x = ~0, prev_val_y = ~0; | |
239 | unsigned int prev_diff_x = ~0, prev_diff_y = ~0; | |
240 | unsigned int read, diff; | |
241 | unsigned int i; | |
242 | ||
243 | /* | |
244 | * Delta filter is used to remove large variations in sampled | |
245 | * values from ADC. The filter tries to predict where the next | |
246 | * coordinate could be. This is done by taking a previous | |
247 | * coordinate and subtracting it form current one. Further the | |
248 | * algorithm compares the difference with that of a present value, | |
249 | * if true the value is reported to the sub system. | |
250 | */ | |
251 | for (i = 0; i < fifocount - 1; i++) { | |
252 | read = tscadc_readl(ts_dev, REG_FIFO0) & 0xfff; | |
253 | diff = abs(read - prev_val_x); | |
254 | if (diff < prev_diff_x) { | |
255 | prev_diff_x = diff; | |
256 | *x = read; | |
257 | } | |
258 | prev_val_x = read; | |
259 | ||
260 | read = tscadc_readl(ts_dev, REG_FIFO1) & 0xfff; | |
261 | diff = abs(read - prev_val_y); | |
262 | if (diff < prev_diff_y) { | |
263 | prev_diff_y = diff; | |
264 | *y = read; | |
265 | } | |
266 | prev_val_y = read; | |
267 | } | |
268 | } | |
269 | ||
270 | static irqreturn_t tscadc_irq(int irq, void *dev) | |
271 | { | |
272 | struct tscadc *ts_dev = dev; | |
273 | struct input_dev *input_dev = ts_dev->input; | |
274 | unsigned int status, irqclr = 0; | |
275 | unsigned int x = 0, y = 0; | |
276 | unsigned int z1, z2, z; | |
277 | unsigned int fsm; | |
278 | ||
279 | status = tscadc_readl(ts_dev, REG_IRQSTATUS); | |
280 | if (status & IRQENB_FIFO1THRES) { | |
281 | tscadc_read_coordinates(ts_dev, &x, &y); | |
282 | ||
283 | z1 = tscadc_readl(ts_dev, REG_FIFO0) & 0xfff; | |
284 | z2 = tscadc_readl(ts_dev, REG_FIFO1) & 0xfff; | |
285 | ||
286 | if (ts_dev->pen_down && z1 != 0 && z2 != 0) { | |
287 | /* | |
288 | * Calculate pressure using formula | |
289 | * Resistance(touch) = x plate resistance * | |
290 | * x postion/4096 * ((z2 / z1) - 1) | |
291 | */ | |
292 | z = z2 - z1; | |
293 | z *= x; | |
294 | z *= ts_dev->x_plate_resistance; | |
295 | z /= z1; | |
296 | z = (z + 2047) >> 12; | |
297 | ||
298 | if (z <= MAX_12BIT) { | |
299 | input_report_abs(input_dev, ABS_X, x); | |
300 | input_report_abs(input_dev, ABS_Y, y); | |
301 | input_report_abs(input_dev, ABS_PRESSURE, z); | |
302 | input_report_key(input_dev, BTN_TOUCH, 1); | |
303 | input_sync(input_dev); | |
304 | } | |
305 | } | |
306 | irqclr |= IRQENB_FIFO1THRES; | |
307 | } | |
308 | ||
309 | /* | |
310 | * Time for sequencer to settle, to read | |
311 | * correct state of the sequencer. | |
312 | */ | |
313 | udelay(SEQ_SETTLE); | |
314 | ||
315 | status = tscadc_readl(ts_dev, REG_RAWIRQSTATUS); | |
316 | if (status & IRQENB_PENUP) { | |
317 | /* Pen up event */ | |
318 | fsm = tscadc_readl(ts_dev, REG_ADCFSM); | |
319 | if (fsm == ADCFSM_STEPID) { | |
320 | ts_dev->pen_down = false; | |
321 | input_report_key(input_dev, BTN_TOUCH, 0); | |
322 | input_report_abs(input_dev, ABS_PRESSURE, 0); | |
323 | input_sync(input_dev); | |
324 | } else { | |
325 | ts_dev->pen_down = true; | |
326 | } | |
327 | irqclr |= IRQENB_PENUP; | |
328 | } | |
329 | ||
330 | tscadc_writel(ts_dev, REG_IRQSTATUS, irqclr); | |
331 | /* check pending interrupts */ | |
332 | tscadc_writel(ts_dev, REG_IRQEOI, 0x0); | |
333 | ||
334 | tscadc_writel(ts_dev, REG_SE, STPENB_STEPENB); | |
335 | return IRQ_HANDLED; | |
336 | } | |
337 | ||
338 | /* | |
339 | * The functions for inserting/removing driver as a module. | |
340 | */ | |
341 | ||
342 | static int __devinit tscadc_probe(struct platform_device *pdev) | |
343 | { | |
344 | const struct tsc_data *pdata = pdev->dev.platform_data; | |
345 | struct resource *res; | |
346 | struct tscadc *ts_dev; | |
347 | struct input_dev *input_dev; | |
348 | struct clk *clk; | |
349 | int err; | |
350 | int clk_value, ctrl, irq; | |
351 | ||
352 | if (!pdata) { | |
353 | dev_err(&pdev->dev, "missing platform data.\n"); | |
354 | return -EINVAL; | |
355 | } | |
356 | ||
357 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
358 | if (!res) { | |
359 | dev_err(&pdev->dev, "no memory resource defined.\n"); | |
360 | return -EINVAL; | |
361 | } | |
362 | ||
363 | irq = platform_get_irq(pdev, 0); | |
364 | if (irq < 0) { | |
365 | dev_err(&pdev->dev, "no irq ID is specified.\n"); | |
366 | return -EINVAL; | |
367 | } | |
368 | ||
369 | /* Allocate memory for device */ | |
370 | ts_dev = kzalloc(sizeof(struct tscadc), GFP_KERNEL); | |
371 | input_dev = input_allocate_device(); | |
372 | if (!ts_dev || !input_dev) { | |
373 | dev_err(&pdev->dev, "failed to allocate memory.\n"); | |
374 | err = -ENOMEM; | |
375 | goto err_free_mem; | |
376 | } | |
377 | ||
378 | ts_dev->input = input_dev; | |
379 | ts_dev->irq = irq; | |
380 | ts_dev->wires = pdata->wires; | |
381 | ts_dev->x_plate_resistance = pdata->x_plate_resistance; | |
d1fb5743 | 382 | ts_dev->steps_to_configure = pdata->steps_to_configure; |
1b8be32e RP |
383 | |
384 | res = request_mem_region(res->start, resource_size(res), pdev->name); | |
385 | if (!res) { | |
386 | dev_err(&pdev->dev, "failed to reserve registers.\n"); | |
387 | err = -EBUSY; | |
388 | goto err_free_mem; | |
389 | } | |
390 | ||
391 | ts_dev->tsc_base = ioremap(res->start, resource_size(res)); | |
392 | if (!ts_dev->tsc_base) { | |
393 | dev_err(&pdev->dev, "failed to map registers.\n"); | |
394 | err = -ENOMEM; | |
395 | goto err_release_mem_region; | |
396 | } | |
397 | ||
398 | err = request_irq(ts_dev->irq, tscadc_irq, | |
399 | 0, pdev->dev.driver->name, ts_dev); | |
400 | if (err) { | |
401 | dev_err(&pdev->dev, "failed to allocate irq.\n"); | |
402 | goto err_unmap_regs; | |
403 | } | |
404 | ||
405 | ts_dev->tsc_ick = clk_get(&pdev->dev, "adc_tsc_ick"); | |
406 | if (IS_ERR(ts_dev->tsc_ick)) { | |
407 | dev_err(&pdev->dev, "failed to get TSC ick\n"); | |
408 | goto err_free_irq; | |
409 | } | |
410 | clk_enable(ts_dev->tsc_ick); | |
411 | ||
412 | clk = clk_get(&pdev->dev, "adc_tsc_fck"); | |
413 | if (IS_ERR(clk)) { | |
414 | dev_err(&pdev->dev, "failed to get TSC fck\n"); | |
415 | err = PTR_ERR(clk); | |
416 | goto err_disable_clk; | |
417 | } | |
418 | ||
419 | clk_value = clk_get_rate(clk) / ADC_CLK; | |
420 | clk_put(clk); | |
421 | ||
422 | if (clk_value < 7) { | |
423 | dev_err(&pdev->dev, "clock input less than min clock requirement\n"); | |
424 | goto err_disable_clk; | |
425 | } | |
426 | /* CLKDIV needs to be configured to the value minus 1 */ | |
427 | tscadc_writel(ts_dev, REG_CLKDIV, clk_value - 1); | |
428 | ||
429 | /* Enable wake-up of the SoC using touchscreen */ | |
430 | tscadc_writel(ts_dev, REG_IRQWAKEUP, IRQWKUP_ENB); | |
431 | ||
432 | ctrl = CNTRLREG_STEPCONFIGWRT | | |
433 | CNTRLREG_TSCENB | | |
434 | CNTRLREG_STEPID; | |
435 | switch (ts_dev->wires) { | |
436 | case 4: | |
437 | ctrl |= CNTRLREG_4WIRE; | |
438 | break; | |
439 | case 5: | |
440 | ctrl |= CNTRLREG_5WIRE; | |
441 | break; | |
442 | case 8: | |
443 | ctrl |= CNTRLREG_8WIRE; | |
444 | break; | |
445 | } | |
446 | tscadc_writel(ts_dev, REG_CTRL, ctrl); | |
447 | ||
448 | tscadc_idle_config(ts_dev); | |
449 | tscadc_writel(ts_dev, REG_IRQENABLE, IRQENB_FIFO1THRES); | |
450 | tscadc_step_config(ts_dev); | |
d1fb5743 | 451 | tscadc_writel(ts_dev, REG_FIFO1THR, ts_dev->steps_to_configure); |
1b8be32e RP |
452 | |
453 | ctrl |= CNTRLREG_TSCSSENB; | |
454 | tscadc_writel(ts_dev, REG_CTRL, ctrl); | |
455 | ||
456 | input_dev->name = "ti-tsc-adc"; | |
457 | input_dev->dev.parent = &pdev->dev; | |
458 | ||
459 | input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); | |
460 | input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); | |
461 | ||
462 | input_set_abs_params(input_dev, ABS_X, 0, MAX_12BIT, 0, 0); | |
463 | input_set_abs_params(input_dev, ABS_Y, 0, MAX_12BIT, 0, 0); | |
464 | input_set_abs_params(input_dev, ABS_PRESSURE, 0, MAX_12BIT, 0, 0); | |
465 | ||
466 | /* register to the input system */ | |
467 | err = input_register_device(input_dev); | |
468 | if (err) | |
469 | goto err_disable_clk; | |
470 | ||
471 | platform_set_drvdata(pdev, ts_dev); | |
472 | return 0; | |
473 | ||
474 | err_disable_clk: | |
475 | clk_disable(ts_dev->tsc_ick); | |
476 | clk_put(ts_dev->tsc_ick); | |
477 | err_free_irq: | |
478 | free_irq(ts_dev->irq, ts_dev); | |
479 | err_unmap_regs: | |
480 | iounmap(ts_dev->tsc_base); | |
481 | err_release_mem_region: | |
482 | release_mem_region(res->start, resource_size(res)); | |
483 | err_free_mem: | |
484 | input_free_device(input_dev); | |
485 | kfree(ts_dev); | |
486 | return err; | |
487 | } | |
488 | ||
489 | static int __devexit tscadc_remove(struct platform_device *pdev) | |
490 | { | |
491 | struct tscadc *ts_dev = platform_get_drvdata(pdev); | |
492 | struct resource *res; | |
493 | ||
494 | free_irq(ts_dev->irq, ts_dev); | |
495 | ||
496 | input_unregister_device(ts_dev->input); | |
497 | ||
498 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
499 | iounmap(ts_dev->tsc_base); | |
500 | release_mem_region(res->start, resource_size(res)); | |
501 | ||
502 | clk_disable(ts_dev->tsc_ick); | |
503 | clk_put(ts_dev->tsc_ick); | |
504 | ||
505 | kfree(ts_dev); | |
506 | ||
507 | platform_set_drvdata(pdev, NULL); | |
508 | return 0; | |
509 | } | |
510 | ||
511 | static struct platform_driver ti_tsc_driver = { | |
512 | .probe = tscadc_probe, | |
513 | .remove = __devexit_p(tscadc_remove), | |
514 | .driver = { | |
515 | .name = "tsc", | |
516 | .owner = THIS_MODULE, | |
517 | }, | |
518 | }; | |
519 | module_platform_driver(ti_tsc_driver); | |
520 | ||
521 | MODULE_DESCRIPTION("TI touchscreen controller driver"); | |
522 | MODULE_AUTHOR("Rachna Patil <rachna@ti.com>"); | |
523 | MODULE_LICENSE("GPL"); |