Linux Audio

Check our new training course

Loading...
Note: File does not exist in v4.6.
  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-2021 Maximilian Luz <luzmaximilian@gmail.com>
 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/* -- Device registry. ------------------------------------------------------ */
 26
 27/*
 28 * SSAM device names follow the SSAM module alias, meaning they are prefixed
 29 * with 'ssam:', followed by domain, category, target ID, instance ID, and
 30 * function, each encoded as two-digit hexadecimal, separated by ':'. In other
 31 * words, it follows the scheme
 32 *
 33 *      ssam:dd:cc:tt:ii:ff
 34 *
 35 * Where, 'dd', 'cc', 'tt', 'ii', and 'ff' are the two-digit hexadecimal
 36 * values mentioned above, respectively.
 37 */
 38
 39/* Root node. */
 40static const struct software_node ssam_node_root = {
 41	.name = "ssam_platform_hub",
 42};
 43
 44/* Base device hub (devices attached to Surface Book 3 base). */
 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/* AC adapter. */
 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/* Primary battery. */
 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/* Secondary battery (Surface Book 3). */
 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/* Platform profile / performance-mode device. */
 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/* DTX / detachment-system device (Surface Book 3). */
 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/* HID keyboard. */
 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/* HID touchpad. */
 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/* HID device instance 5 (unknown HID device). */
 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/* HID keyboard (base hub). */
 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/* HID touchpad (base hub). */
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/* HID device instance 5 (unknown HID device, base hub). */
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/* HID device instance 6 (unknown HID device, base hub). */
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/*
123 * Devices for 5th- and 6th-generations models:
124 * - Surface Book 2,
125 * - Surface Laptop 1 and 2,
126 * - Surface Pro 5 and 6.
127 */
128static const struct software_node *ssam_node_group_gen5[] = {
129	&ssam_node_root,
130	&ssam_node_tmp_pprof,
131	NULL,
132};
133
134/* Devices for Surface Book 3. */
135static const struct software_node *ssam_node_group_sb3[] = {
136	&ssam_node_root,
137	&ssam_node_hub_base,
138	&ssam_node_bat_ac,
139	&ssam_node_bat_main,
140	&ssam_node_bat_sb3base,
141	&ssam_node_tmp_pprof,
142	&ssam_node_bas_dtx,
143	&ssam_node_hid_base_keyboard,
144	&ssam_node_hid_base_touchpad,
145	&ssam_node_hid_base_iid5,
146	&ssam_node_hid_base_iid6,
147	NULL,
148};
149
150/* Devices for Surface Laptop 3 and 4. */
151static const struct software_node *ssam_node_group_sl3[] = {
152	&ssam_node_root,
153	&ssam_node_bat_ac,
154	&ssam_node_bat_main,
155	&ssam_node_tmp_pprof,
156	&ssam_node_hid_main_keyboard,
157	&ssam_node_hid_main_touchpad,
158	&ssam_node_hid_main_iid5,
159	NULL,
160};
161
162/* Devices for Surface Laptop Go. */
163static const struct software_node *ssam_node_group_slg1[] = {
164	&ssam_node_root,
165	&ssam_node_bat_ac,
166	&ssam_node_bat_main,
167	&ssam_node_tmp_pprof,
168	NULL,
169};
170
171/* Devices for Surface Pro 7 and Surface Pro 7+. */
172static const struct software_node *ssam_node_group_sp7[] = {
173	&ssam_node_root,
174	&ssam_node_bat_ac,
175	&ssam_node_bat_main,
176	&ssam_node_tmp_pprof,
177	NULL,
178};
179
180
181/* -- Device registry helper functions. ------------------------------------- */
182
183static int ssam_uid_from_string(const char *str, struct ssam_device_uid *uid)
184{
185	u8 d, tc, tid, iid, fn;
186	int n;
187
188	n = sscanf(str, "ssam:%hhx:%hhx:%hhx:%hhx:%hhx", &d, &tc, &tid, &iid, &fn);
189	if (n != 5)
190		return -EINVAL;
191
192	uid->domain = d;
193	uid->category = tc;
194	uid->target = tid;
195	uid->instance = iid;
196	uid->function = fn;
197
198	return 0;
199}
200
201static int ssam_hub_remove_devices_fn(struct device *dev, void *data)
202{
203	if (!is_ssam_device(dev))
204		return 0;
205
206	ssam_device_remove(to_ssam_device(dev));
207	return 0;
208}
209
210static void ssam_hub_remove_devices(struct device *parent)
211{
212	device_for_each_child_reverse(parent, NULL, ssam_hub_remove_devices_fn);
213}
214
215static int ssam_hub_add_device(struct device *parent, struct ssam_controller *ctrl,
216			       struct fwnode_handle *node)
217{
218	struct ssam_device_uid uid;
219	struct ssam_device *sdev;
220	int status;
221
222	status = ssam_uid_from_string(fwnode_get_name(node), &uid);
223	if (status)
224		return status;
225
226	sdev = ssam_device_alloc(ctrl, uid);
227	if (!sdev)
228		return -ENOMEM;
229
230	sdev->dev.parent = parent;
231	sdev->dev.fwnode = node;
232
233	status = ssam_device_add(sdev);
234	if (status)
235		ssam_device_put(sdev);
236
237	return status;
238}
239
240static int ssam_hub_add_devices(struct device *parent, struct ssam_controller *ctrl,
241				struct fwnode_handle *node)
242{
243	struct fwnode_handle *child;
244	int status;
245
246	fwnode_for_each_child_node(node, child) {
247		/*
248		 * Try to add the device specified in the firmware node. If
249		 * this fails with -EINVAL, the node does not specify any SSAM
250		 * device, so ignore it and continue with the next one.
251		 */
252
253		status = ssam_hub_add_device(parent, ctrl, child);
254		if (status && status != -EINVAL)
255			goto err;
256	}
257
258	return 0;
259err:
260	ssam_hub_remove_devices(parent);
261	return status;
262}
263
264
265/* -- SSAM base-hub driver. ------------------------------------------------- */
266
267/*
268 * Some devices (especially battery) may need a bit of time to be fully usable
269 * after being (re-)connected. This delay has been determined via
270 * experimentation.
271 */
272#define SSAM_BASE_UPDATE_CONNECT_DELAY		msecs_to_jiffies(2500)
273
274enum ssam_base_hub_state {
275	SSAM_BASE_HUB_UNINITIALIZED,
276	SSAM_BASE_HUB_CONNECTED,
277	SSAM_BASE_HUB_DISCONNECTED,
278};
279
280struct ssam_base_hub {
281	struct ssam_device *sdev;
282
283	enum ssam_base_hub_state state;
284	struct delayed_work update_work;
285
286	struct ssam_event_notifier notif;
287};
288
289SSAM_DEFINE_SYNC_REQUEST_R(ssam_bas_query_opmode, u8, {
290	.target_category = SSAM_SSH_TC_BAS,
291	.target_id       = 0x01,
292	.command_id      = 0x0d,
293	.instance_id     = 0x00,
294});
295
296#define SSAM_BAS_OPMODE_TABLET		0x00
297#define SSAM_EVENT_BAS_CID_CONNECTION	0x0c
298
299static int ssam_base_hub_query_state(struct ssam_base_hub *hub, enum ssam_base_hub_state *state)
300{
301	u8 opmode;
302	int status;
303
304	status = ssam_retry(ssam_bas_query_opmode, hub->sdev->ctrl, &opmode);
305	if (status < 0) {
306		dev_err(&hub->sdev->dev, "failed to query base state: %d\n", status);
307		return status;
308	}
309
310	if (opmode != SSAM_BAS_OPMODE_TABLET)
311		*state = SSAM_BASE_HUB_CONNECTED;
312	else
313		*state = SSAM_BASE_HUB_DISCONNECTED;
314
315	return 0;
316}
317
318static ssize_t ssam_base_hub_state_show(struct device *dev, struct device_attribute *attr,
319					char *buf)
320{
321	struct ssam_base_hub *hub = dev_get_drvdata(dev);
322	bool connected = hub->state == SSAM_BASE_HUB_CONNECTED;
323
324	return sysfs_emit(buf, "%d\n", connected);
325}
326
327static struct device_attribute ssam_base_hub_attr_state =
328	__ATTR(state, 0444, ssam_base_hub_state_show, NULL);
329
330static struct attribute *ssam_base_hub_attrs[] = {
331	&ssam_base_hub_attr_state.attr,
332	NULL,
333};
334
335static const struct attribute_group ssam_base_hub_group = {
336	.attrs = ssam_base_hub_attrs,
337};
338
339static void ssam_base_hub_update_workfn(struct work_struct *work)
340{
341	struct ssam_base_hub *hub = container_of(work, struct ssam_base_hub, update_work.work);
342	struct fwnode_handle *node = dev_fwnode(&hub->sdev->dev);
343	enum ssam_base_hub_state state;
344	int status = 0;
345
346	status = ssam_base_hub_query_state(hub, &state);
347	if (status)
348		return;
349
350	if (hub->state == state)
351		return;
352	hub->state = state;
353
354	if (hub->state == SSAM_BASE_HUB_CONNECTED)
355		status = ssam_hub_add_devices(&hub->sdev->dev, hub->sdev->ctrl, node);
356	else
357		ssam_hub_remove_devices(&hub->sdev->dev);
358
359	if (status)
360		dev_err(&hub->sdev->dev, "failed to update base-hub devices: %d\n", status);
361}
362
363static u32 ssam_base_hub_notif(struct ssam_event_notifier *nf, const struct ssam_event *event)
364{
365	struct ssam_base_hub *hub = container_of(nf, struct ssam_base_hub, notif);
366	unsigned long delay;
367
368	if (event->command_id != SSAM_EVENT_BAS_CID_CONNECTION)
369		return 0;
370
371	if (event->length < 1) {
372		dev_err(&hub->sdev->dev, "unexpected payload size: %u\n", event->length);
373		return 0;
374	}
375
376	/*
377	 * Delay update when the base is being connected to give devices/EC
378	 * some time to set up.
379	 */
380	delay = event->data[0] ? SSAM_BASE_UPDATE_CONNECT_DELAY : 0;
381
382	schedule_delayed_work(&hub->update_work, delay);
383
384	/*
385	 * Do not return SSAM_NOTIF_HANDLED: The event should be picked up and
386	 * consumed by the detachment system driver. We're just a (more or less)
387	 * silent observer.
388	 */
389	return 0;
390}
391
392static int __maybe_unused ssam_base_hub_resume(struct device *dev)
393{
394	struct ssam_base_hub *hub = dev_get_drvdata(dev);
395
396	schedule_delayed_work(&hub->update_work, 0);
397	return 0;
398}
399static SIMPLE_DEV_PM_OPS(ssam_base_hub_pm_ops, NULL, ssam_base_hub_resume);
400
401static int ssam_base_hub_probe(struct ssam_device *sdev)
402{
403	struct ssam_base_hub *hub;
404	int status;
405
406	hub = devm_kzalloc(&sdev->dev, sizeof(*hub), GFP_KERNEL);
407	if (!hub)
408		return -ENOMEM;
409
410	hub->sdev = sdev;
411	hub->state = SSAM_BASE_HUB_UNINITIALIZED;
412
413	hub->notif.base.priority = INT_MAX;  /* This notifier should run first. */
414	hub->notif.base.fn = ssam_base_hub_notif;
415	hub->notif.event.reg = SSAM_EVENT_REGISTRY_SAM;
416	hub->notif.event.id.target_category = SSAM_SSH_TC_BAS,
417	hub->notif.event.id.instance = 0,
418	hub->notif.event.mask = SSAM_EVENT_MASK_NONE;
419	hub->notif.event.flags = SSAM_EVENT_SEQUENCED;
420
421	INIT_DELAYED_WORK(&hub->update_work, ssam_base_hub_update_workfn);
422
423	ssam_device_set_drvdata(sdev, hub);
424
425	status = ssam_notifier_register(sdev->ctrl, &hub->notif);
426	if (status)
427		return status;
428
429	status = sysfs_create_group(&sdev->dev.kobj, &ssam_base_hub_group);
430	if (status)
431		goto err;
432
433	schedule_delayed_work(&hub->update_work, 0);
434	return 0;
435
436err:
437	ssam_notifier_unregister(sdev->ctrl, &hub->notif);
438	cancel_delayed_work_sync(&hub->update_work);
439	ssam_hub_remove_devices(&sdev->dev);
440	return status;
441}
442
443static void ssam_base_hub_remove(struct ssam_device *sdev)
444{
445	struct ssam_base_hub *hub = ssam_device_get_drvdata(sdev);
446
447	sysfs_remove_group(&sdev->dev.kobj, &ssam_base_hub_group);
448
449	ssam_notifier_unregister(sdev->ctrl, &hub->notif);
450	cancel_delayed_work_sync(&hub->update_work);
451	ssam_hub_remove_devices(&sdev->dev);
452}
453
454static const struct ssam_device_id ssam_base_hub_match[] = {
455	{ SSAM_VDEV(HUB, 0x02, SSAM_ANY_IID, 0x00) },
456	{ },
457};
458
459static struct ssam_device_driver ssam_base_hub_driver = {
460	.probe = ssam_base_hub_probe,
461	.remove = ssam_base_hub_remove,
462	.match_table = ssam_base_hub_match,
463	.driver = {
464		.name = "surface_aggregator_base_hub",
465		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
466		.pm = &ssam_base_hub_pm_ops,
467	},
468};
469
470
471/* -- SSAM platform/meta-hub driver. ---------------------------------------- */
472
473static const struct acpi_device_id ssam_platform_hub_match[] = {
474	/* Surface Pro 4, 5, and 6 (OMBR < 0x10) */
475	{ "MSHW0081", (unsigned long)ssam_node_group_gen5 },
476
477	/* Surface Pro 6 (OMBR >= 0x10) */
478	{ "MSHW0111", (unsigned long)ssam_node_group_gen5 },
479
480	/* Surface Pro 7 */
481	{ "MSHW0116", (unsigned long)ssam_node_group_sp7 },
482
483	/* Surface Pro 7+ */
484	{ "MSHW0119", (unsigned long)ssam_node_group_sp7 },
485
486	/* Surface Book 2 */
487	{ "MSHW0107", (unsigned long)ssam_node_group_gen5 },
488
489	/* Surface Book 3 */
490	{ "MSHW0117", (unsigned long)ssam_node_group_sb3 },
491
492	/* Surface Laptop 1 */
493	{ "MSHW0086", (unsigned long)ssam_node_group_gen5 },
494
495	/* Surface Laptop 2 */
496	{ "MSHW0112", (unsigned long)ssam_node_group_gen5 },
497
498	/* Surface Laptop 3 (13", Intel) */
499	{ "MSHW0114", (unsigned long)ssam_node_group_sl3 },
500
501	/* Surface Laptop 3 (15", AMD) and 4 (15", AMD) */
502	{ "MSHW0110", (unsigned long)ssam_node_group_sl3 },
503
504	/* Surface Laptop 4 (13", Intel) */
505	{ "MSHW0250", (unsigned long)ssam_node_group_sl3 },
506
507	/* Surface Laptop Go 1 */
508	{ "MSHW0118", (unsigned long)ssam_node_group_slg1 },
509
510	{ },
511};
512MODULE_DEVICE_TABLE(acpi, ssam_platform_hub_match);
513
514static int ssam_platform_hub_probe(struct platform_device *pdev)
515{
516	const struct software_node **nodes;
517	struct ssam_controller *ctrl;
518	struct fwnode_handle *root;
519	int status;
520
521	nodes = (const struct software_node **)acpi_device_get_match_data(&pdev->dev);
522	if (!nodes)
523		return -ENODEV;
524
525	/*
526	 * As we're adding the SSAM client devices as children under this device
527	 * and not the SSAM controller, we need to add a device link to the
528	 * controller to ensure that we remove all of our devices before the
529	 * controller is removed. This also guarantees proper ordering for
530	 * suspend/resume of the devices on this hub.
531	 */
532	ctrl = ssam_client_bind(&pdev->dev);
533	if (IS_ERR(ctrl))
534		return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl);
535
536	status = software_node_register_node_group(nodes);
537	if (status)
538		return status;
539
540	root = software_node_fwnode(&ssam_node_root);
541	if (!root) {
542		software_node_unregister_node_group(nodes);
543		return -ENOENT;
544	}
545
546	set_secondary_fwnode(&pdev->dev, root);
547
548	status = ssam_hub_add_devices(&pdev->dev, ctrl, root);
549	if (status) {
550		set_secondary_fwnode(&pdev->dev, NULL);
551		software_node_unregister_node_group(nodes);
552	}
553
554	platform_set_drvdata(pdev, nodes);
555	return status;
556}
557
558static int ssam_platform_hub_remove(struct platform_device *pdev)
559{
560	const struct software_node **nodes = platform_get_drvdata(pdev);
561
562	ssam_hub_remove_devices(&pdev->dev);
563	set_secondary_fwnode(&pdev->dev, NULL);
564	software_node_unregister_node_group(nodes);
565	return 0;
566}
567
568static struct platform_driver ssam_platform_hub_driver = {
569	.probe = ssam_platform_hub_probe,
570	.remove = ssam_platform_hub_remove,
571	.driver = {
572		.name = "surface_aggregator_platform_hub",
573		.acpi_match_table = ssam_platform_hub_match,
574		.probe_type = PROBE_PREFER_ASYNCHRONOUS,
575	},
576};
577
578
579/* -- Module initialization. ------------------------------------------------ */
580
581static int __init ssam_device_hub_init(void)
582{
583	int status;
584
585	status = platform_driver_register(&ssam_platform_hub_driver);
586	if (status)
587		return status;
588
589	status = ssam_device_driver_register(&ssam_base_hub_driver);
590	if (status)
591		platform_driver_unregister(&ssam_platform_hub_driver);
592
593	return status;
594}
595module_init(ssam_device_hub_init);
596
597static void __exit ssam_device_hub_exit(void)
598{
599	ssam_device_driver_unregister(&ssam_base_hub_driver);
600	platform_driver_unregister(&ssam_platform_hub_driver);
601}
602module_exit(ssam_device_hub_exit);
603
604MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
605MODULE_DESCRIPTION("Device-registry for Surface System Aggregator Module");
606MODULE_LICENSE("GPL");