1eb0e90a8SMaximilian Luz // SPDX-License-Identifier: GPL-2.0+
2eb0e90a8SMaximilian Luz /*
3eb0e90a8SMaximilian Luz  * Surface System Aggregator Module bus and device integration.
4eb0e90a8SMaximilian Luz  *
5221756e6SMaximilian Luz  * Copyright (C) 2019-2022 Maximilian Luz <luzmaximilian@gmail.com>
6eb0e90a8SMaximilian Luz  */
7eb0e90a8SMaximilian Luz 
8eb0e90a8SMaximilian Luz #include <linux/device.h>
94a4ab610SMaximilian 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 
184a4ab610SMaximilian Luz 
194a4ab610SMaximilian Luz /* -- Device and bus functions. --------------------------------------------- */
204a4ab610SMaximilian Luz 
modalias_show(struct device * dev,struct device_attribute * attr,char * buf)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 
ssam_device_uevent(const struct device * dev,struct kobj_uevent_env * env)38162736b0SGreg Kroah-Hartman static int ssam_device_uevent(const struct device *dev, struct kobj_uevent_env *env)
39eb0e90a8SMaximilian Luz {
40162736b0SGreg Kroah-Hartman 	const 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 
ssam_device_release(struct device * dev)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);
534a4ab610SMaximilian 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  */
ssam_device_alloc(struct ssam_controller * ctrl,struct ssam_device_uid uid)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  */
ssam_device_add(struct ssam_device * sdev)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
139b09ee1cdSMaximilian Luz 	 * for functions like ssam_request_do_sync() (controller has to be
140b09ee1cdSMaximilian Luz 	 * started and is not suspended) to hold and thus does not have to check
141b09ee1cdSMaximilian Luz 	 * for 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  */
ssam_device_remove(struct ssam_device * sdev)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  */
ssam_device_id_compatible(const struct ssam_device_id * id,struct ssam_device_uid uid)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  */
ssam_device_id_is_null(const struct ssam_device_id * id)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  */
ssam_device_id_match(const struct ssam_device_id * table,const struct ssam_device_uid uid)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  */
ssam_device_get_match(const struct ssam_device * dev)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  */
ssam_device_get_match_data(const struct ssam_device * dev)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 
ssam_bus_match(struct device * dev,struct device_driver * drv)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 
ssam_bus_probe(struct device * dev)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 
ssam_bus_remove(struct device * dev)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  */
__ssam_device_driver_register(struct ssam_device_driver * sdrv,struct module * owner)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  */
ssam_device_driver_unregister(struct ssam_device_driver * sdrv)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 
3714a4ab610SMaximilian Luz 
3724a4ab610SMaximilian Luz /* -- Bus registration. ----------------------------------------------------- */
3734a4ab610SMaximilian Luz 
3744a4ab610SMaximilian Luz /**
3754a4ab610SMaximilian Luz  * ssam_bus_register() - Register and set-up the SSAM client device bus.
3764a4ab610SMaximilian Luz  */
ssam_bus_register(void)3774a4ab610SMaximilian Luz int ssam_bus_register(void)
3784a4ab610SMaximilian Luz {
3794a4ab610SMaximilian Luz 	return bus_register(&ssam_bus_type);
3804a4ab610SMaximilian Luz }
3814a4ab610SMaximilian Luz 
3824a4ab610SMaximilian Luz /**
3834a4ab610SMaximilian Luz  * ssam_bus_unregister() - Unregister the SSAM client device bus.
3844a4ab610SMaximilian Luz  */
ssam_bus_unregister(void)3854a4ab610SMaximilian Luz void ssam_bus_unregister(void)
3864a4ab610SMaximilian Luz {
3874a4ab610SMaximilian Luz 	return bus_unregister(&ssam_bus_type);
3884a4ab610SMaximilian Luz }
3894a4ab610SMaximilian Luz 
3904a4ab610SMaximilian Luz 
3914a4ab610SMaximilian Luz /* -- Helpers for controller and hub devices. ------------------------------- */
3924a4ab610SMaximilian Luz 
ssam_device_uid_from_string(const char * str,struct ssam_device_uid * uid)3934a4ab610SMaximilian Luz static int ssam_device_uid_from_string(const char *str, struct ssam_device_uid *uid)
3944a4ab610SMaximilian Luz {
3954a4ab610SMaximilian Luz 	u8 d, tc, tid, iid, fn;
3964a4ab610SMaximilian Luz 	int n;
3974a4ab610SMaximilian Luz 
3984a4ab610SMaximilian Luz 	n = sscanf(str, "%hhx:%hhx:%hhx:%hhx:%hhx", &d, &tc, &tid, &iid, &fn);
3994a4ab610SMaximilian Luz 	if (n != 5)
4004a4ab610SMaximilian Luz 		return -EINVAL;
4014a4ab610SMaximilian Luz 
4024a4ab610SMaximilian Luz 	uid->domain = d;
4034a4ab610SMaximilian Luz 	uid->category = tc;
4044a4ab610SMaximilian Luz 	uid->target = tid;
4054a4ab610SMaximilian Luz 	uid->instance = iid;
4064a4ab610SMaximilian Luz 	uid->function = fn;
4074a4ab610SMaximilian Luz 
4084a4ab610SMaximilian Luz 	return 0;
4094a4ab610SMaximilian Luz }
4104a4ab610SMaximilian Luz 
ssam_get_uid_for_node(struct fwnode_handle * node,struct ssam_device_uid * uid)4114a4ab610SMaximilian Luz static int ssam_get_uid_for_node(struct fwnode_handle *node, struct ssam_device_uid *uid)
4124a4ab610SMaximilian Luz {
4134a4ab610SMaximilian Luz 	const char *str = fwnode_get_name(node);
4144a4ab610SMaximilian Luz 
4154a4ab610SMaximilian Luz 	/*
4164a4ab610SMaximilian Luz 	 * To simplify definitions of firmware nodes, we set the device name
4174a4ab610SMaximilian Luz 	 * based on the UID of the device, prefixed with "ssam:".
4184a4ab610SMaximilian Luz 	 */
4194a4ab610SMaximilian Luz 	if (strncmp(str, "ssam:", strlen("ssam:")) != 0)
4204a4ab610SMaximilian Luz 		return -ENODEV;
4214a4ab610SMaximilian Luz 
4224a4ab610SMaximilian Luz 	str += strlen("ssam:");
4234a4ab610SMaximilian Luz 	return ssam_device_uid_from_string(str, uid);
4244a4ab610SMaximilian Luz }
4254a4ab610SMaximilian Luz 
ssam_add_client_device(struct device * parent,struct ssam_controller * ctrl,struct fwnode_handle * node)4264a4ab610SMaximilian Luz static int ssam_add_client_device(struct device *parent, struct ssam_controller *ctrl,
4274a4ab610SMaximilian Luz 				  struct fwnode_handle *node)
4284a4ab610SMaximilian Luz {
4294a4ab610SMaximilian Luz 	struct ssam_device_uid uid;
4304a4ab610SMaximilian Luz 	struct ssam_device *sdev;
4314a4ab610SMaximilian Luz 	int status;
4324a4ab610SMaximilian Luz 
4334a4ab610SMaximilian Luz 	status = ssam_get_uid_for_node(node, &uid);
4344a4ab610SMaximilian Luz 	if (status)
4354a4ab610SMaximilian Luz 		return status;
4364a4ab610SMaximilian Luz 
4374a4ab610SMaximilian Luz 	sdev = ssam_device_alloc(ctrl, uid);
4384a4ab610SMaximilian Luz 	if (!sdev)
4394a4ab610SMaximilian Luz 		return -ENOMEM;
4404a4ab610SMaximilian Luz 
4414a4ab610SMaximilian Luz 	sdev->dev.parent = parent;
4424a4ab610SMaximilian Luz 	sdev->dev.fwnode = fwnode_handle_get(node);
4434a4ab610SMaximilian Luz 
4444a4ab610SMaximilian Luz 	status = ssam_device_add(sdev);
4454a4ab610SMaximilian Luz 	if (status)
4464a4ab610SMaximilian Luz 		ssam_device_put(sdev);
4474a4ab610SMaximilian Luz 
4484a4ab610SMaximilian Luz 	return status;
4494a4ab610SMaximilian Luz }
4504a4ab610SMaximilian Luz 
4514a4ab610SMaximilian Luz /**
4524a4ab610SMaximilian Luz  * __ssam_register_clients() - Register client devices defined under the
4534a4ab610SMaximilian Luz  * given firmware node as children of the given device.
4544a4ab610SMaximilian Luz  * @parent: The parent device under which clients should be registered.
4554a4ab610SMaximilian Luz  * @ctrl: The controller with which client should be registered.
4564a4ab610SMaximilian Luz  * @node: The firmware node holding definitions of the devices to be added.
4574a4ab610SMaximilian Luz  *
4584a4ab610SMaximilian Luz  * Register all clients that have been defined as children of the given root
4594a4ab610SMaximilian Luz  * firmware node as children of the given parent device. The respective child
4604a4ab610SMaximilian Luz  * firmware nodes will be associated with the correspondingly created child
4614a4ab610SMaximilian Luz  * devices.
4624a4ab610SMaximilian Luz  *
4634a4ab610SMaximilian Luz  * The given controller will be used to instantiate the new devices. See
4644a4ab610SMaximilian Luz  * ssam_device_add() for details.
4654a4ab610SMaximilian Luz  *
4664a4ab610SMaximilian Luz  * Note that, generally, the use of either ssam_device_register_clients() or
4674a4ab610SMaximilian Luz  * ssam_register_clients() should be preferred as they directly use the
4684a4ab610SMaximilian Luz  * firmware node and/or controller associated with the given device. This
4694a4ab610SMaximilian Luz  * function is only intended for use when different device specifications (e.g.
4704a4ab610SMaximilian Luz  * ACPI and firmware nodes) need to be combined (as is done in the platform hub
4714a4ab610SMaximilian Luz  * of the device registry).
4724a4ab610SMaximilian Luz  *
4734a4ab610SMaximilian Luz  * Return: Returns zero on success, nonzero on failure.
4744a4ab610SMaximilian Luz  */
__ssam_register_clients(struct device * parent,struct ssam_controller * ctrl,struct fwnode_handle * node)4754a4ab610SMaximilian Luz int __ssam_register_clients(struct device *parent, struct ssam_controller *ctrl,
4764a4ab610SMaximilian Luz 			    struct fwnode_handle *node)
4774a4ab610SMaximilian Luz {
4784a4ab610SMaximilian Luz 	struct fwnode_handle *child;
4794a4ab610SMaximilian Luz 	int status;
4804a4ab610SMaximilian Luz 
4814a4ab610SMaximilian Luz 	fwnode_for_each_child_node(node, child) {
4824a4ab610SMaximilian Luz 		/*
4834a4ab610SMaximilian Luz 		 * Try to add the device specified in the firmware node. If
4844a4ab610SMaximilian Luz 		 * this fails with -ENODEV, the node does not specify any SSAM
4854a4ab610SMaximilian Luz 		 * device, so ignore it and continue with the next one.
4864a4ab610SMaximilian Luz 		 */
4874a4ab610SMaximilian Luz 		status = ssam_add_client_device(parent, ctrl, child);
488*acd0acb8SLiang He 		if (status && status != -ENODEV) {
489*acd0acb8SLiang He 			fwnode_handle_put(child);
4904a4ab610SMaximilian Luz 			goto err;
4914a4ab610SMaximilian Luz 		}
492*acd0acb8SLiang He 	}
4934a4ab610SMaximilian Luz 
4944a4ab610SMaximilian Luz 	return 0;
4954a4ab610SMaximilian Luz err:
4964a4ab610SMaximilian Luz 	ssam_remove_clients(parent);
4974a4ab610SMaximilian Luz 	return status;
4984a4ab610SMaximilian Luz }
4994a4ab610SMaximilian Luz EXPORT_SYMBOL_GPL(__ssam_register_clients);
5004a4ab610SMaximilian Luz 
ssam_remove_device(struct device * dev,void * _data)501eb0e90a8SMaximilian Luz static int ssam_remove_device(struct device *dev, void *_data)
502eb0e90a8SMaximilian Luz {
503eb0e90a8SMaximilian Luz 	struct ssam_device *sdev = to_ssam_device(dev);
504eb0e90a8SMaximilian Luz 
505eb0e90a8SMaximilian Luz 	if (is_ssam_device(dev))
506eb0e90a8SMaximilian Luz 		ssam_device_remove(sdev);
507eb0e90a8SMaximilian Luz 
508eb0e90a8SMaximilian Luz 	return 0;
509eb0e90a8SMaximilian Luz }
510eb0e90a8SMaximilian Luz 
511eb0e90a8SMaximilian Luz /**
51238543b72SMaximilian Luz  * ssam_remove_clients() - Remove SSAM client devices registered as direct
51338543b72SMaximilian Luz  * children under the given parent device.
51438543b72SMaximilian Luz  * @dev: The (parent) device to remove all direct clients for.
515eb0e90a8SMaximilian Luz  *
51638543b72SMaximilian Luz  * Remove all SSAM client devices registered as direct children under the given
51738543b72SMaximilian Luz  * device. Note that this only accounts for direct children of the device.
51838543b72SMaximilian Luz  * Refer to ssam_device_add()/ssam_device_remove() for more details.
519eb0e90a8SMaximilian Luz  */
ssam_remove_clients(struct device * dev)52038543b72SMaximilian Luz void ssam_remove_clients(struct device *dev)
521eb0e90a8SMaximilian Luz {
522eb0e90a8SMaximilian Luz 	device_for_each_child_reverse(dev, NULL, ssam_remove_device);
523eb0e90a8SMaximilian Luz }
52438543b72SMaximilian Luz EXPORT_SYMBOL_GPL(ssam_remove_clients);
525