]>
Commit | Line | Data |
---|---|---|
3eb66d9f LW |
1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* | |
3 | * Driver for Cypress CY8CTMA140 (TMA140) touchscreen | |
4 | * (C) 2020 Linus Walleij <[email protected]> | |
5 | * (C) 2007 Cypress | |
6 | * (C) 2007 Google, Inc. | |
7 | * | |
8 | * Inspired by the tma140_skomer.c driver in the Samsung GT-S7710 code | |
9 | * drop. The GT-S7710 is codenamed "Skomer", the code also indicates | |
10 | * that the same touchscreen was used in a product called "Lucas". | |
11 | * | |
12 | * The code drop for GT-S7710 also contains a firmware downloader and | |
13 | * 15 (!) versions of the firmware drop from Cypress. But here we assume | |
14 | * the firmware got downloaded to the touchscreen flash successfully and | |
15 | * just use it to read the fingers. The shipped vendor driver does the | |
16 | * same. | |
17 | */ | |
18 | ||
19 | #include <asm/unaligned.h> | |
20 | #include <linux/module.h> | |
21 | #include <linux/kernel.h> | |
22 | #include <linux/input.h> | |
23 | #include <linux/input/touchscreen.h> | |
24 | #include <linux/input/mt.h> | |
25 | #include <linux/slab.h> | |
26 | #include <linux/interrupt.h> | |
27 | #include <linux/io.h> | |
28 | #include <linux/i2c.h> | |
29 | #include <linux/regulator/consumer.h> | |
30 | #include <linux/delay.h> | |
31 | ||
32 | #define CY8CTMA140_NAME "cy8ctma140" | |
33 | ||
34 | #define CY8CTMA140_MAX_FINGERS 4 | |
35 | ||
36 | #define CY8CTMA140_GET_FINGERS 0x00 | |
37 | #define CY8CTMA140_GET_FW_INFO 0x19 | |
38 | ||
39 | /* This message also fits some bytes for touchkeys, if used */ | |
40 | #define CY8CTMA140_PACKET_SIZE 31 | |
41 | ||
42 | #define CY8CTMA140_INVALID_BUFFER_BIT 5 | |
43 | ||
44 | struct cy8ctma140 { | |
45 | struct input_dev *input; | |
46 | struct touchscreen_properties props; | |
47 | struct device *dev; | |
48 | struct i2c_client *client; | |
49 | struct regulator_bulk_data regulators[2]; | |
50 | u8 prev_fingers; | |
51 | u8 prev_f1id; | |
52 | u8 prev_f2id; | |
53 | }; | |
54 | ||
55 | static void cy8ctma140_report(struct cy8ctma140 *ts, u8 *data, int n_fingers) | |
56 | { | |
57 | static const u8 contact_offsets[] = { 0x03, 0x09, 0x10, 0x16 }; | |
58 | u8 *buf; | |
59 | u16 x, y; | |
60 | u8 w; | |
61 | u8 id; | |
62 | int slot; | |
63 | int i; | |
64 | ||
65 | for (i = 0; i < n_fingers; i++) { | |
66 | buf = &data[contact_offsets[i]]; | |
67 | ||
68 | /* | |
69 | * Odd contacts have contact ID in the lower nibble of | |
70 | * the preceding byte, whereas even contacts have it in | |
71 | * the upper nibble of the following byte. | |
72 | */ | |
73 | id = i % 2 ? buf[-1] & 0x0f : buf[5] >> 4; | |
74 | slot = input_mt_get_slot_by_key(ts->input, id); | |
75 | if (slot < 0) | |
76 | continue; | |
77 | ||
78 | x = get_unaligned_be16(buf); | |
79 | y = get_unaligned_be16(buf + 2); | |
80 | w = buf[4]; | |
81 | ||
82 | dev_dbg(ts->dev, "finger %d: ID %02x (%d, %d) w: %d\n", | |
83 | slot, id, x, y, w); | |
84 | ||
85 | input_mt_slot(ts->input, slot); | |
86 | input_mt_report_slot_state(ts->input, MT_TOOL_FINGER, true); | |
87 | touchscreen_report_pos(ts->input, &ts->props, x, y, true); | |
88 | input_report_abs(ts->input, ABS_MT_TOUCH_MAJOR, w); | |
89 | } | |
90 | ||
91 | input_mt_sync_frame(ts->input); | |
92 | input_sync(ts->input); | |
93 | } | |
94 | ||
95 | static irqreturn_t cy8ctma140_irq_thread(int irq, void *d) | |
96 | { | |
97 | struct cy8ctma140 *ts = d; | |
98 | u8 cmdbuf[] = { CY8CTMA140_GET_FINGERS }; | |
99 | u8 buf[CY8CTMA140_PACKET_SIZE]; | |
100 | struct i2c_msg msg[] = { | |
101 | { | |
102 | .addr = ts->client->addr, | |
103 | .flags = 0, | |
104 | .len = sizeof(cmdbuf), | |
105 | .buf = cmdbuf, | |
106 | }, { | |
107 | .addr = ts->client->addr, | |
108 | .flags = I2C_M_RD, | |
109 | .len = sizeof(buf), | |
110 | .buf = buf, | |
111 | }, | |
112 | }; | |
113 | u8 n_fingers; | |
114 | int ret; | |
115 | ||
116 | ret = i2c_transfer(ts->client->adapter, msg, ARRAY_SIZE(msg)); | |
117 | if (ret != ARRAY_SIZE(msg)) { | |
118 | if (ret < 0) | |
119 | dev_err(ts->dev, "error reading message: %d\n", ret); | |
120 | else | |
121 | dev_err(ts->dev, "wrong number of messages\n"); | |
122 | goto out; | |
123 | } | |
124 | ||
125 | if (buf[1] & BIT(CY8CTMA140_INVALID_BUFFER_BIT)) { | |
126 | dev_dbg(ts->dev, "invalid event\n"); | |
127 | goto out; | |
128 | } | |
129 | ||
130 | n_fingers = buf[2] & 0x0f; | |
131 | if (n_fingers > CY8CTMA140_MAX_FINGERS) { | |
132 | dev_err(ts->dev, "unexpected number of fingers: %d\n", | |
133 | n_fingers); | |
134 | goto out; | |
135 | } | |
136 | ||
137 | cy8ctma140_report(ts, buf, n_fingers); | |
138 | ||
139 | out: | |
140 | return IRQ_HANDLED; | |
141 | } | |
142 | ||
143 | static int cy8ctma140_init(struct cy8ctma140 *ts) | |
144 | { | |
145 | u8 addr[1]; | |
146 | u8 buf[5]; | |
147 | int ret; | |
148 | ||
149 | addr[0] = CY8CTMA140_GET_FW_INFO; | |
150 | ret = i2c_master_send(ts->client, addr, 1); | |
151 | if (ret < 0) { | |
152 | dev_err(ts->dev, "error sending FW info message\n"); | |
153 | return ret; | |
154 | } | |
155 | ret = i2c_master_recv(ts->client, buf, 5); | |
156 | if (ret < 0) { | |
157 | dev_err(ts->dev, "error receiving FW info message\n"); | |
158 | return ret; | |
159 | } | |
160 | if (ret != 5) { | |
161 | dev_err(ts->dev, "got only %d bytes\n", ret); | |
162 | return -EIO; | |
163 | } | |
164 | ||
165 | dev_dbg(ts->dev, "vendor %c%c, HW ID %.2d, FW ver %.4d\n", | |
166 | buf[0], buf[1], buf[3], buf[4]); | |
167 | ||
168 | return 0; | |
169 | } | |
170 | ||
171 | static int cy8ctma140_power_up(struct cy8ctma140 *ts) | |
172 | { | |
173 | int error; | |
174 | ||
175 | error = regulator_bulk_enable(ARRAY_SIZE(ts->regulators), | |
176 | ts->regulators); | |
177 | if (error) { | |
178 | dev_err(ts->dev, "failed to enable regulators\n"); | |
179 | return error; | |
180 | } | |
181 | ||
182 | msleep(250); | |
183 | ||
184 | return 0; | |
185 | } | |
186 | ||
187 | static void cy8ctma140_power_down(struct cy8ctma140 *ts) | |
188 | { | |
189 | regulator_bulk_disable(ARRAY_SIZE(ts->regulators), | |
190 | ts->regulators); | |
191 | } | |
192 | ||
193 | /* Called from the registered devm action */ | |
194 | static void cy8ctma140_power_off_action(void *d) | |
195 | { | |
196 | struct cy8ctma140 *ts = d; | |
197 | ||
198 | cy8ctma140_power_down(ts); | |
199 | } | |
200 | ||
4baa3011 | 201 | static int cy8ctma140_probe(struct i2c_client *client) |
3eb66d9f LW |
202 | { |
203 | struct cy8ctma140 *ts; | |
204 | struct input_dev *input; | |
205 | struct device *dev = &client->dev; | |
206 | int error; | |
207 | ||
208 | ts = devm_kzalloc(dev, sizeof(*ts), GFP_KERNEL); | |
209 | if (!ts) | |
210 | return -ENOMEM; | |
211 | ||
212 | input = devm_input_allocate_device(dev); | |
213 | if (!input) | |
214 | return -ENOMEM; | |
215 | ||
216 | ts->dev = dev; | |
217 | ts->client = client; | |
218 | ts->input = input; | |
219 | ||
220 | input_set_capability(input, EV_ABS, ABS_MT_POSITION_X); | |
221 | input_set_capability(input, EV_ABS, ABS_MT_POSITION_Y); | |
222 | /* One byte for width 0..255 so this is the limit */ | |
223 | input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0, 255, 0, 0); | |
224 | /* | |
225 | * This sets up event max/min capabilities and fuzz. | |
226 | * Some DT properties are compulsory so we do not need | |
227 | * to provide defaults for X/Y max or pressure max. | |
228 | * | |
229 | * We just initialize a very simple MT touchscreen here, | |
230 | * some devices use the capability of this touchscreen to | |
231 | * provide touchkeys, and in that case this needs to be | |
232 | * extended to handle touchkey input. | |
233 | * | |
234 | * The firmware takes care of finger tracking and dropping | |
235 | * invalid ranges. | |
236 | */ | |
237 | touchscreen_parse_properties(input, true, &ts->props); | |
238 | input_abs_set_fuzz(input, ABS_MT_POSITION_X, 0); | |
239 | input_abs_set_fuzz(input, ABS_MT_POSITION_Y, 0); | |
240 | ||
241 | error = input_mt_init_slots(input, CY8CTMA140_MAX_FINGERS, | |
242 | INPUT_MT_DIRECT | INPUT_MT_DROP_UNUSED); | |
243 | if (error) | |
244 | return error; | |
245 | ||
246 | input->name = CY8CTMA140_NAME; | |
247 | input->id.bustype = BUS_I2C; | |
248 | input_set_drvdata(input, ts); | |
249 | ||
250 | /* | |
251 | * VCPIN is the analog voltage supply | |
252 | * VDD is the digital voltage supply | |
253 | * since the voltage range of VDD overlaps that of VCPIN, | |
254 | * many designs to just supply both with a single voltage | |
255 | * source of ~3.3 V. | |
256 | */ | |
257 | ts->regulators[0].supply = "vcpin"; | |
258 | ts->regulators[1].supply = "vdd"; | |
259 | error = devm_regulator_bulk_get(dev, ARRAY_SIZE(ts->regulators), | |
260 | ts->regulators); | |
8d3b0460 KK |
261 | if (error) |
262 | return dev_err_probe(dev, error, "Failed to get regulators\n"); | |
3eb66d9f LW |
263 | |
264 | error = cy8ctma140_power_up(ts); | |
265 | if (error) | |
266 | return error; | |
267 | ||
268 | error = devm_add_action_or_reset(dev, cy8ctma140_power_off_action, ts); | |
269 | if (error) { | |
270 | dev_err(dev, "failed to install power off handler\n"); | |
271 | return error; | |
272 | } | |
273 | ||
274 | error = devm_request_threaded_irq(dev, client->irq, | |
275 | NULL, cy8ctma140_irq_thread, | |
276 | IRQF_ONESHOT, CY8CTMA140_NAME, ts); | |
277 | if (error) { | |
278 | dev_err(dev, "irq %d busy? error %d\n", client->irq, error); | |
279 | return error; | |
280 | } | |
281 | ||
282 | error = cy8ctma140_init(ts); | |
283 | if (error) | |
284 | return error; | |
285 | ||
286 | error = input_register_device(input); | |
287 | if (error) | |
288 | return error; | |
289 | ||
290 | i2c_set_clientdata(client, ts); | |
291 | ||
292 | return 0; | |
293 | } | |
294 | ||
02998590 | 295 | static int cy8ctma140_suspend(struct device *dev) |
3eb66d9f LW |
296 | { |
297 | struct i2c_client *client = to_i2c_client(dev); | |
298 | struct cy8ctma140 *ts = i2c_get_clientdata(client); | |
299 | ||
300 | if (!device_may_wakeup(&client->dev)) | |
301 | cy8ctma140_power_down(ts); | |
302 | ||
303 | return 0; | |
304 | } | |
305 | ||
02998590 | 306 | static int cy8ctma140_resume(struct device *dev) |
3eb66d9f LW |
307 | { |
308 | struct i2c_client *client = to_i2c_client(dev); | |
309 | struct cy8ctma140 *ts = i2c_get_clientdata(client); | |
310 | int error; | |
311 | ||
312 | if (!device_may_wakeup(&client->dev)) { | |
313 | error = cy8ctma140_power_up(ts); | |
314 | if (error) | |
315 | return error; | |
316 | } | |
317 | ||
318 | return 0; | |
319 | } | |
320 | ||
02998590 JC |
321 | static DEFINE_SIMPLE_DEV_PM_OPS(cy8ctma140_pm, |
322 | cy8ctma140_suspend, cy8ctma140_resume); | |
3eb66d9f LW |
323 | |
324 | static const struct i2c_device_id cy8ctma140_idtable[] = { | |
5852f2af | 325 | { CY8CTMA140_NAME }, |
3eb66d9f LW |
326 | { /* sentinel */ } |
327 | }; | |
328 | MODULE_DEVICE_TABLE(i2c, cy8ctma140_idtable); | |
329 | ||
330 | static const struct of_device_id cy8ctma140_of_match[] = { | |
331 | { .compatible = "cypress,cy8ctma140", }, | |
332 | { /* sentinel */ } | |
333 | }; | |
334 | MODULE_DEVICE_TABLE(of, cy8ctma140_of_match); | |
335 | ||
336 | static struct i2c_driver cy8ctma140_driver = { | |
337 | .driver = { | |
338 | .name = CY8CTMA140_NAME, | |
02998590 | 339 | .pm = pm_sleep_ptr(&cy8ctma140_pm), |
3eb66d9f LW |
340 | .of_match_table = cy8ctma140_of_match, |
341 | }, | |
342 | .id_table = cy8ctma140_idtable, | |
d8bde56d | 343 | .probe = cy8ctma140_probe, |
3eb66d9f LW |
344 | }; |
345 | module_i2c_driver(cy8ctma140_driver); | |
346 | ||
347 | MODULE_AUTHOR("Linus Walleij <[email protected]>"); | |
348 | MODULE_DESCRIPTION("CY8CTMA140 TouchScreen Driver"); | |
349 | MODULE_LICENSE("GPL v2"); |