1eb0e90a8SMaximilian Luz // SPDX-License-Identifier: GPL-2.0+
2eb0e90a8SMaximilian Luz /*
3eb0e90a8SMaximilian Luz  * Surface System Aggregator Module bus and device integration.
4eb0e90a8SMaximilian Luz  *
5b2763358SMaximilian Luz  * Copyright (C) 2019-2021 Maximilian Luz <luzmaximilian@gmail.com>
6eb0e90a8SMaximilian Luz  */
7eb0e90a8SMaximilian Luz 
8eb0e90a8SMaximilian Luz #include <linux/device.h>
9*4a4ab610SMaximilian Luz #include <linux/property.h>
10eb0e90a8SMaximilian Luz #include <linux/slab.h>
11eb0e90a8SMaximilian Luz 
12eb0e90a8SMaximilian Luz #include <linux/surface_aggregator/controller.h>
13eb0e90a8SMaximilian Luz #include <linux/surface_aggregator/device.h>
14eb0e90a8SMaximilian Luz 
15eb0e90a8SMaximilian Luz #include "bus.h"
16eb0e90a8SMaximilian Luz #include "controller.h"
17eb0e90a8SMaximilian Luz 
18*4a4ab610SMaximilian Luz 
19*4a4ab610SMaximilian Luz /* -- Device and bus functions. --------------------------------------------- */
20*4a4ab610SMaximilian Luz 
21eb0e90a8SMaximilian Luz static ssize_t modalias_show(struct device *dev, struct device_attribute *attr,
22eb0e90a8SMaximilian Luz 			     char *buf)
23eb0e90a8SMaximilian Luz {
24eb0e90a8SMaximilian Luz 	struct ssam_device *sdev = to_ssam_device(dev);
25eb0e90a8SMaximilian Luz 
26eb0e90a8SMaximilian Luz 	return sysfs_emit(buf, "ssam:d%02Xc%02Xt%02Xi%02Xf%02X\n",
27eb0e90a8SMaximilian Luz 			sdev->uid.domain, sdev->uid.category, sdev->uid.target,
28eb0e90a8SMaximilian Luz 			sdev->uid.instance, sdev->uid.function);
29eb0e90a8SMaximilian Luz }
30eb0e90a8SMaximilian Luz static DEVICE_ATTR_RO(modalias);
31eb0e90a8SMaximilian Luz 
32eb0e90a8SMaximilian Luz static struct attribute *ssam_device_attrs[] = {
33eb0e90a8SMaximilian Luz 	&dev_attr_modalias.attr,
34eb0e90a8SMaximilian Luz 	NULL,
35eb0e90a8SMaximilian Luz };
36eb0e90a8SMaximilian Luz ATTRIBUTE_GROUPS(ssam_device);
37eb0e90a8SMaximilian Luz 
38eb0e90a8SMaximilian Luz static int ssam_device_uevent(struct device *dev, struct kobj_uevent_env *env)
39eb0e90a8SMaximilian Luz {
40eb0e90a8SMaximilian Luz 	struct ssam_device *sdev = to_ssam_device(dev);
41eb0e90a8SMaximilian Luz 
42eb0e90a8SMaximilian Luz 	return add_uevent_var(env, "MODALIAS=ssam:d%02Xc%02Xt%02Xi%02Xf%02X",
43eb0e90a8SMaximilian Luz 			      sdev->uid.domain, sdev->uid.category,
44eb0e90a8SMaximilian Luz 			      sdev->uid.target, sdev->uid.instance,
45eb0e90a8SMaximilian Luz 			      sdev->uid.function);
46eb0e90a8SMaximilian Luz }
47eb0e90a8SMaximilian Luz 
48eb0e90a8SMaximilian Luz static void ssam_device_release(struct device *dev)
49eb0e90a8SMaximilian Luz {
50eb0e90a8SMaximilian Luz 	struct ssam_device *sdev = to_ssam_device(dev);
51eb0e90a8SMaximilian Luz 
52eb0e90a8SMaximilian Luz 	ssam_controller_put(sdev->ctrl);
53*4a4ab610SMaximilian Luz 	fwnode_handle_put(sdev->dev.fwnode);
54eb0e90a8SMaximilian Luz 	kfree(sdev);
55eb0e90a8SMaximilian Luz }
56eb0e90a8SMaximilian Luz 
57eb0e90a8SMaximilian Luz const struct device_type ssam_device_type = {
58eb0e90a8SMaximilian Luz 	.name    = "surface_aggregator_device",
59eb0e90a8SMaximilian Luz 	.groups  = ssam_device_groups,
60eb0e90a8SMaximilian Luz 	.uevent  = ssam_device_uevent,
61eb0e90a8SMaximilian Luz 	.release = ssam_device_release,
62eb0e90a8SMaximilian Luz };
63eb0e90a8SMaximilian Luz EXPORT_SYMBOL_GPL(ssam_device_type);
64eb0e90a8SMaximilian Luz 
65eb0e90a8SMaximilian Luz /**
66eb0e90a8SMaximilian Luz  * ssam_device_alloc() - Allocate and initialize a SSAM client device.
67eb0e90a8SMaximilian Luz  * @ctrl: The controller under which the device should be added.
68eb0e90a8SMaximilian Luz  * @uid:  The UID of the device to be added.
69eb0e90a8SMaximilian Luz  *
70eb0e90a8SMaximilian Luz  * Allocates and initializes a new client device. The parent of the device
71eb0e90a8SMaximilian Luz  * will be set to the controller device and the name will be set based on the
72eb0e90a8SMaximilian Luz  * UID. Note that the device still has to be added via ssam_device_add().
73eb0e90a8SMaximilian Luz  * Refer to that function for more details.
74eb0e90a8SMaximilian Luz  *
75eb0e90a8SMaximilian Luz  * Return: Returns the newly allocated and initialized SSAM client device, or
76eb0e90a8SMaximilian Luz  * %NULL if it could not be allocated.
77eb0e90a8SMaximilian Luz  */
78eb0e90a8SMaximilian Luz struct ssam_device *ssam_device_alloc(struct ssam_controller *ctrl,
79eb0e90a8SMaximilian Luz 				      struct ssam_device_uid uid)
80eb0e90a8SMaximilian Luz {
81eb0e90a8SMaximilian Luz 	struct ssam_device *sdev;
82eb0e90a8SMaximilian Luz 
83eb0e90a8SMaximilian Luz 	sdev = kzalloc(sizeof(*sdev), GFP_KERNEL);
84eb0e90a8SMaximilian Luz 	if (!sdev)
85eb0e90a8SMaximilian Luz 		return NULL;
86eb0e90a8SMaximilian Luz 
87eb0e90a8SMaximilian Luz 	device_initialize(&sdev->dev);
88eb0e90a8SMaximilian Luz 	sdev->dev.bus = &ssam_bus_type;
89eb0e90a8SMaximilian Luz 	sdev->dev.type = &ssam_device_type;
90eb0e90a8SMaximilian Luz 	sdev->dev.parent = ssam_controller_device(ctrl);
91eb0e90a8SMaximilian Luz 	sdev->ctrl = ssam_controller_get(ctrl);
92eb0e90a8SMaximilian Luz 	sdev->uid = uid;
93eb0e90a8SMaximilian Luz 
94eb0e90a8SMaximilian Luz 	dev_set_name(&sdev->dev, "%02x:%02x:%02x:%02x:%02x",
95eb0e90a8SMaximilian Luz 		     sdev->uid.domain, sdev->uid.category, sdev->uid.target,
96eb0e90a8SMaximilian Luz 		     sdev->uid.instance, sdev->uid.function);
97eb0e90a8SMaximilian Luz 
98eb0e90a8SMaximilian Luz 	return sdev;
99eb0e90a8SMaximilian Luz }
100eb0e90a8SMaximilian Luz EXPORT_SYMBOL_GPL(ssam_device_alloc);
101eb0e90a8SMaximilian Luz 
102eb0e90a8SMaximilian Luz /**
103eb0e90a8SMaximilian Luz  * ssam_device_add() - Add a SSAM client device.
104eb0e90a8SMaximilian Luz  * @sdev: The SSAM client device to be added.
105eb0e90a8SMaximilian Luz  *
106eb0e90a8SMaximilian Luz  * Added client devices must be guaranteed to always have a valid and active
107eb0e90a8SMaximilian Luz  * controller. Thus, this function will fail with %-ENODEV if the controller
108eb0e90a8SMaximilian Luz  * of the device has not been initialized yet, has been suspended, or has been
109eb0e90a8SMaximilian Luz  * shut down.
110eb0e90a8SMaximilian Luz  *
111eb0e90a8SMaximilian Luz  * The caller of this function should ensure that the corresponding call to
112eb0e90a8SMaximilian Luz  * ssam_device_remove() is issued before the controller is shut down. If the
113eb0e90a8SMaximilian Luz  * added device is a direct child of the controller device (default), it will
114eb0e90a8SMaximilian Luz  * be automatically removed when the controller is shut down.
115eb0e90a8SMaximilian Luz  *
116eb0e90a8SMaximilian Luz  * By default, the controller device will become the parent of the newly
117eb0e90a8SMaximilian Luz  * created client device. The parent may be changed before ssam_device_add is
118eb0e90a8SMaximilian Luz  * called, but care must be taken that a) the correct suspend/resume ordering
119eb0e90a8SMaximilian Luz  * is guaranteed and b) the client device does not outlive the controller,
120eb0e90a8SMaximilian Luz  * i.e. that the device is removed before the controller is being shut down.
121eb0e90a8SMaximilian Luz  * In case these guarantees have to be manually enforced, please refer to the
122eb0e90a8SMaximilian Luz  * ssam_client_link() and ssam_client_bind() functions, which are intended to
123eb0e90a8SMaximilian Luz  * set up device-links for this purpose.
124eb0e90a8SMaximilian Luz  *
125eb0e90a8SMaximilian Luz  * Return: Returns zero on success, a negative error code on failure.
126eb0e90a8SMaximilian Luz  */
127eb0e90a8SMaximilian Luz int ssam_device_add(struct ssam_device *sdev)
128eb0e90a8SMaximilian Luz {
129eb0e90a8SMaximilian Luz 	int status;
130eb0e90a8SMaximilian Luz 
131eb0e90a8SMaximilian Luz 	/*
132eb0e90a8SMaximilian Luz 	 * Ensure that we can only add new devices to a controller if it has
133eb0e90a8SMaximilian Luz 	 * been started and is not going away soon. This works in combination
134eb0e90a8SMaximilian Luz 	 * with ssam_controller_remove_clients to ensure driver presence for the
135eb0e90a8SMaximilian Luz 	 * controller device, i.e. it ensures that the controller (sdev->ctrl)
136eb0e90a8SMaximilian Luz 	 * is always valid and can be used for requests as long as the client
137eb0e90a8SMaximilian Luz 	 * device we add here is registered as child under it. This essentially
138eb0e90a8SMaximilian Luz 	 * guarantees that the client driver can always expect the preconditions
139eb0e90a8SMaximilian Luz 	 * for functions like ssam_request_sync (controller has to be started
140eb0e90a8SMaximilian Luz 	 * and is not suspended) to hold and thus does not have to check for
141eb0e90a8SMaximilian Luz 	 * them.
142eb0e90a8SMaximilian Luz 	 *
143eb0e90a8SMaximilian Luz 	 * Note that for this to work, the controller has to be a parent device.
144eb0e90a8SMaximilian Luz 	 * If it is not a direct parent, care has to be taken that the device is
145eb0e90a8SMaximilian Luz 	 * removed via ssam_device_remove(), as device_unregister does not
146eb0e90a8SMaximilian Luz 	 * remove child devices recursively.
147eb0e90a8SMaximilian Luz 	 */
148eb0e90a8SMaximilian Luz 	ssam_controller_statelock(sdev->ctrl);
149eb0e90a8SMaximilian Luz 
150eb0e90a8SMaximilian Luz 	if (sdev->ctrl->state != SSAM_CONTROLLER_STARTED) {
151eb0e90a8SMaximilian Luz 		ssam_controller_stateunlock(sdev->ctrl);
152eb0e90a8SMaximilian Luz 		return -ENODEV;
153eb0e90a8SMaximilian Luz 	}
154eb0e90a8SMaximilian Luz 
155eb0e90a8SMaximilian Luz 	status = device_add(&sdev->dev);
156eb0e90a8SMaximilian Luz 
157eb0e90a8SMaximilian Luz 	ssam_controller_stateunlock(sdev->ctrl);
158eb0e90a8SMaximilian Luz 	return status;
159eb0e90a8SMaximilian Luz }
160eb0e90a8SMaximilian Luz EXPORT_SYMBOL_GPL(ssam_device_add);
161eb0e90a8SMaximilian Luz 
162eb0e90a8SMaximilian Luz /**
163eb0e90a8SMaximilian Luz  * ssam_device_remove() - Remove a SSAM client device.
164eb0e90a8SMaximilian Luz  * @sdev: The device to remove.
165eb0e90a8SMaximilian Luz  *
166eb0e90a8SMaximilian Luz  * Removes and unregisters the provided SSAM client device.
167eb0e90a8SMaximilian Luz  */
168eb0e90a8SMaximilian Luz void ssam_device_remove(struct ssam_device *sdev)
169eb0e90a8SMaximilian Luz {
170eb0e90a8SMaximilian Luz 	device_unregister(&sdev->dev);
171eb0e90a8SMaximilian Luz }
172eb0e90a8SMaximilian Luz EXPORT_SYMBOL_GPL(ssam_device_remove);
173eb0e90a8SMaximilian Luz 
174eb0e90a8SMaximilian Luz /**
175eb0e90a8SMaximilian Luz  * ssam_device_id_compatible() - Check if a device ID matches a UID.
176eb0e90a8SMaximilian Luz  * @id:  The device ID as potential match.
177eb0e90a8SMaximilian Luz  * @uid: The device UID matching against.
178eb0e90a8SMaximilian Luz  *
179eb0e90a8SMaximilian Luz  * Check if the given ID is a match for the given UID, i.e. if a device with
180eb0e90a8SMaximilian Luz  * the provided UID is compatible to the given ID following the match rules
181eb0e90a8SMaximilian Luz  * described in its &ssam_device_id.match_flags member.
182eb0e90a8SMaximilian Luz  *
183eb0e90a8SMaximilian Luz  * Return: Returns %true if the given UID is compatible to the match rule
184eb0e90a8SMaximilian Luz  * described by the given ID, %false otherwise.
185eb0e90a8SMaximilian Luz  */
186eb0e90a8SMaximilian Luz static bool ssam_device_id_compatible(const struct ssam_device_id *id,
187eb0e90a8SMaximilian Luz 				      struct ssam_device_uid uid)
188eb0e90a8SMaximilian Luz {
189eb0e90a8SMaximilian Luz 	if (id->domain != uid.domain || id->category != uid.category)
190eb0e90a8SMaximilian Luz 		return false;
191eb0e90a8SMaximilian Luz 
192eb0e90a8SMaximilian Luz 	if ((id->match_flags & SSAM_MATCH_TARGET) && id->target != uid.target)
193eb0e90a8SMaximilian Luz 		return false;
194eb0e90a8SMaximilian Luz 
195eb0e90a8SMaximilian Luz 	if ((id->match_flags & SSAM_MATCH_INSTANCE) && id->instance != uid.instance)
196eb0e90a8SMaximilian Luz 		return false;
197eb0e90a8SMaximilian Luz 
198eb0e90a8SMaximilian Luz 	if ((id->match_flags & SSAM_MATCH_FUNCTION) && id->function != uid.function)
199eb0e90a8SMaximilian Luz 		return false;
200eb0e90a8SMaximilian Luz 
201eb0e90a8SMaximilian Luz 	return true;
202eb0e90a8SMaximilian Luz }
203eb0e90a8SMaximilian Luz 
204eb0e90a8SMaximilian Luz /**
205eb0e90a8SMaximilian Luz  * ssam_device_id_is_null() - Check if a device ID is null.
206eb0e90a8SMaximilian Luz  * @id: The device ID to check.
207eb0e90a8SMaximilian Luz  *
208eb0e90a8SMaximilian Luz  * Check if a given device ID is null, i.e. all zeros. Used to check for the
209eb0e90a8SMaximilian Luz  * end of ``MODULE_DEVICE_TABLE(ssam, ...)`` or similar lists.
210eb0e90a8SMaximilian Luz  *
211eb0e90a8SMaximilian Luz  * Return: Returns %true if the given ID represents a null ID, %false
212eb0e90a8SMaximilian Luz  * otherwise.
213eb0e90a8SMaximilian Luz  */
214eb0e90a8SMaximilian Luz static bool ssam_device_id_is_null(const struct ssam_device_id *id)
215eb0e90a8SMaximilian Luz {
216eb0e90a8SMaximilian Luz 	return id->match_flags == 0 &&
217eb0e90a8SMaximilian Luz 		id->domain == 0 &&
218eb0e90a8SMaximilian Luz 		id->category == 0 &&
219eb0e90a8SMaximilian Luz 		id->target == 0 &&
220eb0e90a8SMaximilian Luz 		id->instance == 0 &&
221eb0e90a8SMaximilian Luz 		id->function == 0 &&
222eb0e90a8SMaximilian Luz 		id->driver_data == 0;
223eb0e90a8SMaximilian Luz }
224eb0e90a8SMaximilian Luz 
225eb0e90a8SMaximilian Luz /**
226eb0e90a8SMaximilian Luz  * ssam_device_id_match() - Find the matching ID table entry for the given UID.
227eb0e90a8SMaximilian Luz  * @table: The table to search in.
228eb0e90a8SMaximilian Luz  * @uid:   The UID to matched against the individual table entries.
229eb0e90a8SMaximilian Luz  *
230eb0e90a8SMaximilian Luz  * Find the first match for the provided device UID in the provided ID table
231eb0e90a8SMaximilian Luz  * and return it. Returns %NULL if no match could be found.
232eb0e90a8SMaximilian Luz  */
233eb0e90a8SMaximilian Luz const struct ssam_device_id *ssam_device_id_match(const struct ssam_device_id *table,
234eb0e90a8SMaximilian Luz 						  const struct ssam_device_uid uid)
235eb0e90a8SMaximilian Luz {
236eb0e90a8SMaximilian Luz 	const struct ssam_device_id *id;
237eb0e90a8SMaximilian Luz 
238eb0e90a8SMaximilian Luz 	for (id = table; !ssam_device_id_is_null(id); ++id)
239eb0e90a8SMaximilian Luz 		if (ssam_device_id_compatible(id, uid))
240eb0e90a8SMaximilian Luz 			return id;
241eb0e90a8SMaximilian Luz 
242eb0e90a8SMaximilian Luz 	return NULL;
243eb0e90a8SMaximilian Luz }
244eb0e90a8SMaximilian Luz EXPORT_SYMBOL_GPL(ssam_device_id_match);
245eb0e90a8SMaximilian Luz 
246eb0e90a8SMaximilian Luz /**
247eb0e90a8SMaximilian Luz  * ssam_device_get_match() - Find and return the ID matching the device in the
248eb0e90a8SMaximilian Luz  * ID table of the bound driver.
249eb0e90a8SMaximilian Luz  * @dev: The device for which to get the matching ID table entry.
250eb0e90a8SMaximilian Luz  *
251eb0e90a8SMaximilian Luz  * Find the fist match for the UID of the device in the ID table of the
252eb0e90a8SMaximilian Luz  * currently bound driver and return it. Returns %NULL if the device does not
253eb0e90a8SMaximilian Luz  * have a driver bound to it, the driver does not have match_table (i.e. it is
254eb0e90a8SMaximilian Luz  * %NULL), or there is no match in the driver's match_table.
255eb0e90a8SMaximilian Luz  *
256eb0e90a8SMaximilian Luz  * This function essentially calls ssam_device_id_match() with the ID table of
257eb0e90a8SMaximilian Luz  * the bound device driver and the UID of the device.
258eb0e90a8SMaximilian Luz  *
259eb0e90a8SMaximilian Luz  * Return: Returns the first match for the UID of the device in the device
260eb0e90a8SMaximilian Luz  * driver's match table, or %NULL if no such match could be found.
261eb0e90a8SMaximilian Luz  */
262eb0e90a8SMaximilian Luz const struct ssam_device_id *ssam_device_get_match(const struct ssam_device *dev)
263eb0e90a8SMaximilian Luz {
264eb0e90a8SMaximilian Luz 	const struct ssam_device_driver *sdrv;
265eb0e90a8SMaximilian Luz 
266eb0e90a8SMaximilian Luz 	sdrv = to_ssam_device_driver(dev->dev.driver);
267eb0e90a8SMaximilian Luz 	if (!sdrv)
268eb0e90a8SMaximilian Luz 		return NULL;
269eb0e90a8SMaximilian Luz 
270eb0e90a8SMaximilian Luz 	if (!sdrv->match_table)
271eb0e90a8SMaximilian Luz 		return NULL;
272eb0e90a8SMaximilian Luz 
273eb0e90a8SMaximilian Luz 	return ssam_device_id_match(sdrv->match_table, dev->uid);
274eb0e90a8SMaximilian Luz }
275eb0e90a8SMaximilian Luz EXPORT_SYMBOL_GPL(ssam_device_get_match);
276eb0e90a8SMaximilian Luz 
277eb0e90a8SMaximilian Luz /**
278eb0e90a8SMaximilian Luz  * ssam_device_get_match_data() - Find the ID matching the device in the
279eb0e90a8SMaximilian Luz  * ID table of the bound driver and return its ``driver_data`` member.
280eb0e90a8SMaximilian Luz  * @dev: The device for which to get the match data.
281eb0e90a8SMaximilian Luz  *
282eb0e90a8SMaximilian Luz  * Find the fist match for the UID of the device in the ID table of the
283eb0e90a8SMaximilian Luz  * corresponding driver and return its driver_data. Returns %NULL if the
284eb0e90a8SMaximilian Luz  * device does not have a driver bound to it, the driver does not have
285eb0e90a8SMaximilian Luz  * match_table (i.e. it is %NULL), there is no match in the driver's
286eb0e90a8SMaximilian Luz  * match_table, or the match does not have any driver_data.
287eb0e90a8SMaximilian Luz  *
288eb0e90a8SMaximilian Luz  * This function essentially calls ssam_device_get_match() and, if any match
289eb0e90a8SMaximilian Luz  * could be found, returns its ``struct ssam_device_id.driver_data`` member.
290eb0e90a8SMaximilian Luz  *
291eb0e90a8SMaximilian Luz  * Return: Returns the driver data associated with the first match for the UID
292eb0e90a8SMaximilian Luz  * of the device in the device driver's match table, or %NULL if no such match
293eb0e90a8SMaximilian Luz  * could be found.
294eb0e90a8SMaximilian Luz  */
295eb0e90a8SMaximilian Luz const void *ssam_device_get_match_data(const struct ssam_device *dev)
296eb0e90a8SMaximilian Luz {
297eb0e90a8SMaximilian Luz 	const struct ssam_device_id *id;
298eb0e90a8SMaximilian Luz 
299eb0e90a8SMaximilian Luz 	id = ssam_device_get_match(dev);
300eb0e90a8SMaximilian Luz 	if (!id)
301eb0e90a8SMaximilian Luz 		return NULL;
302eb0e90a8SMaximilian Luz 
303eb0e90a8SMaximilian Luz 	return (const void *)id->driver_data;
304eb0e90a8SMaximilian Luz }
305eb0e90a8SMaximilian Luz EXPORT_SYMBOL_GPL(ssam_device_get_match_data);
306eb0e90a8SMaximilian Luz 
307eb0e90a8SMaximilian Luz static int ssam_bus_match(struct device *dev, struct device_driver *drv)
308eb0e90a8SMaximilian Luz {
309eb0e90a8SMaximilian Luz 	struct ssam_device_driver *sdrv = to_ssam_device_driver(drv);
310eb0e90a8SMaximilian Luz 	struct ssam_device *sdev = to_ssam_device(dev);
311eb0e90a8SMaximilian Luz 
312eb0e90a8SMaximilian Luz 	if (!is_ssam_device(dev))
313eb0e90a8SMaximilian Luz 		return 0;
314eb0e90a8SMaximilian Luz 
315eb0e90a8SMaximilian Luz 	return !!ssam_device_id_match(sdrv->match_table, sdev->uid);
316eb0e90a8SMaximilian Luz }
317eb0e90a8SMaximilian Luz 
318eb0e90a8SMaximilian Luz static int ssam_bus_probe(struct device *dev)
319eb0e90a8SMaximilian Luz {
320eb0e90a8SMaximilian Luz 	return to_ssam_device_driver(dev->driver)
321eb0e90a8SMaximilian Luz 		->probe(to_ssam_device(dev));
322eb0e90a8SMaximilian Luz }
323eb0e90a8SMaximilian Luz 
324fc7a6209SUwe Kleine-König static void ssam_bus_remove(struct device *dev)
325eb0e90a8SMaximilian Luz {
326eb0e90a8SMaximilian Luz 	struct ssam_device_driver *sdrv = to_ssam_device_driver(dev->driver);
327eb0e90a8SMaximilian Luz 
328eb0e90a8SMaximilian Luz 	if (sdrv->remove)
329eb0e90a8SMaximilian Luz 		sdrv->remove(to_ssam_device(dev));
330eb0e90a8SMaximilian Luz }
331eb0e90a8SMaximilian Luz 
332eb0e90a8SMaximilian Luz struct bus_type ssam_bus_type = {
333eb0e90a8SMaximilian Luz 	.name   = "surface_aggregator",
334eb0e90a8SMaximilian Luz 	.match  = ssam_bus_match,
335eb0e90a8SMaximilian Luz 	.probe  = ssam_bus_probe,
336eb0e90a8SMaximilian Luz 	.remove = ssam_bus_remove,
337eb0e90a8SMaximilian Luz };
338eb0e90a8SMaximilian Luz EXPORT_SYMBOL_GPL(ssam_bus_type);
339eb0e90a8SMaximilian Luz 
340eb0e90a8SMaximilian Luz /**
341eb0e90a8SMaximilian Luz  * __ssam_device_driver_register() - Register a SSAM client device driver.
342eb0e90a8SMaximilian Luz  * @sdrv:  The driver to register.
343eb0e90a8SMaximilian Luz  * @owner: The module owning the provided driver.
344eb0e90a8SMaximilian Luz  *
345eb0e90a8SMaximilian Luz  * Please refer to the ssam_device_driver_register() macro for the normal way
346eb0e90a8SMaximilian Luz  * to register a driver from inside its owning module.
347eb0e90a8SMaximilian Luz  */
348eb0e90a8SMaximilian Luz int __ssam_device_driver_register(struct ssam_device_driver *sdrv,
349eb0e90a8SMaximilian Luz 				  struct module *owner)
350eb0e90a8SMaximilian Luz {
351eb0e90a8SMaximilian Luz 	sdrv->driver.owner = owner;
352eb0e90a8SMaximilian Luz 	sdrv->driver.bus = &ssam_bus_type;
353eb0e90a8SMaximilian Luz 
354eb0e90a8SMaximilian Luz 	/* force drivers to async probe so I/O is possible in probe */
355eb0e90a8SMaximilian Luz 	sdrv->driver.probe_type = PROBE_PREFER_ASYNCHRONOUS;
356eb0e90a8SMaximilian Luz 
357eb0e90a8SMaximilian Luz 	return driver_register(&sdrv->driver);
358eb0e90a8SMaximilian Luz }
359eb0e90a8SMaximilian Luz EXPORT_SYMBOL_GPL(__ssam_device_driver_register);
360eb0e90a8SMaximilian Luz 
361eb0e90a8SMaximilian Luz /**
362eb0e90a8SMaximilian Luz  * ssam_device_driver_unregister - Unregister a SSAM device driver.
363eb0e90a8SMaximilian Luz  * @sdrv: The driver to unregister.
364eb0e90a8SMaximilian Luz  */
365eb0e90a8SMaximilian Luz void ssam_device_driver_unregister(struct ssam_device_driver *sdrv)
366eb0e90a8SMaximilian Luz {
367eb0e90a8SMaximilian Luz 	driver_unregister(&sdrv->driver);
368eb0e90a8SMaximilian Luz }
369eb0e90a8SMaximilian Luz EXPORT_SYMBOL_GPL(ssam_device_driver_unregister);
370eb0e90a8SMaximilian Luz 
371*4a4ab610SMaximilian Luz 
372*4a4ab610SMaximilian Luz /* -- Bus registration. ----------------------------------------------------- */
373*4a4ab610SMaximilian Luz 
374*4a4ab610SMaximilian Luz /**
375*4a4ab610SMaximilian Luz  * ssam_bus_register() - Register and set-up the SSAM client device bus.
376*4a4ab610SMaximilian Luz  */
377*4a4ab610SMaximilian Luz int ssam_bus_register(void)
378*4a4ab610SMaximilian Luz {
379*4a4ab610SMaximilian Luz 	return bus_register(&ssam_bus_type);
380*4a4ab610SMaximilian Luz }
381*4a4ab610SMaximilian Luz 
382*4a4ab610SMaximilian Luz /**
383*4a4ab610SMaximilian Luz  * ssam_bus_unregister() - Unregister the SSAM client device bus.
384*4a4ab610SMaximilian Luz  */
385*4a4ab610SMaximilian Luz void ssam_bus_unregister(void)
386*4a4ab610SMaximilian Luz {
387*4a4ab610SMaximilian Luz 	return bus_unregister(&ssam_bus_type);
388*4a4ab610SMaximilian Luz }
389*4a4ab610SMaximilian Luz 
390*4a4ab610SMaximilian Luz 
391*4a4ab610SMaximilian Luz /* -- Helpers for controller and hub devices. ------------------------------- */
392*4a4ab610SMaximilian Luz 
393*4a4ab610SMaximilian Luz static int ssam_device_uid_from_string(const char *str, struct ssam_device_uid *uid)
394*4a4ab610SMaximilian Luz {
395*4a4ab610SMaximilian Luz 	u8 d, tc, tid, iid, fn;
396*4a4ab610SMaximilian Luz 	int n;
397*4a4ab610SMaximilian Luz 
398*4a4ab610SMaximilian Luz 	n = sscanf(str, "%hhx:%hhx:%hhx:%hhx:%hhx", &d, &tc, &tid, &iid, &fn);
399*4a4ab610SMaximilian Luz 	if (n != 5)
400*4a4ab610SMaximilian Luz 		return -EINVAL;
401*4a4ab610SMaximilian Luz 
402*4a4ab610SMaximilian Luz 	uid->domain = d;
403*4a4ab610SMaximilian Luz 	uid->category = tc;
404*4a4ab610SMaximilian Luz 	uid->target = tid;
405*4a4ab610SMaximilian Luz 	uid->instance = iid;
406*4a4ab610SMaximilian Luz 	uid->function = fn;
407*4a4ab610SMaximilian Luz 
408*4a4ab610SMaximilian Luz 	return 0;
409*4a4ab610SMaximilian Luz }
410*4a4ab610SMaximilian Luz 
411*4a4ab610SMaximilian Luz static int ssam_get_uid_for_node(struct fwnode_handle *node, struct ssam_device_uid *uid)
412*4a4ab610SMaximilian Luz {
413*4a4ab610SMaximilian Luz 	const char *str = fwnode_get_name(node);
414*4a4ab610SMaximilian Luz 
415*4a4ab610SMaximilian Luz 	/*
416*4a4ab610SMaximilian Luz 	 * To simplify definitions of firmware nodes, we set the device name
417*4a4ab610SMaximilian Luz 	 * based on the UID of the device, prefixed with "ssam:".
418*4a4ab610SMaximilian Luz 	 */
419*4a4ab610SMaximilian Luz 	if (strncmp(str, "ssam:", strlen("ssam:")) != 0)
420*4a4ab610SMaximilian Luz 		return -ENODEV;
421*4a4ab610SMaximilian Luz 
422*4a4ab610SMaximilian Luz 	str += strlen("ssam:");
423*4a4ab610SMaximilian Luz 	return ssam_device_uid_from_string(str, uid);
424*4a4ab610SMaximilian Luz }
425*4a4ab610SMaximilian Luz 
426*4a4ab610SMaximilian Luz static int ssam_add_client_device(struct device *parent, struct ssam_controller *ctrl,
427*4a4ab610SMaximilian Luz 				  struct fwnode_handle *node)
428*4a4ab610SMaximilian Luz {
429*4a4ab610SMaximilian Luz 	struct ssam_device_uid uid;
430*4a4ab610SMaximilian Luz 	struct ssam_device *sdev;
431*4a4ab610SMaximilian Luz 	int status;
432*4a4ab610SMaximilian Luz 
433*4a4ab610SMaximilian Luz 	status = ssam_get_uid_for_node(node, &uid);
434*4a4ab610SMaximilian Luz 	if (status)
435*4a4ab610SMaximilian Luz 		return status;
436*4a4ab610SMaximilian Luz 
437*4a4ab610SMaximilian Luz 	sdev = ssam_device_alloc(ctrl, uid);
438*4a4ab610SMaximilian Luz 	if (!sdev)
439*4a4ab610SMaximilian Luz 		return -ENOMEM;
440*4a4ab610SMaximilian Luz 
441*4a4ab610SMaximilian Luz 	sdev->dev.parent = parent;
442*4a4ab610SMaximilian Luz 	sdev->dev.fwnode = fwnode_handle_get(node);
443*4a4ab610SMaximilian Luz 
444*4a4ab610SMaximilian Luz 	status = ssam_device_add(sdev);
445*4a4ab610SMaximilian Luz 	if (status)
446*4a4ab610SMaximilian Luz 		ssam_device_put(sdev);
447*4a4ab610SMaximilian Luz 
448*4a4ab610SMaximilian Luz 	return status;
449*4a4ab610SMaximilian Luz }
450*4a4ab610SMaximilian Luz 
451*4a4ab610SMaximilian Luz /**
452*4a4ab610SMaximilian Luz  * __ssam_register_clients() - Register client devices defined under the
453*4a4ab610SMaximilian Luz  * given firmware node as children of the given device.
454*4a4ab610SMaximilian Luz  * @parent: The parent device under which clients should be registered.
455*4a4ab610SMaximilian Luz  * @ctrl: The controller with which client should be registered.
456*4a4ab610SMaximilian Luz  * @node: The firmware node holding definitions of the devices to be added.
457*4a4ab610SMaximilian Luz  *
458*4a4ab610SMaximilian Luz  * Register all clients that have been defined as children of the given root
459*4a4ab610SMaximilian Luz  * firmware node as children of the given parent device. The respective child
460*4a4ab610SMaximilian Luz  * firmware nodes will be associated with the correspondingly created child
461*4a4ab610SMaximilian Luz  * devices.
462*4a4ab610SMaximilian Luz  *
463*4a4ab610SMaximilian Luz  * The given controller will be used to instantiate the new devices. See
464*4a4ab610SMaximilian Luz  * ssam_device_add() for details.
465*4a4ab610SMaximilian Luz  *
466*4a4ab610SMaximilian Luz  * Note that, generally, the use of either ssam_device_register_clients() or
467*4a4ab610SMaximilian Luz  * ssam_register_clients() should be preferred as they directly use the
468*4a4ab610SMaximilian Luz  * firmware node and/or controller associated with the given device. This
469*4a4ab610SMaximilian Luz  * function is only intended for use when different device specifications (e.g.
470*4a4ab610SMaximilian Luz  * ACPI and firmware nodes) need to be combined (as is done in the platform hub
471*4a4ab610SMaximilian Luz  * of the device registry).
472*4a4ab610SMaximilian Luz  *
473*4a4ab610SMaximilian Luz  * Return: Returns zero on success, nonzero on failure.
474*4a4ab610SMaximilian Luz  */
475*4a4ab610SMaximilian Luz int __ssam_register_clients(struct device *parent, struct ssam_controller *ctrl,
476*4a4ab610SMaximilian Luz 			    struct fwnode_handle *node)
477*4a4ab610SMaximilian Luz {
478*4a4ab610SMaximilian Luz 	struct fwnode_handle *child;
479*4a4ab610SMaximilian Luz 	int status;
480*4a4ab610SMaximilian Luz 
481*4a4ab610SMaximilian Luz 	fwnode_for_each_child_node(node, child) {
482*4a4ab610SMaximilian Luz 		/*
483*4a4ab610SMaximilian Luz 		 * Try to add the device specified in the firmware node. If
484*4a4ab610SMaximilian Luz 		 * this fails with -ENODEV, the node does not specify any SSAM
485*4a4ab610SMaximilian Luz 		 * device, so ignore it and continue with the next one.
486*4a4ab610SMaximilian Luz 		 */
487*4a4ab610SMaximilian Luz 		status = ssam_add_client_device(parent, ctrl, child);
488*4a4ab610SMaximilian Luz 		if (status && status != -ENODEV)
489*4a4ab610SMaximilian Luz 			goto err;
490*4a4ab610SMaximilian Luz 	}
491*4a4ab610SMaximilian Luz 
492*4a4ab610SMaximilian Luz 	return 0;
493*4a4ab610SMaximilian Luz err:
494*4a4ab610SMaximilian Luz 	ssam_remove_clients(parent);
495*4a4ab610SMaximilian Luz 	return status;
496*4a4ab610SMaximilian Luz }
497*4a4ab610SMaximilian Luz EXPORT_SYMBOL_GPL(__ssam_register_clients);
498*4a4ab610SMaximilian Luz 
499eb0e90a8SMaximilian Luz static int ssam_remove_device(struct device *dev, void *_data)
500eb0e90a8SMaximilian Luz {
501eb0e90a8SMaximilian Luz 	struct ssam_device *sdev = to_ssam_device(dev);
502eb0e90a8SMaximilian Luz 
503eb0e90a8SMaximilian Luz 	if (is_ssam_device(dev))
504eb0e90a8SMaximilian Luz 		ssam_device_remove(sdev);
505eb0e90a8SMaximilian Luz 
506eb0e90a8SMaximilian Luz 	return 0;
507eb0e90a8SMaximilian Luz }
508eb0e90a8SMaximilian Luz 
509eb0e90a8SMaximilian Luz /**
51038543b72SMaximilian Luz  * ssam_remove_clients() - Remove SSAM client devices registered as direct
51138543b72SMaximilian Luz  * children under the given parent device.
51238543b72SMaximilian Luz  * @dev: The (parent) device to remove all direct clients for.
513eb0e90a8SMaximilian Luz  *
51438543b72SMaximilian Luz  * Remove all SSAM client devices registered as direct children under the given
51538543b72SMaximilian Luz  * device. Note that this only accounts for direct children of the device.
51638543b72SMaximilian Luz  * Refer to ssam_device_add()/ssam_device_remove() for more details.
517eb0e90a8SMaximilian Luz  */
51838543b72SMaximilian Luz void ssam_remove_clients(struct device *dev)
519eb0e90a8SMaximilian Luz {
520eb0e90a8SMaximilian Luz 	device_for_each_child_reverse(dev, NULL, ssam_remove_device);
521eb0e90a8SMaximilian Luz }
52238543b72SMaximilian Luz EXPORT_SYMBOL_GPL(ssam_remove_clients);
523