]>
Commit | Line | Data |
---|---|---|
5c6a7a62 OS |
1 | #include <linux/module.h> |
2 | #include <linux/i2c.h> | |
3 | #include <linux/interrupt.h> | |
4 | #include <linux/slab.h> | |
5 | #include <linux/input.h> | |
6 | #include <linux/input/mt.h> | |
7 | #include <linux/delay.h> | |
8 | #include <linux/workqueue.h> | |
201f3c80 | 9 | #include <linux/gpio/consumer.h> |
e3559442 | 10 | #include <asm/unaligned.h> |
5c6a7a62 OS |
11 | |
12 | #define MAX_TOUCHES 2 | |
13 | #define DEFAULT_POLL_PERIOD 20 | |
14 | ||
15 | /* Touchscreen commands */ | |
16 | #define REG_TOUCHDATA 0x10 | |
17 | #define REG_PANEL_INFO 0x20 | |
18 | #define REG_FIRMWARE_VERSION 0x40 | |
19 | #define REG_CALIBRATE 0xcc | |
20 | ||
e3559442 | 21 | struct panel_info { |
5c6a7a62 OS |
22 | u8 x_low; |
23 | u8 x_high; | |
24 | u8 y_low; | |
25 | u8 y_high; | |
5c6a7a62 OS |
26 | u8 xchannel_num; |
27 | u8 ychannel_num; | |
28 | } __packed; | |
29 | ||
30 | struct firmware_version { | |
31 | u8 id; | |
32 | u8 major; | |
33 | u8 minor; | |
34 | } __packed; | |
35 | ||
36 | struct ili210x { | |
37 | struct i2c_client *client; | |
38 | struct input_dev *input; | |
5c6a7a62 OS |
39 | unsigned int poll_period; |
40 | struct delayed_work dwork; | |
201f3c80 | 41 | struct gpio_desc *reset_gpio; |
5c6a7a62 OS |
42 | }; |
43 | ||
44 | static int ili210x_read_reg(struct i2c_client *client, u8 reg, void *buf, | |
45 | size_t len) | |
46 | { | |
47 | struct i2c_msg msg[2] = { | |
48 | { | |
49 | .addr = client->addr, | |
50 | .flags = 0, | |
51 | .len = 1, | |
52 | .buf = ®, | |
53 | }, | |
54 | { | |
55 | .addr = client->addr, | |
56 | .flags = I2C_M_RD, | |
57 | .len = len, | |
58 | .buf = buf, | |
59 | } | |
60 | }; | |
61 | ||
62 | if (i2c_transfer(client->adapter, msg, 2) != 2) { | |
63 | dev_err(&client->dev, "i2c transfer failed\n"); | |
64 | return -EIO; | |
65 | } | |
66 | ||
67 | return 0; | |
68 | } | |
69 | ||
e3559442 MV |
70 | static bool ili210x_touchdata_to_coords(struct ili210x *priv, u8 *touchdata, |
71 | unsigned int finger, | |
72 | unsigned int *x, unsigned int *y) | |
73 | { | |
74 | if (finger >= MAX_TOUCHES) | |
75 | return false; | |
76 | ||
77 | if (touchdata[0] & BIT(finger)) | |
78 | return false; | |
79 | ||
80 | *x = get_unaligned_be16(touchdata + 1 + (finger * 4) + 0); | |
81 | *y = get_unaligned_be16(touchdata + 1 + (finger * 4) + 2); | |
82 | ||
83 | return true; | |
84 | } | |
85 | ||
86 | static bool ili210x_report_events(struct ili210x *priv, u8 *touchdata) | |
5c6a7a62 | 87 | { |
e3559442 | 88 | struct input_dev *input = priv->input; |
5c6a7a62 OS |
89 | int i; |
90 | bool touch; | |
91 | unsigned int x, y; | |
5c6a7a62 OS |
92 | |
93 | for (i = 0; i < MAX_TOUCHES; i++) { | |
94 | input_mt_slot(input, i); | |
95 | ||
e3559442 | 96 | touch = ili210x_touchdata_to_coords(priv, touchdata, i, &x, &y); |
5c6a7a62 OS |
97 | input_mt_report_slot_state(input, MT_TOOL_FINGER, touch); |
98 | if (touch) { | |
5c6a7a62 OS |
99 | input_report_abs(input, ABS_MT_POSITION_X, x); |
100 | input_report_abs(input, ABS_MT_POSITION_Y, y); | |
101 | } | |
102 | } | |
103 | ||
104 | input_mt_report_pointer_emulation(input, false); | |
105 | input_sync(input); | |
e3559442 MV |
106 | |
107 | return touchdata[0] & 0xf3; | |
5c6a7a62 OS |
108 | } |
109 | ||
5c6a7a62 OS |
110 | static void ili210x_work(struct work_struct *work) |
111 | { | |
112 | struct ili210x *priv = container_of(work, struct ili210x, | |
113 | dwork.work); | |
114 | struct i2c_client *client = priv->client; | |
e3559442 MV |
115 | u8 touchdata[1 + 4 * MAX_TOUCHES]; |
116 | bool touch; | |
5c6a7a62 OS |
117 | int error; |
118 | ||
119 | error = ili210x_read_reg(client, REG_TOUCHDATA, | |
e3559442 | 120 | touchdata, sizeof(touchdata)); |
5c6a7a62 OS |
121 | if (error) { |
122 | dev_err(&client->dev, | |
123 | "Unable to get touchdata, err = %d\n", error); | |
124 | return; | |
125 | } | |
126 | ||
e3559442 | 127 | touch = ili210x_report_events(priv, touchdata); |
5c6a7a62 | 128 | |
e3559442 | 129 | if (touch) |
5c6a7a62 OS |
130 | schedule_delayed_work(&priv->dwork, |
131 | msecs_to_jiffies(priv->poll_period)); | |
132 | } | |
133 | ||
134 | static irqreturn_t ili210x_irq(int irq, void *irq_data) | |
135 | { | |
136 | struct ili210x *priv = irq_data; | |
137 | ||
138 | schedule_delayed_work(&priv->dwork, 0); | |
139 | ||
140 | return IRQ_HANDLED; | |
141 | } | |
142 | ||
143 | static ssize_t ili210x_calibrate(struct device *dev, | |
144 | struct device_attribute *attr, | |
145 | const char *buf, size_t count) | |
146 | { | |
147 | struct i2c_client *client = to_i2c_client(dev); | |
148 | struct ili210x *priv = i2c_get_clientdata(client); | |
149 | unsigned long calibrate; | |
150 | int rc; | |
151 | u8 cmd = REG_CALIBRATE; | |
152 | ||
153 | if (kstrtoul(buf, 10, &calibrate)) | |
154 | return -EINVAL; | |
155 | ||
156 | if (calibrate > 1) | |
157 | return -EINVAL; | |
158 | ||
159 | if (calibrate) { | |
160 | rc = i2c_master_send(priv->client, &cmd, sizeof(cmd)); | |
161 | if (rc != sizeof(cmd)) | |
162 | return -EIO; | |
163 | } | |
164 | ||
165 | return count; | |
166 | } | |
b27c0d0c | 167 | static DEVICE_ATTR(calibrate, S_IWUSR, NULL, ili210x_calibrate); |
5c6a7a62 OS |
168 | |
169 | static struct attribute *ili210x_attributes[] = { | |
170 | &dev_attr_calibrate.attr, | |
171 | NULL, | |
172 | }; | |
173 | ||
174 | static const struct attribute_group ili210x_attr_group = { | |
175 | .attrs = ili210x_attributes, | |
176 | }; | |
177 | ||
201f3c80 MV |
178 | static void ili210x_power_down(void *data) |
179 | { | |
180 | struct gpio_desc *reset_gpio = data; | |
181 | ||
182 | gpiod_set_value_cansleep(reset_gpio, 1); | |
183 | } | |
184 | ||
1bdec5d9 MV |
185 | static void ili210x_cancel_work(void *data) |
186 | { | |
187 | struct ili210x *priv = data; | |
188 | ||
189 | cancel_delayed_work_sync(&priv->dwork); | |
190 | } | |
191 | ||
5298cc4c | 192 | static int ili210x_i2c_probe(struct i2c_client *client, |
5c6a7a62 OS |
193 | const struct i2c_device_id *id) |
194 | { | |
195 | struct device *dev = &client->dev; | |
5c6a7a62 | 196 | struct ili210x *priv; |
201f3c80 | 197 | struct gpio_desc *reset_gpio; |
5c6a7a62 OS |
198 | struct input_dev *input; |
199 | struct panel_info panel; | |
200 | struct firmware_version firmware; | |
201 | int xmax, ymax; | |
202 | int error; | |
203 | ||
204 | dev_dbg(dev, "Probing for ILI210X I2C Touschreen driver"); | |
205 | ||
5c6a7a62 OS |
206 | if (client->irq <= 0) { |
207 | dev_err(dev, "No IRQ!\n"); | |
208 | return -EINVAL; | |
209 | } | |
210 | ||
201f3c80 MV |
211 | reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); |
212 | if (IS_ERR(reset_gpio)) | |
213 | return PTR_ERR(reset_gpio); | |
214 | ||
215 | if (reset_gpio) { | |
216 | error = devm_add_action_or_reset(dev, ili210x_power_down, | |
217 | reset_gpio); | |
218 | if (error) | |
219 | return error; | |
220 | ||
221 | usleep_range(50, 100); | |
222 | gpiod_set_value_cansleep(reset_gpio, 0); | |
223 | msleep(100); | |
224 | } | |
225 | ||
12294577 MV |
226 | priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); |
227 | if (!priv) | |
228 | return -ENOMEM; | |
229 | ||
230 | input = devm_input_allocate_device(dev); | |
231 | if (!input) | |
232 | return -ENOMEM; | |
233 | ||
234 | priv->client = client; | |
235 | priv->input = input; | |
236 | priv->poll_period = DEFAULT_POLL_PERIOD; | |
237 | INIT_DELAYED_WORK(&priv->dwork, ili210x_work); | |
238 | priv->reset_gpio = reset_gpio; | |
239 | ||
240 | i2c_set_clientdata(client, priv); | |
241 | ||
5c6a7a62 OS |
242 | /* Get firmware version */ |
243 | error = ili210x_read_reg(client, REG_FIRMWARE_VERSION, | |
244 | &firmware, sizeof(firmware)); | |
245 | if (error) { | |
246 | dev_err(dev, "Failed to get firmware version, err: %d\n", | |
247 | error); | |
248 | return error; | |
249 | } | |
250 | ||
251 | /* get panel info */ | |
252 | error = ili210x_read_reg(client, REG_PANEL_INFO, &panel, sizeof(panel)); | |
253 | if (error) { | |
971bd8fa | 254 | dev_err(dev, "Failed to get panel information, err: %d\n", |
5c6a7a62 OS |
255 | error); |
256 | return error; | |
257 | } | |
258 | ||
e3559442 MV |
259 | xmax = panel.x_low | (panel.x_high << 8); |
260 | ymax = panel.y_low | (panel.y_high << 8); | |
5c6a7a62 | 261 | |
5c6a7a62 OS |
262 | /* Setup input device */ |
263 | input->name = "ILI210x Touchscreen"; | |
264 | input->id.bustype = BUS_I2C; | |
265 | input->dev.parent = dev; | |
266 | ||
267 | __set_bit(EV_SYN, input->evbit); | |
268 | __set_bit(EV_KEY, input->evbit); | |
269 | __set_bit(EV_ABS, input->evbit); | |
270 | __set_bit(BTN_TOUCH, input->keybit); | |
271 | ||
272 | /* Single touch */ | |
273 | input_set_abs_params(input, ABS_X, 0, xmax, 0, 0); | |
274 | input_set_abs_params(input, ABS_Y, 0, ymax, 0, 0); | |
275 | ||
276 | /* Multi touch */ | |
b4adbbef | 277 | input_mt_init_slots(input, MAX_TOUCHES, 0); |
5c6a7a62 OS |
278 | input_set_abs_params(input, ABS_MT_POSITION_X, 0, xmax, 0, 0); |
279 | input_set_abs_params(input, ABS_MT_POSITION_Y, 0, ymax, 0, 0); | |
280 | ||
1bdec5d9 MV |
281 | error = devm_add_action(dev, ili210x_cancel_work, priv); |
282 | if (error) | |
283 | return error; | |
284 | ||
285 | error = devm_request_irq(dev, client->irq, ili210x_irq, 0, | |
286 | client->name, priv); | |
5c6a7a62 OS |
287 | if (error) { |
288 | dev_err(dev, "Unable to request touchscreen IRQ, err: %d\n", | |
289 | error); | |
1bdec5d9 | 290 | return error; |
5c6a7a62 OS |
291 | } |
292 | ||
293 | error = sysfs_create_group(&dev->kobj, &ili210x_attr_group); | |
294 | if (error) { | |
295 | dev_err(dev, "Unable to create sysfs attributes, err: %d\n", | |
296 | error); | |
1bdec5d9 | 297 | return error; |
5c6a7a62 OS |
298 | } |
299 | ||
300 | error = input_register_device(priv->input); | |
301 | if (error) { | |
971bd8fa | 302 | dev_err(dev, "Cannot register input device, err: %d\n", error); |
5c6a7a62 OS |
303 | goto err_remove_sysfs; |
304 | } | |
305 | ||
d7ddf154 | 306 | device_init_wakeup(dev, 1); |
5c6a7a62 OS |
307 | |
308 | dev_dbg(dev, | |
309 | "ILI210x initialized (IRQ: %d), firmware version %d.%d.%d", | |
310 | client->irq, firmware.id, firmware.major, firmware.minor); | |
311 | ||
312 | return 0; | |
313 | ||
314 | err_remove_sysfs: | |
315 | sysfs_remove_group(&dev->kobj, &ili210x_attr_group); | |
5c6a7a62 OS |
316 | return error; |
317 | } | |
318 | ||
e2619cf7 | 319 | static int ili210x_i2c_remove(struct i2c_client *client) |
5c6a7a62 | 320 | { |
5c6a7a62 | 321 | sysfs_remove_group(&client->dev.kobj, &ili210x_attr_group); |
5c6a7a62 OS |
322 | |
323 | return 0; | |
324 | } | |
325 | ||
02b6a58b | 326 | static int __maybe_unused ili210x_i2c_suspend(struct device *dev) |
5c6a7a62 OS |
327 | { |
328 | struct i2c_client *client = to_i2c_client(dev); | |
329 | ||
330 | if (device_may_wakeup(&client->dev)) | |
331 | enable_irq_wake(client->irq); | |
332 | ||
333 | return 0; | |
334 | } | |
335 | ||
02b6a58b | 336 | static int __maybe_unused ili210x_i2c_resume(struct device *dev) |
5c6a7a62 OS |
337 | { |
338 | struct i2c_client *client = to_i2c_client(dev); | |
339 | ||
340 | if (device_may_wakeup(&client->dev)) | |
341 | disable_irq_wake(client->irq); | |
342 | ||
343 | return 0; | |
344 | } | |
5c6a7a62 OS |
345 | |
346 | static SIMPLE_DEV_PM_OPS(ili210x_i2c_pm, | |
347 | ili210x_i2c_suspend, ili210x_i2c_resume); | |
348 | ||
349 | static const struct i2c_device_id ili210x_i2c_id[] = { | |
350 | { "ili210x", 0 }, | |
351 | { } | |
352 | }; | |
353 | MODULE_DEVICE_TABLE(i2c, ili210x_i2c_id); | |
354 | ||
355 | static struct i2c_driver ili210x_ts_driver = { | |
356 | .driver = { | |
357 | .name = "ili210x_i2c", | |
5c6a7a62 OS |
358 | .pm = &ili210x_i2c_pm, |
359 | }, | |
360 | .id_table = ili210x_i2c_id, | |
361 | .probe = ili210x_i2c_probe, | |
1cb0aa88 | 362 | .remove = ili210x_i2c_remove, |
5c6a7a62 OS |
363 | }; |
364 | ||
365 | module_i2c_driver(ili210x_ts_driver); | |
366 | ||
367 | MODULE_AUTHOR("Olivier Sobrie <olivier@sobrie.be>"); | |
368 | MODULE_DESCRIPTION("ILI210X I2C Touchscreen Driver"); | |
369 | MODULE_LICENSE("GPL"); |