blob: a051d941ad9694c79b3798a38a11b305c3cda2a6 [file] [log] [blame]
Maximilian Luzfc622b32021-02-12 12:54:34 +01001// 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/module.h>
15#include <linux/platform_device.h>
16#include <linux/property.h>
17
18#include <linux/surface_aggregator/controller.h>
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. */
37static const struct software_node ssam_node_root = {
38 .name = "ssam_platform_hub",
39};
40
41/* Devices for Surface Book 2. */
42static const struct software_node *ssam_node_group_sb2[] = {
43 &ssam_node_root,
44 NULL,
45};
46
47/* Devices for Surface Book 3. */
48static const struct software_node *ssam_node_group_sb3[] = {
49 &ssam_node_root,
50 NULL,
51};
52
53/* Devices for Surface Laptop 1. */
54static const struct software_node *ssam_node_group_sl1[] = {
55 &ssam_node_root,
56 NULL,
57};
58
59/* Devices for Surface Laptop 2. */
60static const struct software_node *ssam_node_group_sl2[] = {
61 &ssam_node_root,
62 NULL,
63};
64
65/* Devices for Surface Laptop 3. */
66static const struct software_node *ssam_node_group_sl3[] = {
67 &ssam_node_root,
68 NULL,
69};
70
71/* Devices for Surface Laptop Go. */
72static const struct software_node *ssam_node_group_slg1[] = {
73 &ssam_node_root,
74 NULL,
75};
76
77/* Devices for Surface Pro 5. */
78static const struct software_node *ssam_node_group_sp5[] = {
79 &ssam_node_root,
80 NULL,
81};
82
83/* Devices for Surface Pro 6. */
84static const struct software_node *ssam_node_group_sp6[] = {
85 &ssam_node_root,
86 NULL,
87};
88
89/* Devices for Surface Pro 7. */
90static const struct software_node *ssam_node_group_sp7[] = {
91 &ssam_node_root,
92 NULL,
93};
94
95
96/* -- Device registry helper functions. ------------------------------------- */
97
98static int ssam_uid_from_string(const char *str, struct ssam_device_uid *uid)
99{
100 u8 d, tc, tid, iid, fn;
101 int n;
102
103 n = sscanf(str, "ssam:%hhx:%hhx:%hhx:%hhx:%hhx", &d, &tc, &tid, &iid, &fn);
104 if (n != 5)
105 return -EINVAL;
106
107 uid->domain = d;
108 uid->category = tc;
109 uid->target = tid;
110 uid->instance = iid;
111 uid->function = fn;
112
113 return 0;
114}
115
116static int ssam_hub_remove_devices_fn(struct device *dev, void *data)
117{
118 if (!is_ssam_device(dev))
119 return 0;
120
121 ssam_device_remove(to_ssam_device(dev));
122 return 0;
123}
124
125static void ssam_hub_remove_devices(struct device *parent)
126{
127 device_for_each_child_reverse(parent, NULL, ssam_hub_remove_devices_fn);
128}
129
130static int ssam_hub_add_device(struct device *parent, struct ssam_controller *ctrl,
131 struct fwnode_handle *node)
132{
133 struct ssam_device_uid uid;
134 struct ssam_device *sdev;
135 int status;
136
137 status = ssam_uid_from_string(fwnode_get_name(node), &uid);
138 if (status)
139 return status;
140
141 sdev = ssam_device_alloc(ctrl, uid);
142 if (!sdev)
143 return -ENOMEM;
144
145 sdev->dev.parent = parent;
146 sdev->dev.fwnode = node;
147
148 status = ssam_device_add(sdev);
149 if (status)
150 ssam_device_put(sdev);
151
152 return status;
153}
154
155static int ssam_hub_add_devices(struct device *parent, struct ssam_controller *ctrl,
156 struct fwnode_handle *node)
157{
158 struct fwnode_handle *child;
159 int status;
160
161 fwnode_for_each_child_node(node, child) {
162 /*
163 * Try to add the device specified in the firmware node. If
164 * this fails with -EINVAL, the node does not specify any SSAM
165 * device, so ignore it and continue with the next one.
166 */
167
168 status = ssam_hub_add_device(parent, ctrl, child);
169 if (status && status != -EINVAL)
170 goto err;
171 }
172
173 return 0;
174err:
175 ssam_hub_remove_devices(parent);
176 return status;
177}
178
179
180/* -- SSAM platform/meta-hub driver. ---------------------------------------- */
181
182static const struct acpi_device_id ssam_platform_hub_match[] = {
183 /* Surface Pro 4, 5, and 6 (OMBR < 0x10) */
184 { "MSHW0081", (unsigned long)ssam_node_group_sp5 },
185
186 /* Surface Pro 6 (OMBR >= 0x10) */
187 { "MSHW0111", (unsigned long)ssam_node_group_sp6 },
188
189 /* Surface Pro 7 */
190 { "MSHW0116", (unsigned long)ssam_node_group_sp7 },
191
192 /* Surface Book 2 */
193 { "MSHW0107", (unsigned long)ssam_node_group_sb2 },
194
195 /* Surface Book 3 */
196 { "MSHW0117", (unsigned long)ssam_node_group_sb3 },
197
198 /* Surface Laptop 1 */
199 { "MSHW0086", (unsigned long)ssam_node_group_sl1 },
200
201 /* Surface Laptop 2 */
202 { "MSHW0112", (unsigned long)ssam_node_group_sl2 },
203
204 /* Surface Laptop 3 (13", Intel) */
205 { "MSHW0114", (unsigned long)ssam_node_group_sl3 },
206
207 /* Surface Laptop 3 (15", AMD) */
208 { "MSHW0110", (unsigned long)ssam_node_group_sl3 },
209
210 /* Surface Laptop Go 1 */
211 { "MSHW0118", (unsigned long)ssam_node_group_slg1 },
212
213 { },
214};
215MODULE_DEVICE_TABLE(acpi, ssam_platform_hub_match);
216
217static int ssam_platform_hub_probe(struct platform_device *pdev)
218{
219 const struct software_node **nodes;
220 struct ssam_controller *ctrl;
221 struct fwnode_handle *root;
222 int status;
223
224 nodes = (const struct software_node **)acpi_device_get_match_data(&pdev->dev);
225 if (!nodes)
226 return -ENODEV;
227
228 /*
229 * As we're adding the SSAM client devices as children under this device
230 * and not the SSAM controller, we need to add a device link to the
231 * controller to ensure that we remove all of our devices before the
232 * controller is removed. This also guarantees proper ordering for
233 * suspend/resume of the devices on this hub.
234 */
235 ctrl = ssam_client_bind(&pdev->dev);
236 if (IS_ERR(ctrl))
237 return PTR_ERR(ctrl) == -ENODEV ? -EPROBE_DEFER : PTR_ERR(ctrl);
238
239 status = software_node_register_node_group(nodes);
240 if (status)
241 return status;
242
243 root = software_node_fwnode(&ssam_node_root);
244 if (!root) {
245 software_node_unregister_node_group(nodes);
246 return -ENOENT;
247 }
248
249 set_secondary_fwnode(&pdev->dev, root);
250
251 status = ssam_hub_add_devices(&pdev->dev, ctrl, root);
252 if (status) {
253 set_secondary_fwnode(&pdev->dev, NULL);
254 software_node_unregister_node_group(nodes);
255 }
256
257 platform_set_drvdata(pdev, nodes);
258 return status;
259}
260
261static int ssam_platform_hub_remove(struct platform_device *pdev)
262{
263 const struct software_node **nodes = platform_get_drvdata(pdev);
264
265 ssam_hub_remove_devices(&pdev->dev);
266 set_secondary_fwnode(&pdev->dev, NULL);
267 software_node_unregister_node_group(nodes);
268 return 0;
269}
270
271static struct platform_driver ssam_platform_hub_driver = {
272 .probe = ssam_platform_hub_probe,
273 .remove = ssam_platform_hub_remove,
274 .driver = {
275 .name = "surface_aggregator_platform_hub",
276 .acpi_match_table = ssam_platform_hub_match,
277 .probe_type = PROBE_PREFER_ASYNCHRONOUS,
278 },
279};
280module_platform_driver(ssam_platform_hub_driver);
281
282MODULE_AUTHOR("Maximilian Luz <luzmaximilian@gmail.com>");
283MODULE_DESCRIPTION("Device-registry for Surface System Aggregator Module");
284MODULE_LICENSE("GPL");