Commit | Line | Data |
---|---|---|
7e3f7375 WZ |
1 | /* |
2 | * Copyright (c) 2008 Nuvoton technology corporation. | |
3 | * | |
4 | * Wan ZongShun <mcuos.com@gmail.com> | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify | |
7 | * it under the terms of the GNU General Public License as published by | |
8 | * the Free Software Foundation;version 2 of the License. | |
9 | * | |
10 | */ | |
11 | ||
12 | #include <linux/delay.h> | |
13 | #include <linux/module.h> | |
14 | #include <linux/platform_device.h> | |
15 | #include <linux/io.h> | |
b7788c5f | 16 | #include <linux/clk.h> |
7e3f7375 WZ |
17 | #include <linux/input.h> |
18 | #include <linux/interrupt.h> | |
5a0e3ad6 | 19 | #include <linux/slab.h> |
7e3f7375 WZ |
20 | |
21 | /* ADC controller bit defines */ | |
22 | #define ADC_DELAY 0xf00 | |
23 | #define ADC_DOWN 0x01 | |
24 | #define ADC_TSC_Y (0x01 << 8) | |
25 | #define ADC_TSC_X (0x00 << 8) | |
26 | #define TSC_FOURWIRE (~(0x03 << 1)) | |
27 | #define ADC_CLK_EN (0x01 << 28) /* ADC clock enable */ | |
28 | #define ADC_READ_CON (0x01 << 12) | |
29 | #define ADC_CONV (0x01 << 13) | |
30 | #define ADC_SEMIAUTO (0x01 << 14) | |
31 | #define ADC_WAITTRIG (0x03 << 14) | |
32 | #define ADC_RST1 (0x01 << 16) | |
33 | #define ADC_RST0 (0x00 << 16) | |
34 | #define ADC_EN (0x01 << 17) | |
35 | #define ADC_INT (0x01 << 18) | |
36 | #define WT_INT (0x01 << 20) | |
37 | #define ADC_INT_EN (0x01 << 21) | |
38 | #define LVD_INT_EN (0x01 << 22) | |
39 | #define WT_INT_EN (0x01 << 23) | |
40 | #define ADC_DIV (0x04 << 1) /* div = 6 */ | |
41 | ||
42 | enum ts_state { | |
43 | TS_WAIT_NEW_PACKET, /* We are waiting next touch report */ | |
44 | TS_WAIT_X_COORD, /* We are waiting for ADC to report X coord */ | |
45 | TS_WAIT_Y_COORD, /* We are waiting for ADC to report Y coord */ | |
46 | TS_IDLE, /* Input device is closed, don't do anything */ | |
47 | }; | |
48 | ||
49 | struct w90p910_ts { | |
50 | struct input_dev *input; | |
51 | struct timer_list timer; | |
b7788c5f | 52 | struct clk *clk; |
7e3f7375 | 53 | int irq_num; |
7e3f7375 WZ |
54 | void __iomem *ts_reg; |
55 | spinlock_t lock; | |
56 | enum ts_state state; | |
57 | }; | |
58 | ||
59 | static void w90p910_report_event(struct w90p910_ts *w90p910_ts, bool down) | |
60 | { | |
61 | struct input_dev *dev = w90p910_ts->input; | |
62 | ||
63 | if (down) { | |
64 | input_report_abs(dev, ABS_X, | |
65 | __raw_readl(w90p910_ts->ts_reg + 0x0c)); | |
66 | input_report_abs(dev, ABS_Y, | |
67 | __raw_readl(w90p910_ts->ts_reg + 0x10)); | |
68 | } | |
69 | ||
70 | input_report_key(dev, BTN_TOUCH, down); | |
71 | input_sync(dev); | |
72 | } | |
73 | ||
74 | static void w90p910_prepare_x_reading(struct w90p910_ts *w90p910_ts) | |
75 | { | |
76 | unsigned long ctlreg; | |
77 | ||
78 | __raw_writel(ADC_TSC_X, w90p910_ts->ts_reg + 0x04); | |
79 | ctlreg = __raw_readl(w90p910_ts->ts_reg); | |
80 | ctlreg &= ~(ADC_WAITTRIG | WT_INT | WT_INT_EN); | |
81 | ctlreg |= ADC_SEMIAUTO | ADC_INT_EN | ADC_CONV; | |
82 | __raw_writel(ctlreg, w90p910_ts->ts_reg); | |
83 | ||
84 | w90p910_ts->state = TS_WAIT_X_COORD; | |
85 | } | |
86 | ||
87 | static void w90p910_prepare_y_reading(struct w90p910_ts *w90p910_ts) | |
88 | { | |
89 | unsigned long ctlreg; | |
90 | ||
91 | __raw_writel(ADC_TSC_Y, w90p910_ts->ts_reg + 0x04); | |
92 | ctlreg = __raw_readl(w90p910_ts->ts_reg); | |
93 | ctlreg &= ~(ADC_WAITTRIG | ADC_INT | WT_INT_EN); | |
94 | ctlreg |= ADC_SEMIAUTO | ADC_INT_EN | ADC_CONV; | |
95 | __raw_writel(ctlreg, w90p910_ts->ts_reg); | |
96 | ||
97 | w90p910_ts->state = TS_WAIT_Y_COORD; | |
98 | } | |
99 | ||
100 | static void w90p910_prepare_next_packet(struct w90p910_ts *w90p910_ts) | |
101 | { | |
102 | unsigned long ctlreg; | |
103 | ||
104 | ctlreg = __raw_readl(w90p910_ts->ts_reg); | |
105 | ctlreg &= ~(ADC_INT | ADC_INT_EN | ADC_SEMIAUTO | ADC_CONV); | |
106 | ctlreg |= ADC_WAITTRIG | WT_INT_EN; | |
107 | __raw_writel(ctlreg, w90p910_ts->ts_reg); | |
108 | ||
109 | w90p910_ts->state = TS_WAIT_NEW_PACKET; | |
110 | } | |
111 | ||
112 | static irqreturn_t w90p910_ts_interrupt(int irq, void *dev_id) | |
113 | { | |
114 | struct w90p910_ts *w90p910_ts = dev_id; | |
115 | unsigned long flags; | |
116 | ||
117 | spin_lock_irqsave(&w90p910_ts->lock, flags); | |
118 | ||
119 | switch (w90p910_ts->state) { | |
120 | case TS_WAIT_NEW_PACKET: | |
121 | /* | |
122 | * The controller only generates interrupts when pen | |
123 | * is down. | |
124 | */ | |
125 | del_timer(&w90p910_ts->timer); | |
126 | w90p910_prepare_x_reading(w90p910_ts); | |
127 | break; | |
128 | ||
129 | ||
130 | case TS_WAIT_X_COORD: | |
131 | w90p910_prepare_y_reading(w90p910_ts); | |
132 | break; | |
133 | ||
134 | case TS_WAIT_Y_COORD: | |
135 | w90p910_report_event(w90p910_ts, true); | |
136 | w90p910_prepare_next_packet(w90p910_ts); | |
137 | mod_timer(&w90p910_ts->timer, jiffies + msecs_to_jiffies(100)); | |
138 | break; | |
139 | ||
140 | case TS_IDLE: | |
141 | break; | |
142 | } | |
143 | ||
144 | spin_unlock_irqrestore(&w90p910_ts->lock, flags); | |
145 | ||
146 | return IRQ_HANDLED; | |
147 | } | |
148 | ||
149 | static void w90p910_check_pen_up(unsigned long data) | |
150 | { | |
151 | struct w90p910_ts *w90p910_ts = (struct w90p910_ts *) data; | |
152 | unsigned long flags; | |
153 | ||
154 | spin_lock_irqsave(&w90p910_ts->lock, flags); | |
155 | ||
156 | if (w90p910_ts->state == TS_WAIT_NEW_PACKET && | |
157 | !(__raw_readl(w90p910_ts->ts_reg + 0x04) & ADC_DOWN)) { | |
158 | ||
159 | w90p910_report_event(w90p910_ts, false); | |
160 | } | |
161 | ||
162 | spin_unlock_irqrestore(&w90p910_ts->lock, flags); | |
163 | } | |
164 | ||
165 | static int w90p910_open(struct input_dev *dev) | |
166 | { | |
167 | struct w90p910_ts *w90p910_ts = input_get_drvdata(dev); | |
168 | unsigned long val; | |
169 | ||
170 | /* enable the ADC clock */ | |
b7788c5f | 171 | clk_enable(w90p910_ts->clk); |
7e3f7375 WZ |
172 | |
173 | __raw_writel(ADC_RST1, w90p910_ts->ts_reg); | |
174 | msleep(1); | |
175 | __raw_writel(ADC_RST0, w90p910_ts->ts_reg); | |
176 | msleep(1); | |
177 | ||
178 | /* set delay and screen type */ | |
179 | val = __raw_readl(w90p910_ts->ts_reg + 0x04); | |
180 | __raw_writel(val & TSC_FOURWIRE, w90p910_ts->ts_reg + 0x04); | |
181 | __raw_writel(ADC_DELAY, w90p910_ts->ts_reg + 0x08); | |
182 | ||
183 | w90p910_ts->state = TS_WAIT_NEW_PACKET; | |
184 | wmb(); | |
185 | ||
186 | /* set trigger mode */ | |
187 | val = __raw_readl(w90p910_ts->ts_reg); | |
188 | val |= ADC_WAITTRIG | ADC_DIV | ADC_EN | WT_INT_EN; | |
189 | __raw_writel(val, w90p910_ts->ts_reg); | |
190 | ||
191 | return 0; | |
192 | } | |
193 | ||
194 | static void w90p910_close(struct input_dev *dev) | |
195 | { | |
196 | struct w90p910_ts *w90p910_ts = input_get_drvdata(dev); | |
197 | unsigned long val; | |
198 | ||
199 | /* disable trigger mode */ | |
200 | ||
201 | spin_lock_irq(&w90p910_ts->lock); | |
202 | ||
203 | w90p910_ts->state = TS_IDLE; | |
204 | ||
205 | val = __raw_readl(w90p910_ts->ts_reg); | |
206 | val &= ~(ADC_WAITTRIG | ADC_DIV | ADC_EN | WT_INT_EN | ADC_INT_EN); | |
207 | __raw_writel(val, w90p910_ts->ts_reg); | |
208 | ||
209 | spin_unlock_irq(&w90p910_ts->lock); | |
210 | ||
211 | /* Now that interrupts are shut off we can safely delete timer */ | |
212 | del_timer_sync(&w90p910_ts->timer); | |
213 | ||
214 | /* stop the ADC clock */ | |
b7788c5f | 215 | clk_disable(w90p910_ts->clk); |
7e3f7375 WZ |
216 | } |
217 | ||
5298cc4c | 218 | static int w90x900ts_probe(struct platform_device *pdev) |
7e3f7375 WZ |
219 | { |
220 | struct w90p910_ts *w90p910_ts; | |
221 | struct input_dev *input_dev; | |
222 | struct resource *res; | |
223 | int err; | |
224 | ||
225 | w90p910_ts = kzalloc(sizeof(struct w90p910_ts), GFP_KERNEL); | |
226 | input_dev = input_allocate_device(); | |
227 | if (!w90p910_ts || !input_dev) { | |
228 | err = -ENOMEM; | |
229 | goto fail1; | |
230 | } | |
231 | ||
232 | w90p910_ts->input = input_dev; | |
233 | w90p910_ts->state = TS_IDLE; | |
234 | spin_lock_init(&w90p910_ts->lock); | |
235 | setup_timer(&w90p910_ts->timer, w90p910_check_pen_up, | |
5b39187f | 236 | (unsigned long)w90p910_ts); |
7e3f7375 WZ |
237 | |
238 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); | |
239 | if (!res) { | |
240 | err = -ENXIO; | |
241 | goto fail1; | |
242 | } | |
243 | ||
72398e4b | 244 | if (!request_mem_region(res->start, resource_size(res), |
7e3f7375 WZ |
245 | pdev->name)) { |
246 | err = -EBUSY; | |
247 | goto fail1; | |
248 | } | |
249 | ||
72398e4b | 250 | w90p910_ts->ts_reg = ioremap(res->start, resource_size(res)); |
7e3f7375 WZ |
251 | if (!w90p910_ts->ts_reg) { |
252 | err = -ENOMEM; | |
253 | goto fail2; | |
254 | } | |
255 | ||
b7788c5f WZ |
256 | w90p910_ts->clk = clk_get(&pdev->dev, NULL); |
257 | if (IS_ERR(w90p910_ts->clk)) { | |
258 | err = PTR_ERR(w90p910_ts->clk); | |
7e3f7375 WZ |
259 | goto fail3; |
260 | } | |
261 | ||
7e3f7375 WZ |
262 | input_dev->name = "W90P910 TouchScreen"; |
263 | input_dev->phys = "w90p910ts/event0"; | |
264 | input_dev->id.bustype = BUS_HOST; | |
265 | input_dev->id.vendor = 0x0005; | |
266 | input_dev->id.product = 0x0001; | |
267 | input_dev->id.version = 0x0100; | |
268 | input_dev->dev.parent = &pdev->dev; | |
269 | input_dev->open = w90p910_open; | |
270 | input_dev->close = w90p910_close; | |
271 | ||
272 | input_dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_ABS); | |
273 | input_dev->keybit[BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH); | |
274 | ||
275 | input_set_abs_params(input_dev, ABS_X, 0, 0x400, 0, 0); | |
276 | input_set_abs_params(input_dev, ABS_Y, 0, 0x400, 0, 0); | |
277 | ||
278 | input_set_drvdata(input_dev, w90p910_ts); | |
279 | ||
280 | w90p910_ts->irq_num = platform_get_irq(pdev, 0); | |
281 | if (request_irq(w90p910_ts->irq_num, w90p910_ts_interrupt, | |
ec4665c4 | 282 | 0, "w90p910ts", w90p910_ts)) { |
7e3f7375 | 283 | err = -EBUSY; |
b7788c5f | 284 | goto fail4; |
7e3f7375 WZ |
285 | } |
286 | ||
287 | err = input_register_device(w90p910_ts->input); | |
288 | if (err) | |
b7788c5f | 289 | goto fail5; |
7e3f7375 WZ |
290 | |
291 | platform_set_drvdata(pdev, w90p910_ts); | |
292 | ||
293 | return 0; | |
294 | ||
b7788c5f WZ |
295 | fail5: free_irq(w90p910_ts->irq_num, w90p910_ts); |
296 | fail4: clk_put(w90p910_ts->clk); | |
7e3f7375 | 297 | fail3: iounmap(w90p910_ts->ts_reg); |
72398e4b | 298 | fail2: release_mem_region(res->start, resource_size(res)); |
7e3f7375 WZ |
299 | fail1: input_free_device(input_dev); |
300 | kfree(w90p910_ts); | |
301 | return err; | |
302 | } | |
303 | ||
e2619cf7 | 304 | static int w90x900ts_remove(struct platform_device *pdev) |
7e3f7375 WZ |
305 | { |
306 | struct w90p910_ts *w90p910_ts = platform_get_drvdata(pdev); | |
307 | struct resource *res; | |
308 | ||
309 | free_irq(w90p910_ts->irq_num, w90p910_ts); | |
310 | del_timer_sync(&w90p910_ts->timer); | |
311 | iounmap(w90p910_ts->ts_reg); | |
312 | ||
b7788c5f WZ |
313 | clk_put(w90p910_ts->clk); |
314 | ||
7e3f7375 | 315 | res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
72398e4b | 316 | release_mem_region(res->start, resource_size(res)); |
7e3f7375 WZ |
317 | |
318 | input_unregister_device(w90p910_ts->input); | |
319 | kfree(w90p910_ts); | |
320 | ||
7e3f7375 WZ |
321 | return 0; |
322 | } | |
323 | ||
324 | static struct platform_driver w90x900ts_driver = { | |
325 | .probe = w90x900ts_probe, | |
1cb0aa88 | 326 | .remove = w90x900ts_remove, |
7e3f7375 | 327 | .driver = { |
35c9221a | 328 | .name = "nuc900-ts", |
7e3f7375 WZ |
329 | }, |
330 | }; | |
cdcc96e2 | 331 | module_platform_driver(w90x900ts_driver); |
7e3f7375 WZ |
332 | |
333 | MODULE_AUTHOR("Wan ZongShun <mcuos.com@gmail.com>"); | |
334 | MODULE_DESCRIPTION("w90p910 touch screen driver!"); | |
335 | MODULE_LICENSE("GPL"); | |
35c9221a | 336 | MODULE_ALIAS("platform:nuc900-ts"); |