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