1 | // SPDX-License-Identifier: GPL-2.0+ |
2 | /* |
3 | * Surface System Aggregator Module (SSAM) client device registry. |
4 | * |
5 | * Registry for non-platform/non-ACPI SSAM client devices, i.e. devices that |
6 | * cannot be auto-detected. Provides device-hubs and performs instantiation |
7 | * for these devices. |
8 | * |
9 | * Copyright (C) 2020-2022 Maximilian Luz <luzmaximilian@gmail.com> |
10 | */ |
11 | |
12 | #include <linux/acpi.h> |
13 | #include <linux/kernel.h> |
14 | #include <linux/module.h> |
15 | #include <linux/platform_device.h> |
16 | #include <linux/property.h> |
17 | #include <linux/types.h> |
18 | |
19 | #include <linux/surface_aggregator/device.h> |
20 | |
21 | |
22 | /* -- Device registry. ------------------------------------------------------ */ |
23 | |
24 | /* |
25 | * SSAM device names follow the SSAM module alias, meaning they are prefixed |
26 | * with 'ssam:', followed by domain, category, target ID, instance ID, and |
27 | * function, each encoded as two-digit hexadecimal, separated by ':'. In other |
28 | * words, it follows the scheme |
29 | * |
30 | * ssam:dd:cc:tt:ii:ff |
31 | * |
32 | * Where, 'dd', 'cc', 'tt', 'ii', and 'ff' are the two-digit hexadecimal |
33 | * values mentioned above, respectively. |
34 | */ |
35 | |
36 | /* Root node. */ |
37 | static const struct software_node ssam_node_root = { |
38 | .name = "ssam_platform_hub" , |
39 | }; |
40 | |
41 | /* KIP device hub (connects keyboard cover devices on Surface Pro 8). */ |
42 | static const struct software_node ssam_node_hub_kip = { |
43 | .name = "ssam:00:00:01:0e:00" , |
44 | .parent = &ssam_node_root, |
45 | }; |
46 | |
47 | /* Base device hub (devices attached to Surface Book 3 base). */ |
48 | static const struct software_node ssam_node_hub_base = { |
49 | .name = "ssam:00:00:01:11:00" , |
50 | .parent = &ssam_node_root, |
51 | }; |
52 | |
53 | /* AC adapter. */ |
54 | static const struct software_node ssam_node_bat_ac = { |
55 | .name = "ssam:01:02:01:01:01" , |
56 | .parent = &ssam_node_root, |
57 | }; |
58 | |
59 | /* Primary battery. */ |
60 | static const struct software_node ssam_node_bat_main = { |
61 | .name = "ssam:01:02:01:01:00" , |
62 | .parent = &ssam_node_root, |
63 | }; |
64 | |
65 | /* Secondary battery (Surface Book 3). */ |
66 | static const struct software_node ssam_node_bat_sb3base = { |
67 | .name = "ssam:01:02:02:01:00" , |
68 | .parent = &ssam_node_hub_base, |
69 | }; |
70 | |
71 | /* Platform profile / performance-mode device. */ |
72 | static const struct software_node ssam_node_tmp_pprof = { |
73 | .name = "ssam:01:03:01:00:01" , |
74 | .parent = &ssam_node_root, |
75 | }; |
76 | |
77 | /* Fan speed function. */ |
78 | static const struct software_node ssam_node_fan_speed = { |
79 | .name = "ssam:01:05:01:01:01" , |
80 | .parent = &ssam_node_root, |
81 | }; |
82 | |
83 | /* Tablet-mode switch via KIP subsystem. */ |
84 | static const struct software_node ssam_node_kip_tablet_switch = { |
85 | .name = "ssam:01:0e:01:00:01" , |
86 | .parent = &ssam_node_root, |
87 | }; |
88 | |
89 | /* DTX / detachment-system device (Surface Book 3). */ |
90 | static const struct software_node ssam_node_bas_dtx = { |
91 | .name = "ssam:01:11:01:00:00" , |
92 | .parent = &ssam_node_root, |
93 | }; |
94 | |
95 | /* HID keyboard (SAM, TID=1). */ |
96 | static const struct software_node ssam_node_hid_sam_keyboard = { |
97 | .name = "ssam:01:15:01:01:00" , |
98 | .parent = &ssam_node_root, |
99 | }; |
100 | |
101 | /* HID pen stash (SAM, TID=1; pen taken / stashed away evens). */ |
102 | static const struct software_node ssam_node_hid_sam_penstash = { |
103 | .name = "ssam:01:15:01:02:00" , |
104 | .parent = &ssam_node_root, |
105 | }; |
106 | |
107 | /* HID touchpad (SAM, TID=1). */ |
108 | static const struct software_node ssam_node_hid_sam_touchpad = { |
109 | .name = "ssam:01:15:01:03:00" , |
110 | .parent = &ssam_node_root, |
111 | }; |
112 | |
113 | /* HID device instance 6 (SAM, TID=1, HID sensor collection). */ |
114 | static const struct software_node ssam_node_hid_sam_sensors = { |
115 | .name = "ssam:01:15:01:06:00" , |
116 | .parent = &ssam_node_root, |
117 | }; |
118 | |
119 | /* HID device instance 7 (SAM, TID=1, UCM UCSI HID client). */ |
120 | static const struct software_node ssam_node_hid_sam_ucm_ucsi = { |
121 | .name = "ssam:01:15:01:07:00" , |
122 | .parent = &ssam_node_root, |
123 | }; |
124 | |
125 | /* HID system controls (SAM, TID=1). */ |
126 | static const struct software_node ssam_node_hid_sam_sysctrl = { |
127 | .name = "ssam:01:15:01:08:00" , |
128 | .parent = &ssam_node_root, |
129 | }; |
130 | |
131 | /* HID keyboard. */ |
132 | static const struct software_node ssam_node_hid_main_keyboard = { |
133 | .name = "ssam:01:15:02:01:00" , |
134 | .parent = &ssam_node_root, |
135 | }; |
136 | |
137 | /* HID touchpad. */ |
138 | static const struct software_node ssam_node_hid_main_touchpad = { |
139 | .name = "ssam:01:15:02:03:00" , |
140 | .parent = &ssam_node_root, |
141 | }; |
142 | |
143 | /* HID device instance 5 (unknown HID device). */ |
144 | static const struct software_node ssam_node_hid_main_iid5 = { |
145 | .name = "ssam:01:15:02:05:00" , |
146 | .parent = &ssam_node_root, |
147 | }; |
148 | |
149 | /* HID keyboard (base hub). */ |
150 | static const struct software_node ssam_node_hid_base_keyboard = { |
151 | .name = "ssam:01:15:02:01:00" , |
152 | .parent = &ssam_node_hub_base, |
153 | }; |
154 | |
155 | /* HID touchpad (base hub). */ |
156 | static const struct software_node ssam_node_hid_base_touchpad = { |
157 | .name = "ssam:01:15:02:03:00" , |
158 | .parent = &ssam_node_hub_base, |
159 | }; |
160 | |
161 | /* HID device instance 5 (unknown HID device, base hub). */ |
162 | static const struct software_node ssam_node_hid_base_iid5 = { |
163 | .name = "ssam:01:15:02:05:00" , |
164 | .parent = &ssam_node_hub_base, |
165 | }; |
166 | |
167 | /* HID device instance 6 (unknown HID device, base hub). */ |
168 | static const struct software_node ssam_node_hid_base_iid6 = { |
169 | .name = "ssam:01:15:02:06:00" , |
170 | .parent = &ssam_node_hub_base, |
171 | }; |
172 | |
173 | /* HID keyboard (KIP hub). */ |
174 | static const struct software_node ssam_node_hid_kip_keyboard = { |
175 | .name = "ssam:01:15:02:01:00" , |
176 | .parent = &ssam_node_hub_kip, |
177 | }; |
178 | |
179 | /* HID pen stash (KIP hub; pen taken / stashed away evens). */ |
180 | static const struct software_node ssam_node_hid_kip_penstash = { |
181 | .name = "ssam:01:15:02:02:00" , |
182 | .parent = &ssam_node_hub_kip, |
183 | }; |
184 | |
185 | /* HID touchpad (KIP hub). */ |
186 | static const struct software_node ssam_node_hid_kip_touchpad = { |
187 | .name = "ssam:01:15:02:03:00" , |
188 | .parent = &ssam_node_hub_kip, |
189 | }; |
190 | |
191 | /* HID device instance 5 (KIP hub, type-cover firmware update). */ |
192 | static const struct software_node ssam_node_hid_kip_fwupd = { |
193 | .name = "ssam:01:15:02:05:00" , |
194 | .parent = &ssam_node_hub_kip, |
195 | }; |
196 | |
197 | /* Tablet-mode switch via POS subsystem. */ |
198 | static const struct software_node ssam_node_pos_tablet_switch = { |
199 | .name = "ssam:01:26:01:00:01" , |
200 | .parent = &ssam_node_root, |
201 | }; |
202 | |
203 | /* |
204 | * Devices for 5th- and 6th-generations models: |
205 | * - Surface Book 2, |
206 | * - Surface Laptop 1 and 2, |
207 | * - Surface Pro 5 and 6. |
208 | */ |
209 | static const struct software_node *ssam_node_group_gen5[] = { |
210 | &ssam_node_root, |
211 | &ssam_node_tmp_pprof, |
212 | NULL, |
213 | }; |
214 | |
215 | /* Devices for Surface Book 3. */ |
216 | static const struct software_node *ssam_node_group_sb3[] = { |
217 | &ssam_node_root, |
218 | &ssam_node_hub_base, |
219 | &ssam_node_bat_ac, |
220 | &ssam_node_bat_main, |
221 | &ssam_node_bat_sb3base, |
222 | &ssam_node_tmp_pprof, |
223 | &ssam_node_bas_dtx, |
224 | &ssam_node_hid_base_keyboard, |
225 | &ssam_node_hid_base_touchpad, |
226 | &ssam_node_hid_base_iid5, |
227 | &ssam_node_hid_base_iid6, |
228 | NULL, |
229 | }; |
230 | |
231 | /* Devices for Surface Laptop 3 and 4. */ |
232 | static const struct software_node *ssam_node_group_sl3[] = { |
233 | &ssam_node_root, |
234 | &ssam_node_bat_ac, |
235 | &ssam_node_bat_main, |
236 | &ssam_node_tmp_pprof, |
237 | &ssam_node_hid_main_keyboard, |
238 | &ssam_node_hid_main_touchpad, |
239 | &ssam_node_hid_main_iid5, |
240 | NULL, |
241 | }; |
242 | |
243 | /* Devices for Surface Laptop 5. */ |
244 | static const struct software_node *ssam_node_group_sl5[] = { |
245 | &ssam_node_root, |
246 | &ssam_node_bat_ac, |
247 | &ssam_node_bat_main, |
248 | &ssam_node_tmp_pprof, |
249 | &ssam_node_hid_main_keyboard, |
250 | &ssam_node_hid_main_touchpad, |
251 | &ssam_node_hid_main_iid5, |
252 | &ssam_node_hid_sam_ucm_ucsi, |
253 | NULL, |
254 | }; |
255 | |
256 | /* Devices for Surface Laptop Studio. */ |
257 | static const struct software_node *ssam_node_group_sls[] = { |
258 | &ssam_node_root, |
259 | &ssam_node_bat_ac, |
260 | &ssam_node_bat_main, |
261 | &ssam_node_tmp_pprof, |
262 | &ssam_node_pos_tablet_switch, |
263 | &ssam_node_hid_sam_keyboard, |
264 | &ssam_node_hid_sam_penstash, |
265 | &ssam_node_hid_sam_touchpad, |
266 | &ssam_node_hid_sam_sensors, |
267 | &ssam_node_hid_sam_ucm_ucsi, |
268 | &ssam_node_hid_sam_sysctrl, |
269 | NULL, |
270 | }; |
271 | |
272 | /* Devices for Surface Laptop Go. */ |
273 | static const struct software_node *ssam_node_group_slg1[] = { |
274 | &ssam_node_root, |
275 | &ssam_node_bat_ac, |
276 | &ssam_node_bat_main, |
277 | &ssam_node_tmp_pprof, |
278 | NULL, |
279 | }; |
280 | |
281 | /* Devices for Surface Pro 7 and Surface Pro 7+. */ |
282 | static const struct software_node *ssam_node_group_sp7[] = { |
283 | &ssam_node_root, |
284 | &ssam_node_bat_ac, |
285 | &ssam_node_bat_main, |
286 | &ssam_node_tmp_pprof, |
287 | NULL, |
288 | }; |
289 | |
290 | /* Devices for Surface Pro 8 */ |
291 | static const struct software_node *ssam_node_group_sp8[] = { |
292 | &ssam_node_root, |
293 | &ssam_node_hub_kip, |
294 | &ssam_node_bat_ac, |
295 | &ssam_node_bat_main, |
296 | &ssam_node_tmp_pprof, |
297 | &ssam_node_kip_tablet_switch, |
298 | &ssam_node_hid_kip_keyboard, |
299 | &ssam_node_hid_kip_penstash, |
300 | &ssam_node_hid_kip_touchpad, |
301 | &ssam_node_hid_kip_fwupd, |
302 | &ssam_node_hid_sam_sensors, |
303 | &ssam_node_hid_sam_ucm_ucsi, |
304 | NULL, |
305 | }; |
306 | |
307 | /* Devices for Surface Pro 9 */ |
308 | static const struct software_node *ssam_node_group_sp9[] = { |
309 | &ssam_node_root, |
310 | &ssam_node_hub_kip, |
311 | &ssam_node_bat_ac, |
312 | &ssam_node_bat_main, |
313 | &ssam_node_tmp_pprof, |
314 | &ssam_node_fan_speed, |
315 | &ssam_node_pos_tablet_switch, |
316 | &ssam_node_hid_kip_keyboard, |
317 | &ssam_node_hid_kip_penstash, |
318 | &ssam_node_hid_kip_touchpad, |
319 | &ssam_node_hid_kip_fwupd, |
320 | &ssam_node_hid_sam_sensors, |
321 | &ssam_node_hid_sam_ucm_ucsi, |
322 | NULL, |
323 | }; |
324 | |
325 | |
326 | /* -- SSAM platform/meta-hub driver. ---------------------------------------- */ |
327 | |
328 | static const struct acpi_device_id ssam_platform_hub_match[] = { |
329 | /* Surface Pro 4, 5, and 6 (OMBR < 0x10) */ |
330 | { "MSHW0081" , (unsigned long)ssam_node_group_gen5 }, |
331 | |
332 | /* Surface Pro 6 (OMBR >= 0x10) */ |
333 | { "MSHW0111" , (unsigned long)ssam_node_group_gen5 }, |
334 | |
335 | /* Surface Pro 7 */ |
336 | { "MSHW0116" , (unsigned long)ssam_node_group_sp7 }, |
337 | |
338 | /* Surface Pro 7+ */ |
339 | { "MSHW0119" , (unsigned long)ssam_node_group_sp7 }, |
340 | |
341 | /* Surface Pro 8 */ |
342 | { "MSHW0263" , (unsigned long)ssam_node_group_sp8 }, |
343 | |
344 | /* Surface Pro 9 */ |
345 | { "MSHW0343" , (unsigned long)ssam_node_group_sp9 }, |
346 | |
347 | /* Surface Book 2 */ |
348 | { "MSHW0107" , (unsigned long)ssam_node_group_gen5 }, |
349 | |
350 | /* Surface Book 3 */ |
351 | { "MSHW0117" , (unsigned long)ssam_node_group_sb3 }, |
352 | |
353 | /* Surface Laptop 1 */ |
354 | { "MSHW0086" , (unsigned long)ssam_node_group_gen5 }, |
355 | |
356 | /* Surface Laptop 2 */ |
357 | { "MSHW0112" , (unsigned long)ssam_node_group_gen5 }, |
358 | |
359 | /* Surface Laptop 3 (13", Intel) */ |
360 | { "MSHW0114" , (unsigned long)ssam_node_group_sl3 }, |
361 | |
362 | /* Surface Laptop 3 (15", AMD) and 4 (15", AMD) */ |
363 | { "MSHW0110" , (unsigned long)ssam_node_group_sl3 }, |
364 | |
365 | /* Surface Laptop 4 (13", Intel) */ |
366 | { "MSHW0250" , (unsigned long)ssam_node_group_sl3 }, |
367 | |
368 | /* Surface Laptop 5 */ |
369 | { "MSHW0350" , (unsigned long)ssam_node_group_sl5 }, |
370 | |
371 | /* Surface Laptop Go 1 */ |
372 | { "MSHW0118" , (unsigned long)ssam_node_group_slg1 }, |
373 | |
374 | /* Surface Laptop Go 2 */ |
375 | { "MSHW0290" , (unsigned long)ssam_node_group_slg1 }, |
376 | |
377 | /* Surface Laptop Studio */ |
378 | { "MSHW0123" , (unsigned long)ssam_node_group_sls }, |
379 | |
380 | { }, |
381 | }; |
382 | MODULE_DEVICE_TABLE(acpi, ssam_platform_hub_match); |
383 | |
384 | static int ssam_platform_hub_probe(struct platform_device *pdev) |
385 | { |
386 | const struct software_node **nodes; |
387 | struct ssam_controller *ctrl; |
388 | struct fwnode_handle *root; |
389 | int status; |
390 | |
391 | nodes = (const struct software_node **)acpi_device_get_match_data(dev: &pdev->dev); |
392 | if (!nodes) |
393 | return -ENODEV; |
394 | |
395 | /* |
396 | * As we're adding the SSAM client devices as children under this device |
397 | * and not the SSAM controller, we need to add a device link to the |
398 | * controller to ensure that we remove all of our devices before the |
399 | * controller is removed. This also guarantees proper ordering for |
400 | * suspend/resume of the devices on this hub. |
401 | */ |
402 | ctrl = ssam_client_bind(client: &pdev->dev); |
403 | if (IS_ERR(ptr: ctrl)) |
404 | return PTR_ERR(ptr: ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ptr: ctrl); |
405 | |
406 | status = software_node_register_node_group(node_group: nodes); |
407 | if (status) |
408 | return status; |
409 | |
410 | root = software_node_fwnode(node: &ssam_node_root); |
411 | if (!root) { |
412 | software_node_unregister_node_group(node_group: nodes); |
413 | return -ENOENT; |
414 | } |
415 | |
416 | set_secondary_fwnode(dev: &pdev->dev, fwnode: root); |
417 | |
418 | status = __ssam_register_clients(parent: &pdev->dev, ctrl, node: root); |
419 | if (status) { |
420 | set_secondary_fwnode(dev: &pdev->dev, NULL); |
421 | software_node_unregister_node_group(node_group: nodes); |
422 | } |
423 | |
424 | platform_set_drvdata(pdev, data: nodes); |
425 | return status; |
426 | } |
427 | |
428 | static void ssam_platform_hub_remove(struct platform_device *pdev) |
429 | { |
430 | const struct software_node **nodes = platform_get_drvdata(pdev); |
431 | |
432 | ssam_remove_clients(dev: &pdev->dev); |
433 | set_secondary_fwnode(dev: &pdev->dev, NULL); |
434 | software_node_unregister_node_group(node_group: nodes); |
435 | } |
436 | |
437 | static struct platform_driver ssam_platform_hub_driver = { |
438 | .probe = ssam_platform_hub_probe, |
439 | .remove_new = ssam_platform_hub_remove, |
440 | .driver = { |
441 | .name = "surface_aggregator_platform_hub" , |
442 | .acpi_match_table = ssam_platform_hub_match, |
443 | .probe_type = PROBE_PREFER_ASYNCHRONOUS, |
444 | }, |
445 | }; |
446 | module_platform_driver(ssam_platform_hub_driver); |
447 | |
448 | MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>" ); |
449 | MODULE_DESCRIPTION("Device-registry for Surface System Aggregator Module" ); |
450 | MODULE_LICENSE("GPL" ); |
451 | |