1
2
3
4
5
6
7
8
9
10
11
12#include <linux/acpi.h>
13#include <linux/kernel.h>
14#include <linux/limits.h>
15#include <linux/module.h>
16#include <linux/platform_device.h>
17#include <linux/property.h>
18#include <linux/types.h>
19#include <linux/workqueue.h>
20
21#include <linux/surface_aggregator/controller.h>
22#include <linux/surface_aggregator/device.h>
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40static const struct software_node ssam_node_root = {
41 .name = "ssam_platform_hub",
42};
43
44
45static const struct software_node ssam_node_hub_base = {
46 .name = "ssam:00:00:02:00:00",
47 .parent = &ssam_node_root,
48};
49
50
51static const struct software_node ssam_node_bat_ac = {
52 .name = "ssam:01:02:01:01:01",
53 .parent = &ssam_node_root,
54};
55
56
57static const struct software_node ssam_node_bat_main = {
58 .name = "ssam:01:02:01:01:00",
59 .parent = &ssam_node_root,
60};
61
62
63static const struct software_node ssam_node_bat_sb3base = {
64 .name = "ssam:01:02:02:01:00",
65 .parent = &ssam_node_hub_base,
66};
67
68
69static const struct software_node ssam_node_tmp_pprof = {
70 .name = "ssam:01:03:01:00:01",
71 .parent = &ssam_node_root,
72};
73
74
75static const struct software_node ssam_node_bas_dtx = {
76 .name = "ssam:01:11:01:00:00",
77 .parent = &ssam_node_root,
78};
79
80
81static const struct software_node ssam_node_hid_main_keyboard = {
82 .name = "ssam:01:15:02:01:00",
83 .parent = &ssam_node_root,
84};
85
86
87static const struct software_node ssam_node_hid_main_touchpad = {
88 .name = "ssam:01:15:02:03:00",
89 .parent = &ssam_node_root,
90};
91
92
93static const struct software_node ssam_node_hid_main_iid5 = {
94 .name = "ssam:01:15:02:05:00",
95 .parent = &ssam_node_root,
96};
97
98
99static const struct software_node ssam_node_hid_base_keyboard = {
100 .name = "ssam:01:15:02:01:00",
101 .parent = &ssam_node_hub_base,
102};
103
104
105static const struct software_node ssam_node_hid_base_touchpad = {
106 .name = "ssam:01:15:02:03:00",
107 .parent = &ssam_node_hub_base,
108};
109
110
111static const struct software_node ssam_node_hid_base_iid5 = {
112 .name = "ssam:01:15:02:05:00",
113 .parent = &ssam_node_hub_base,
114};
115
116
117static const struct software_node ssam_node_hid_base_iid6 = {
118 .name = "ssam:01:15:02:06:00",
119 .parent = &ssam_node_hub_base,
120};
121
122
123static const struct software_node *ssam_node_group_sb2[] = {
124 &ssam_node_root,
125 &ssam_node_tmp_pprof,
126 NULL,
127};
128
129
130static const struct software_node *ssam_node_group_sb3[] = {
131 &ssam_node_root,
132 &ssam_node_hub_base,
133 &ssam_node_bat_ac,
134 &ssam_node_bat_main,
135 &ssam_node_bat_sb3base,
136 &ssam_node_tmp_pprof,
137 &ssam_node_bas_dtx,
138 &ssam_node_hid_base_keyboard,
139 &ssam_node_hid_base_touchpad,
140 &ssam_node_hid_base_iid5,
141 &ssam_node_hid_base_iid6,
142 NULL,
143};
144
145
146static const struct software_node *ssam_node_group_sl1[] = {
147 &ssam_node_root,
148 &ssam_node_tmp_pprof,
149 NULL,
150};
151
152
153static const struct software_node *ssam_node_group_sl2[] = {
154 &ssam_node_root,
155 &ssam_node_tmp_pprof,
156 NULL,
157};
158
159
160static const struct software_node *ssam_node_group_sl3[] = {
161 &ssam_node_root,
162 &ssam_node_bat_ac,
163 &ssam_node_bat_main,
164 &ssam_node_tmp_pprof,
165 &ssam_node_hid_main_keyboard,
166 &ssam_node_hid_main_touchpad,
167 &ssam_node_hid_main_iid5,
168 NULL,
169};
170
171
172static const struct software_node *ssam_node_group_slg1[] = {
173 &ssam_node_root,
174 &ssam_node_bat_ac,
175 &ssam_node_bat_main,
176 &ssam_node_tmp_pprof,
177 NULL,
178};
179
180
181static const struct software_node *ssam_node_group_sp5[] = {
182 &ssam_node_root,
183 &ssam_node_tmp_pprof,
184 NULL,
185};
186
187
188static const struct software_node *ssam_node_group_sp6[] = {
189 &ssam_node_root,
190 &ssam_node_tmp_pprof,
191 NULL,
192};
193
194
195static const struct software_node *ssam_node_group_sp7[] = {
196 &ssam_node_root,
197 &ssam_node_bat_ac,
198 &ssam_node_bat_main,
199 &ssam_node_tmp_pprof,
200 NULL,
201};
202
203
204
205
206static int ssam_uid_from_string(const char *str, struct ssam_device_uid *uid)
207{
208 u8 d, tc, tid, iid, fn;
209 int n;
210
211 n = sscanf(str, "ssam:%hhx:%hhx:%hhx:%hhx:%hhx", &d, &tc, &tid, &iid, &fn);
212 if (n != 5)
213 return -EINVAL;
214
215 uid->domain = d;
216 uid->category = tc;
217 uid->target = tid;
218 uid->instance = iid;
219 uid->function = fn;
220
221 return 0;
222}
223
224static int ssam_hub_remove_devices_fn(struct device *dev, void *data)
225{
226 if (!is_ssam_device(dev))
227 return 0;
228
229 ssam_device_remove(to_ssam_device(dev));
230 return 0;
231}
232
233static void ssam_hub_remove_devices(struct device *parent)
234{
235 device_for_each_child_reverse(parent, NULL, ssam_hub_remove_devices_fn);
236}
237
238static int ssam_hub_add_device(struct device *parent, struct ssam_controller *ctrl,
239 struct fwnode_handle *node)
240{
241 struct ssam_device_uid uid;
242 struct ssam_device *sdev;
243 int status;
244
245 status = ssam_uid_from_string(fwnode_get_name(node), &uid);
246 if (status)
247 return status;
248
249 sdev = ssam_device_alloc(ctrl, uid);
250 if (!sdev)
251 return -ENOMEM;
252
253 sdev->dev.parent = parent;
254 sdev->dev.fwnode = node;
255
256 status = ssam_device_add(sdev);
257 if (status)
258 ssam_device_put(sdev);
259
260 return status;
261}
262
263static int ssam_hub_add_devices(struct device *parent, struct ssam_controller *ctrl,
264 struct fwnode_handle *node)
265{
266 struct fwnode_handle *child;
267 int status;
268
269 fwnode_for_each_child_node(node, child) {
270
271
272
273
274
275
276 status = ssam_hub_add_device(parent, ctrl, child);
277 if (status && status != -EINVAL)
278 goto err;
279 }
280
281 return 0;
282err:
283 ssam_hub_remove_devices(parent);
284 return status;
285}
286
287
288
289
290
291
292
293
294
295#define SSAM_BASE_UPDATE_CONNECT_DELAY msecs_to_jiffies(2500)
296
297enum ssam_base_hub_state {
298 SSAM_BASE_HUB_UNINITIALIZED,
299 SSAM_BASE_HUB_CONNECTED,
300 SSAM_BASE_HUB_DISCONNECTED,
301};
302
303struct ssam_base_hub {
304 struct ssam_device *sdev;
305
306 enum ssam_base_hub_state state;
307 struct delayed_work update_work;
308
309 struct ssam_event_notifier notif;
310};
311
312SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_query_opmode, u8, {
313 .target_category = SSAM_SSH_TC_BAS,
314 .target_id = 0x01,
315 .command_id = 0x0d,
316 .instance_id = 0x00,
317});
318
319#define SSAM_BAS_OPMODE_TABLET 0x00
320#define SSAM_EVENT_BAS_CID_CONNECTION 0x0c
321
322static int ssam_base_hub_query_state(struct ssam_base_hub *hub, enum ssam_base_hub_state *state)
323{
324 u8 opmode;
325 int status;
326
327 status = ssam_retry(ssam_bas_query_opmode, hub->sdev->ctrl, &opmode);
328 if (status < 0) {
329 dev_err(&hub->sdev->dev, "failed to query base state: %d\n", status);
330 return status;
331 }
332
333 if (opmode != SSAM_BAS_OPMODE_TABLET)
334 *state = SSAM_BASE_HUB_CONNECTED;
335 else
336 *state = SSAM_BASE_HUB_DISCONNECTED;
337
338 return 0;
339}
340
341static ssize_t ssam_base_hub_state_show(struct device *dev, struct device_attribute *attr,
342 char *buf)
343{
344 struct ssam_base_hub *hub = dev_get_drvdata(dev);
345 bool connected = hub->state == SSAM_BASE_HUB_CONNECTED;
346
347 return sysfs_emit(buf, "%d\n", connected);
348}
349
350static struct device_attribute ssam_base_hub_attr_state =
351 __ATTR(state, 0444, ssam_base_hub_state_show, NULL);
352
353static struct attribute *ssam_base_hub_attrs[] = {
354 &ssam_base_hub_attr_state.attr,
355 NULL,
356};
357
358static const struct attribute_group ssam_base_hub_group = {
359 .attrs = ssam_base_hub_attrs,
360};
361
362static void ssam_base_hub_update_workfn(struct work_struct *work)
363{
364 struct ssam_base_hub *hub = container_of(work, struct ssam_base_hub, update_work.work);
365 struct fwnode_handle *node = dev_fwnode(&hub->sdev->dev);
366 enum ssam_base_hub_state state;
367 int status = 0;
368
369 status = ssam_base_hub_query_state(hub, &state);
370 if (status)
371 return;
372
373 if (hub->state == state)
374 return;
375 hub->state = state;
376
377 if (hub->state == SSAM_BASE_HUB_CONNECTED)
378 status = ssam_hub_add_devices(&hub->sdev->dev, hub->sdev->ctrl, node);
379 else
380 ssam_hub_remove_devices(&hub->sdev->dev);
381
382 if (status)
383 dev_err(&hub->sdev->dev, "failed to update base-hub devices: %d\n", status);
384}
385
386static u32 ssam_base_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event)
387{
388 struct ssam_base_hub *hub = container_of(nf, struct ssam_base_hub, notif);
389 unsigned long delay;
390
391 if (event->command_id != SSAM_EVENT_BAS_CID_CONNECTION)
392 return 0;
393
394 if (event->length < 1) {
395 dev_err(&hub->sdev->dev, "unexpected payload size: %u\n", event->length);
396 return 0;
397 }
398
399
400
401
402
403 delay = event->data[0] ? SSAM_BASE_UPDATE_CONNECT_DELAY : 0;
404
405 schedule_delayed_work(&hub->update_work, delay);
406
407
408
409
410
411
412 return 0;
413}
414
415static int __maybe_unused ssam_base_hub_resume(struct device *dev)
416{
417 struct ssam_base_hub *hub = dev_get_drvdata(dev);
418
419 schedule_delayed_work(&hub->update_work, 0);
420 return 0;
421}
422static SIMPLE_DEV_PM_OPS(ssam_base_hub_pm_ops, NULL, ssam_base_hub_resume);
423
424static int ssam_base_hub_probe(struct ssam_device *sdev)
425{
426 struct ssam_base_hub *hub;
427 int status;
428
429 hub = devm_kzalloc(&sdev->dev, sizeof(*hub), GFP_KERNEL);
430 if (!hub)
431 return -ENOMEM;
432
433 hub->sdev = sdev;
434 hub->state = SSAM_BASE_HUB_UNINITIALIZED;
435
436 hub->notif.base.priority = INT_MAX;
437 hub->notif.base.fn = ssam_base_hub_notif;
438 hub->notif.event.reg = SSAM_EVENT_REGISTRY_SAM;
439 hub->notif.event.id.target_category = SSAM_SSH_TC_BAS,
440 hub->notif.event.id.instance = 0,
441 hub->notif.event.mask = SSAM_EVENT_MASK_NONE;
442 hub->notif.event.flags = SSAM_EVENT_SEQUENCED;
443
444 INIT_DELAYED_WORK(&hub->update_work, ssam_base_hub_update_workfn);
445
446 ssam_device_set_drvdata(sdev, hub);
447
448 status = ssam_notifier_register(sdev->ctrl, &hub->notif);
449 if (status)
450 return status;
451
452 status = sysfs_create_group(&sdev->dev.kobj, &ssam_base_hub_group);
453 if (status)
454 goto err;
455
456 schedule_delayed_work(&hub->update_work, 0);
457 return 0;
458
459err:
460 ssam_notifier_unregister(sdev->ctrl, &hub->notif);
461 cancel_delayed_work_sync(&hub->update_work);
462 ssam_hub_remove_devices(&sdev->dev);
463 return status;
464}
465
466static void ssam_base_hub_remove(struct ssam_device *sdev)
467{
468 struct ssam_base_hub *hub = ssam_device_get_drvdata(sdev);
469
470 sysfs_remove_group(&sdev->dev.kobj, &ssam_base_hub_group);
471
472 ssam_notifier_unregister(sdev->ctrl, &hub->notif);
473 cancel_delayed_work_sync(&hub->update_work);
474 ssam_hub_remove_devices(&sdev->dev);
475}
476
477static const struct ssam_device_id ssam_base_hub_match[] = {
478 { SSAM_VDEV(HUB, 0x02, SSAM_ANY_IID, 0x00) },
479 { },
480};
481
482static struct ssam_device_driver ssam_base_hub_driver = {
483 .probe = ssam_base_hub_probe,
484 .remove = ssam_base_hub_remove,
485 .match_table = ssam_base_hub_match,
486 .driver = {
487 .name = "surface_aggregator_base_hub",
488 .probe_type = PROBE_PREFER_ASYNCHRONOUS,
489 .pm = &ssam_base_hub_pm_ops,
490 },
491};
492
493
494
495
496static const struct acpi_device_id ssam_platform_hub_match[] = {
497
498 { "MSHW0081", (unsigned long)ssam_node_group_sp5 },
499
500
501 { "MSHW0111", (unsigned long)ssam_node_group_sp6 },
502
503
504 { "MSHW0116", (unsigned long)ssam_node_group_sp7 },
505
506
507 { "MSHW0119", (unsigned long)ssam_node_group_sp7 },
508
509
510 { "MSHW0107", (unsigned long)ssam_node_group_sb2 },
511
512
513 { "MSHW0117", (unsigned long)ssam_node_group_sb3 },
514
515
516 { "MSHW0086", (unsigned long)ssam_node_group_sl1 },
517
518
519 { "MSHW0112", (unsigned long)ssam_node_group_sl2 },
520
521
522 { "MSHW0114", (unsigned long)ssam_node_group_sl3 },
523
524
525 { "MSHW0110", (unsigned long)ssam_node_group_sl3 },
526
527
528 { "MSHW0250", (unsigned long)ssam_node_group_sl3 },
529
530
531 { "MSHW0118", (unsigned long)ssam_node_group_slg1 },
532
533 { },
534};
535MODULE_DEVICE_TABLE(acpi, ssam_platform_hub_match);
536
537static int ssam_platform_hub_probe(struct platform_device *pdev)
538{
539 const struct software_node **nodes;
540 struct ssam_controller *ctrl;
541 struct fwnode_handle *root;
542 int status;
543
544 nodes = (const struct software_node **)acpi_device_get_match_data(&pdev->dev);
545 if (!nodes)
546 return -ENODEV;
547
548
549
550
551
552
553
554
555 ctrl = ssam_client_bind(&pdev->dev);
556 if (IS_ERR(ctrl))
557 return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl);
558
559 status = software_node_register_node_group(nodes);
560 if (status)
561 return status;
562
563 root = software_node_fwnode(&ssam_node_root);
564 if (!root) {
565 software_node_unregister_node_group(nodes);
566 return -ENOENT;
567 }
568
569 set_secondary_fwnode(&pdev->dev, root);
570
571 status = ssam_hub_add_devices(&pdev->dev, ctrl, root);
572 if (status) {
573 set_secondary_fwnode(&pdev->dev, NULL);
574 software_node_unregister_node_group(nodes);
575 }
576
577 platform_set_drvdata(pdev, nodes);
578 return status;
579}
580
581static int ssam_platform_hub_remove(struct platform_device *pdev)
582{
583 const struct software_node **nodes = platform_get_drvdata(pdev);
584
585 ssam_hub_remove_devices(&pdev->dev);
586 set_secondary_fwnode(&pdev->dev, NULL);
587 software_node_unregister_node_group(nodes);
588 return 0;
589}
590
591static struct platform_driver ssam_platform_hub_driver = {
592 .probe = ssam_platform_hub_probe,
593 .remove = ssam_platform_hub_remove,
594 .driver = {
595 .name = "surface_aggregator_platform_hub",
596 .acpi_match_table = ssam_platform_hub_match,
597 .probe_type = PROBE_PREFER_ASYNCHRONOUS,
598 },
599};
600
601
602
603
604static int __init ssam_device_hub_init(void)
605{
606 int status;
607
608 status = platform_driver_register(&ssam_platform_hub_driver);
609 if (status)
610 return status;
611
612 status = ssam_device_driver_register(&ssam_base_hub_driver);
613 if (status)
614 platform_driver_unregister(&ssam_platform_hub_driver);
615
616 return status;
617}
618module_init(ssam_device_hub_init);
619
620static void __exit ssam_device_hub_exit(void)
621{
622 ssam_device_driver_unregister(&ssam_base_hub_driver);
623 platform_driver_unregister(&ssam_platform_hub_driver);
624}
625module_exit(ssam_device_hub_exit);
626
627MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
628MODULE_DESCRIPTION("Device-registry for Surface System Aggregator Module");
629MODULE_LICENSE("GPL");
630