1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Fujitsu B-series Lifebook PS/2 TouchScreen driver |
4 | * |
5 | * Copyright (c) 2005 Vojtech Pavlik <vojtech@suse.cz> |
6 | * Copyright (c) 2005 Kenan Esau <kenan.esau@conan.de> |
7 | * |
8 | * TouchScreen detection, absolute mode setting and packet layout is taken from |
9 | * Harald Hoyer's description of the device. |
10 | */ |
11 | |
12 | #include <linux/input.h> |
13 | #include <linux/serio.h> |
14 | #include <linux/libps2.h> |
15 | #include <linux/dmi.h> |
16 | #include <linux/slab.h> |
17 | #include <linux/types.h> |
18 | |
19 | #include "psmouse.h" |
20 | #include "lifebook.h" |
21 | |
22 | struct lifebook_data { |
23 | struct input_dev *dev2; /* Relative device */ |
24 | char phys[32]; |
25 | }; |
26 | |
27 | static bool lifebook_present; |
28 | |
29 | static const char *desired_serio_phys; |
30 | |
31 | static int lifebook_limit_serio3(const struct dmi_system_id *d) |
32 | { |
33 | desired_serio_phys = "isa0060/serio3" ; |
34 | return 1; |
35 | } |
36 | |
37 | static bool lifebook_use_6byte_proto; |
38 | |
39 | static int lifebook_set_6byte_proto(const struct dmi_system_id *d) |
40 | { |
41 | lifebook_use_6byte_proto = true; |
42 | return 1; |
43 | } |
44 | |
45 | static const struct dmi_system_id lifebook_dmi_table[] __initconst = { |
46 | { |
47 | /* FLORA-ie 55mi */ |
48 | .matches = { |
49 | DMI_MATCH(DMI_PRODUCT_NAME, "FLORA-ie 55mi" ), |
50 | }, |
51 | }, |
52 | { |
53 | /* LifeBook B */ |
54 | .matches = { |
55 | DMI_MATCH(DMI_PRODUCT_NAME, "Lifebook B Series" ), |
56 | }, |
57 | }, |
58 | { |
59 | /* LifeBook B */ |
60 | .matches = { |
61 | DMI_MATCH(DMI_PRODUCT_NAME, "LifeBook B Series" ), |
62 | }, |
63 | }, |
64 | { |
65 | /* Lifebook B */ |
66 | .matches = { |
67 | DMI_MATCH(DMI_PRODUCT_NAME, "LIFEBOOK B Series" ), |
68 | }, |
69 | }, |
70 | { |
71 | /* Lifebook B-2130 */ |
72 | .matches = { |
73 | DMI_MATCH(DMI_BOARD_NAME, "ZEPHYR" ), |
74 | }, |
75 | }, |
76 | { |
77 | /* Lifebook B213x/B2150 */ |
78 | .matches = { |
79 | DMI_MATCH(DMI_PRODUCT_NAME, "LifeBook B2131/B2133/B2150" ), |
80 | }, |
81 | }, |
82 | { |
83 | /* Zephyr */ |
84 | .matches = { |
85 | DMI_MATCH(DMI_PRODUCT_NAME, "ZEPHYR" ), |
86 | }, |
87 | }, |
88 | { |
89 | /* Panasonic CF-18 */ |
90 | .matches = { |
91 | DMI_MATCH(DMI_PRODUCT_NAME, "CF-18" ), |
92 | }, |
93 | .callback = lifebook_limit_serio3, |
94 | }, |
95 | { |
96 | /* Panasonic CF-28 */ |
97 | .matches = { |
98 | DMI_MATCH(DMI_SYS_VENDOR, "Matsushita" ), |
99 | DMI_MATCH(DMI_PRODUCT_NAME, "CF-28" ), |
100 | }, |
101 | .callback = lifebook_set_6byte_proto, |
102 | }, |
103 | { |
104 | /* Panasonic CF-29 */ |
105 | .matches = { |
106 | DMI_MATCH(DMI_SYS_VENDOR, "Matsushita" ), |
107 | DMI_MATCH(DMI_PRODUCT_NAME, "CF-29" ), |
108 | }, |
109 | .callback = lifebook_set_6byte_proto, |
110 | }, |
111 | { |
112 | /* Panasonic CF-72 */ |
113 | .matches = { |
114 | DMI_MATCH(DMI_PRODUCT_NAME, "CF-72" ), |
115 | }, |
116 | .callback = lifebook_set_6byte_proto, |
117 | }, |
118 | { |
119 | /* Lifebook B142 */ |
120 | .matches = { |
121 | DMI_MATCH(DMI_PRODUCT_NAME, "LifeBook B142" ), |
122 | }, |
123 | }, |
124 | { } |
125 | }; |
126 | |
127 | void __init lifebook_module_init(void) |
128 | { |
129 | lifebook_present = dmi_check_system(list: lifebook_dmi_table); |
130 | } |
131 | |
132 | static psmouse_ret_t lifebook_process_byte(struct psmouse *psmouse) |
133 | { |
134 | struct lifebook_data *priv = psmouse->private; |
135 | struct input_dev *dev1 = psmouse->dev; |
136 | struct input_dev *dev2 = priv ? priv->dev2 : NULL; |
137 | u8 *packet = psmouse->packet; |
138 | bool relative_packet = packet[0] & 0x08; |
139 | |
140 | if (relative_packet || !lifebook_use_6byte_proto) { |
141 | if (psmouse->pktcnt != 3) |
142 | return PSMOUSE_GOOD_DATA; |
143 | } else { |
144 | switch (psmouse->pktcnt) { |
145 | case 1: |
146 | return (packet[0] & 0xf8) == 0x00 ? |
147 | PSMOUSE_GOOD_DATA : PSMOUSE_BAD_DATA; |
148 | case 2: |
149 | return PSMOUSE_GOOD_DATA; |
150 | case 3: |
151 | return ((packet[2] & 0x30) << 2) == (packet[2] & 0xc0) ? |
152 | PSMOUSE_GOOD_DATA : PSMOUSE_BAD_DATA; |
153 | case 4: |
154 | return (packet[3] & 0xf8) == 0xc0 ? |
155 | PSMOUSE_GOOD_DATA : PSMOUSE_BAD_DATA; |
156 | case 5: |
157 | return (packet[4] & 0xc0) == (packet[2] & 0xc0) ? |
158 | PSMOUSE_GOOD_DATA : PSMOUSE_BAD_DATA; |
159 | case 6: |
160 | if (((packet[5] & 0x30) << 2) != (packet[5] & 0xc0)) |
161 | return PSMOUSE_BAD_DATA; |
162 | if ((packet[5] & 0xc0) != (packet[1] & 0xc0)) |
163 | return PSMOUSE_BAD_DATA; |
164 | break; /* report data */ |
165 | } |
166 | } |
167 | |
168 | if (relative_packet) { |
169 | if (!dev2) |
170 | psmouse_warn(psmouse, |
171 | "got relative packet but no relative device set up\n" ); |
172 | } else { |
173 | if (lifebook_use_6byte_proto) { |
174 | input_report_abs(dev: dev1, ABS_X, |
175 | value: ((packet[1] & 0x3f) << 6) | (packet[2] & 0x3f)); |
176 | input_report_abs(dev: dev1, ABS_Y, |
177 | value: 4096 - (((packet[4] & 0x3f) << 6) | (packet[5] & 0x3f))); |
178 | } else { |
179 | input_report_abs(dev: dev1, ABS_X, |
180 | value: (packet[1] | ((packet[0] & 0x30) << 4))); |
181 | input_report_abs(dev: dev1, ABS_Y, |
182 | value: 1024 - (packet[2] | ((packet[0] & 0xC0) << 2))); |
183 | } |
184 | input_report_key(dev: dev1, BTN_TOUCH, value: packet[0] & 0x04); |
185 | input_sync(dev: dev1); |
186 | } |
187 | |
188 | if (dev2) { |
189 | if (relative_packet) |
190 | psmouse_report_standard_motion(dev2, packet); |
191 | |
192 | psmouse_report_standard_buttons(dev2, buttons: packet[0]); |
193 | input_sync(dev: dev2); |
194 | } |
195 | |
196 | return PSMOUSE_FULL_PACKET; |
197 | } |
198 | |
199 | static int lifebook_absolute_mode(struct psmouse *psmouse) |
200 | { |
201 | struct ps2dev *ps2dev = &psmouse->ps2dev; |
202 | u8 param; |
203 | int error; |
204 | |
205 | error = psmouse_reset(psmouse); |
206 | if (error) |
207 | return error; |
208 | |
209 | /* |
210 | * Enable absolute output -- ps2_command fails always but if |
211 | * you leave this call out the touchscreen will never send |
212 | * absolute coordinates |
213 | */ |
214 | param = lifebook_use_6byte_proto ? 0x08 : 0x07; |
215 | ps2_command(ps2dev, param: ¶m, PSMOUSE_CMD_SETRES); |
216 | |
217 | return 0; |
218 | } |
219 | |
220 | static void lifebook_relative_mode(struct psmouse *psmouse) |
221 | { |
222 | struct ps2dev *ps2dev = &psmouse->ps2dev; |
223 | u8 param = 0x06; |
224 | |
225 | ps2_command(ps2dev, param: ¶m, PSMOUSE_CMD_SETRES); |
226 | } |
227 | |
228 | static void lifebook_set_resolution(struct psmouse *psmouse, unsigned int resolution) |
229 | { |
230 | static const u8 params[] = { 0, 1, 2, 2, 3 }; |
231 | u8 p; |
232 | |
233 | if (resolution == 0 || resolution > 400) |
234 | resolution = 400; |
235 | |
236 | p = params[resolution / 100]; |
237 | ps2_command(ps2dev: &psmouse->ps2dev, param: &p, PSMOUSE_CMD_SETRES); |
238 | psmouse->resolution = 50 << p; |
239 | } |
240 | |
241 | static void lifebook_disconnect(struct psmouse *psmouse) |
242 | { |
243 | struct lifebook_data *priv = psmouse->private; |
244 | |
245 | psmouse_reset(psmouse); |
246 | if (priv) { |
247 | input_unregister_device(priv->dev2); |
248 | kfree(objp: priv); |
249 | } |
250 | psmouse->private = NULL; |
251 | } |
252 | |
253 | int lifebook_detect(struct psmouse *psmouse, bool set_properties) |
254 | { |
255 | if (!lifebook_present) |
256 | return -ENXIO; |
257 | |
258 | if (desired_serio_phys && |
259 | strcmp(psmouse->ps2dev.serio->phys, desired_serio_phys)) |
260 | return -ENXIO; |
261 | |
262 | if (set_properties) { |
263 | psmouse->vendor = "Fujitsu" ; |
264 | psmouse->name = "Lifebook TouchScreen" ; |
265 | } |
266 | |
267 | return 0; |
268 | } |
269 | |
270 | static int lifebook_create_relative_device(struct psmouse *psmouse) |
271 | { |
272 | struct input_dev *dev2; |
273 | struct lifebook_data *priv; |
274 | int error = -ENOMEM; |
275 | |
276 | priv = kzalloc(size: sizeof(struct lifebook_data), GFP_KERNEL); |
277 | dev2 = input_allocate_device(); |
278 | if (!priv || !dev2) |
279 | goto err_out; |
280 | |
281 | priv->dev2 = dev2; |
282 | snprintf(buf: priv->phys, size: sizeof(priv->phys), |
283 | fmt: "%s/input1" , psmouse->ps2dev.serio->phys); |
284 | |
285 | dev2->phys = priv->phys; |
286 | dev2->name = "LBPS/2 Fujitsu Lifebook Touchpad" ; |
287 | dev2->id.bustype = BUS_I8042; |
288 | dev2->id.vendor = 0x0002; |
289 | dev2->id.product = PSMOUSE_LIFEBOOK; |
290 | dev2->id.version = 0x0000; |
291 | dev2->dev.parent = &psmouse->ps2dev.serio->dev; |
292 | |
293 | input_set_capability(dev: dev2, EV_REL, REL_X); |
294 | input_set_capability(dev: dev2, EV_REL, REL_Y); |
295 | input_set_capability(dev: dev2, EV_KEY, BTN_LEFT); |
296 | input_set_capability(dev: dev2, EV_KEY, BTN_RIGHT); |
297 | |
298 | error = input_register_device(priv->dev2); |
299 | if (error) |
300 | goto err_out; |
301 | |
302 | psmouse->private = priv; |
303 | return 0; |
304 | |
305 | err_out: |
306 | input_free_device(dev: dev2); |
307 | kfree(objp: priv); |
308 | return error; |
309 | } |
310 | |
311 | int lifebook_init(struct psmouse *psmouse) |
312 | { |
313 | struct input_dev *dev1 = psmouse->dev; |
314 | int max_coord = lifebook_use_6byte_proto ? 4096 : 1024; |
315 | int error; |
316 | |
317 | error = lifebook_absolute_mode(psmouse); |
318 | if (error) |
319 | return error; |
320 | |
321 | /* Clear default capabilities */ |
322 | bitmap_zero(dst: dev1->evbit, EV_CNT); |
323 | bitmap_zero(dst: dev1->relbit, REL_CNT); |
324 | bitmap_zero(dst: dev1->keybit, KEY_CNT); |
325 | |
326 | input_set_capability(dev: dev1, EV_KEY, BTN_TOUCH); |
327 | input_set_abs_params(dev: dev1, ABS_X, min: 0, max: max_coord, fuzz: 0, flat: 0); |
328 | input_set_abs_params(dev: dev1, ABS_Y, min: 0, max: max_coord, fuzz: 0, flat: 0); |
329 | |
330 | if (!desired_serio_phys) { |
331 | error = lifebook_create_relative_device(psmouse); |
332 | if (error) { |
333 | lifebook_relative_mode(psmouse); |
334 | return error; |
335 | } |
336 | } |
337 | |
338 | psmouse->protocol_handler = lifebook_process_byte; |
339 | psmouse->set_resolution = lifebook_set_resolution; |
340 | psmouse->disconnect = lifebook_disconnect; |
341 | psmouse->reconnect = lifebook_absolute_mode; |
342 | |
343 | psmouse->model = lifebook_use_6byte_proto ? 6 : 3; |
344 | |
345 | /* |
346 | * Use packet size = 3 even when using 6-byte protocol because |
347 | * that's what POLL will return on Lifebooks (according to spec). |
348 | */ |
349 | psmouse->pktsize = 3; |
350 | |
351 | return 0; |
352 | } |
353 | |
354 | |