162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0+
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Surface System Aggregator Module bus and device integration.
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2019-2022 Maximilian Luz <luzmaximilian@gmail.com>
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/device.h>
962306a36Sopenharmony_ci#include <linux/property.h>
1062306a36Sopenharmony_ci#include <linux/slab.h>
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci#include <linux/surface_aggregator/controller.h>
1362306a36Sopenharmony_ci#include <linux/surface_aggregator/device.h>
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci#include "bus.h"
1662306a36Sopenharmony_ci#include "controller.h"
1762306a36Sopenharmony_ci
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ci/* -- Device and bus functions. --------------------------------------------- */
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_cistatic ssize_t modalias_show(struct device *dev, struct device_attribute *attr,
2262306a36Sopenharmony_ci			     char *buf)
2362306a36Sopenharmony_ci{
2462306a36Sopenharmony_ci	struct ssam_device *sdev = to_ssam_device(dev);
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci	return sysfs_emit(buf, "ssam:d%02Xc%02Xt%02Xi%02Xf%02X\n",
2762306a36Sopenharmony_ci			sdev->uid.domain, sdev->uid.category, sdev->uid.target,
2862306a36Sopenharmony_ci			sdev->uid.instance, sdev->uid.function);
2962306a36Sopenharmony_ci}
3062306a36Sopenharmony_cistatic DEVICE_ATTR_RO(modalias);
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_cistatic struct attribute *ssam_device_attrs[] = {
3362306a36Sopenharmony_ci	&dev_attr_modalias.attr,
3462306a36Sopenharmony_ci	NULL,
3562306a36Sopenharmony_ci};
3662306a36Sopenharmony_ciATTRIBUTE_GROUPS(ssam_device);
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_cistatic int ssam_device_uevent(const struct device *dev, struct kobj_uevent_env *env)
3962306a36Sopenharmony_ci{
4062306a36Sopenharmony_ci	const struct ssam_device *sdev = to_ssam_device(dev);
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_ci	return add_uevent_var(env, "MODALIAS=ssam:d%02Xc%02Xt%02Xi%02Xf%02X",
4362306a36Sopenharmony_ci			      sdev->uid.domain, sdev->uid.category,
4462306a36Sopenharmony_ci			      sdev->uid.target, sdev->uid.instance,
4562306a36Sopenharmony_ci			      sdev->uid.function);
4662306a36Sopenharmony_ci}
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_cistatic void ssam_device_release(struct device *dev)
4962306a36Sopenharmony_ci{
5062306a36Sopenharmony_ci	struct ssam_device *sdev = to_ssam_device(dev);
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci	ssam_controller_put(sdev->ctrl);
5362306a36Sopenharmony_ci	fwnode_handle_put(sdev->dev.fwnode);
5462306a36Sopenharmony_ci	kfree(sdev);
5562306a36Sopenharmony_ci}
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_ciconst struct device_type ssam_device_type = {
5862306a36Sopenharmony_ci	.name    = "surface_aggregator_device",
5962306a36Sopenharmony_ci	.groups  = ssam_device_groups,
6062306a36Sopenharmony_ci	.uevent  = ssam_device_uevent,
6162306a36Sopenharmony_ci	.release = ssam_device_release,
6262306a36Sopenharmony_ci};
6362306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(ssam_device_type);
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci/**
6662306a36Sopenharmony_ci * ssam_device_alloc() - Allocate and initialize a SSAM client device.
6762306a36Sopenharmony_ci * @ctrl: The controller under which the device should be added.
6862306a36Sopenharmony_ci * @uid:  The UID of the device to be added.
6962306a36Sopenharmony_ci *
7062306a36Sopenharmony_ci * Allocates and initializes a new client device. The parent of the device
7162306a36Sopenharmony_ci * will be set to the controller device and the name will be set based on the
7262306a36Sopenharmony_ci * UID. Note that the device still has to be added via ssam_device_add().
7362306a36Sopenharmony_ci * Refer to that function for more details.
7462306a36Sopenharmony_ci *
7562306a36Sopenharmony_ci * Return: Returns the newly allocated and initialized SSAM client device, or
7662306a36Sopenharmony_ci * %NULL if it could not be allocated.
7762306a36Sopenharmony_ci */
7862306a36Sopenharmony_cistruct ssam_device *ssam_device_alloc(struct ssam_controller *ctrl,
7962306a36Sopenharmony_ci				      struct ssam_device_uid uid)
8062306a36Sopenharmony_ci{
8162306a36Sopenharmony_ci	struct ssam_device *sdev;
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci	sdev = kzalloc(sizeof(*sdev), GFP_KERNEL);
8462306a36Sopenharmony_ci	if (!sdev)
8562306a36Sopenharmony_ci		return NULL;
8662306a36Sopenharmony_ci
8762306a36Sopenharmony_ci	device_initialize(&sdev->dev);
8862306a36Sopenharmony_ci	sdev->dev.bus = &ssam_bus_type;
8962306a36Sopenharmony_ci	sdev->dev.type = &ssam_device_type;
9062306a36Sopenharmony_ci	sdev->dev.parent = ssam_controller_device(ctrl);
9162306a36Sopenharmony_ci	sdev->ctrl = ssam_controller_get(ctrl);
9262306a36Sopenharmony_ci	sdev->uid = uid;
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci	dev_set_name(&sdev->dev, "%02x:%02x:%02x:%02x:%02x",
9562306a36Sopenharmony_ci		     sdev->uid.domain, sdev->uid.category, sdev->uid.target,
9662306a36Sopenharmony_ci		     sdev->uid.instance, sdev->uid.function);
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci	return sdev;
9962306a36Sopenharmony_ci}
10062306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(ssam_device_alloc);
10162306a36Sopenharmony_ci
10262306a36Sopenharmony_ci/**
10362306a36Sopenharmony_ci * ssam_device_add() - Add a SSAM client device.
10462306a36Sopenharmony_ci * @sdev: The SSAM client device to be added.
10562306a36Sopenharmony_ci *
10662306a36Sopenharmony_ci * Added client devices must be guaranteed to always have a valid and active
10762306a36Sopenharmony_ci * controller. Thus, this function will fail with %-ENODEV if the controller
10862306a36Sopenharmony_ci * of the device has not been initialized yet, has been suspended, or has been
10962306a36Sopenharmony_ci * shut down.
11062306a36Sopenharmony_ci *
11162306a36Sopenharmony_ci * The caller of this function should ensure that the corresponding call to
11262306a36Sopenharmony_ci * ssam_device_remove() is issued before the controller is shut down. If the
11362306a36Sopenharmony_ci * added device is a direct child of the controller device (default), it will
11462306a36Sopenharmony_ci * be automatically removed when the controller is shut down.
11562306a36Sopenharmony_ci *
11662306a36Sopenharmony_ci * By default, the controller device will become the parent of the newly
11762306a36Sopenharmony_ci * created client device. The parent may be changed before ssam_device_add is
11862306a36Sopenharmony_ci * called, but care must be taken that a) the correct suspend/resume ordering
11962306a36Sopenharmony_ci * is guaranteed and b) the client device does not outlive the controller,
12062306a36Sopenharmony_ci * i.e. that the device is removed before the controller is being shut down.
12162306a36Sopenharmony_ci * In case these guarantees have to be manually enforced, please refer to the
12262306a36Sopenharmony_ci * ssam_client_link() and ssam_client_bind() functions, which are intended to
12362306a36Sopenharmony_ci * set up device-links for this purpose.
12462306a36Sopenharmony_ci *
12562306a36Sopenharmony_ci * Return: Returns zero on success, a negative error code on failure.
12662306a36Sopenharmony_ci */
12762306a36Sopenharmony_ciint ssam_device_add(struct ssam_device *sdev)
12862306a36Sopenharmony_ci{
12962306a36Sopenharmony_ci	int status;
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ci	/*
13262306a36Sopenharmony_ci	 * Ensure that we can only add new devices to a controller if it has
13362306a36Sopenharmony_ci	 * been started and is not going away soon. This works in combination
13462306a36Sopenharmony_ci	 * with ssam_controller_remove_clients to ensure driver presence for the
13562306a36Sopenharmony_ci	 * controller device, i.e. it ensures that the controller (sdev->ctrl)
13662306a36Sopenharmony_ci	 * is always valid and can be used for requests as long as the client
13762306a36Sopenharmony_ci	 * device we add here is registered as child under it. This essentially
13862306a36Sopenharmony_ci	 * guarantees that the client driver can always expect the preconditions
13962306a36Sopenharmony_ci	 * for functions like ssam_request_do_sync() (controller has to be
14062306a36Sopenharmony_ci	 * started and is not suspended) to hold and thus does not have to check
14162306a36Sopenharmony_ci	 * for them.
14262306a36Sopenharmony_ci	 *
14362306a36Sopenharmony_ci	 * Note that for this to work, the controller has to be a parent device.
14462306a36Sopenharmony_ci	 * If it is not a direct parent, care has to be taken that the device is
14562306a36Sopenharmony_ci	 * removed via ssam_device_remove(), as device_unregister does not
14662306a36Sopenharmony_ci	 * remove child devices recursively.
14762306a36Sopenharmony_ci	 */
14862306a36Sopenharmony_ci	ssam_controller_statelock(sdev->ctrl);
14962306a36Sopenharmony_ci
15062306a36Sopenharmony_ci	if (sdev->ctrl->state != SSAM_CONTROLLER_STARTED) {
15162306a36Sopenharmony_ci		ssam_controller_stateunlock(sdev->ctrl);
15262306a36Sopenharmony_ci		return -ENODEV;
15362306a36Sopenharmony_ci	}
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci	status = device_add(&sdev->dev);
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_ci	ssam_controller_stateunlock(sdev->ctrl);
15862306a36Sopenharmony_ci	return status;
15962306a36Sopenharmony_ci}
16062306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(ssam_device_add);
16162306a36Sopenharmony_ci
16262306a36Sopenharmony_ci/**
16362306a36Sopenharmony_ci * ssam_device_remove() - Remove a SSAM client device.
16462306a36Sopenharmony_ci * @sdev: The device to remove.
16562306a36Sopenharmony_ci *
16662306a36Sopenharmony_ci * Removes and unregisters the provided SSAM client device.
16762306a36Sopenharmony_ci */
16862306a36Sopenharmony_civoid ssam_device_remove(struct ssam_device *sdev)
16962306a36Sopenharmony_ci{
17062306a36Sopenharmony_ci	device_unregister(&sdev->dev);
17162306a36Sopenharmony_ci}
17262306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(ssam_device_remove);
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_ci/**
17562306a36Sopenharmony_ci * ssam_device_id_compatible() - Check if a device ID matches a UID.
17662306a36Sopenharmony_ci * @id:  The device ID as potential match.
17762306a36Sopenharmony_ci * @uid: The device UID matching against.
17862306a36Sopenharmony_ci *
17962306a36Sopenharmony_ci * Check if the given ID is a match for the given UID, i.e. if a device with
18062306a36Sopenharmony_ci * the provided UID is compatible to the given ID following the match rules
18162306a36Sopenharmony_ci * described in its &ssam_device_id.match_flags member.
18262306a36Sopenharmony_ci *
18362306a36Sopenharmony_ci * Return: Returns %true if the given UID is compatible to the match rule
18462306a36Sopenharmony_ci * described by the given ID, %false otherwise.
18562306a36Sopenharmony_ci */
18662306a36Sopenharmony_cistatic bool ssam_device_id_compatible(const struct ssam_device_id *id,
18762306a36Sopenharmony_ci				      struct ssam_device_uid uid)
18862306a36Sopenharmony_ci{
18962306a36Sopenharmony_ci	if (id->domain != uid.domain || id->category != uid.category)
19062306a36Sopenharmony_ci		return false;
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_ci	if ((id->match_flags & SSAM_MATCH_TARGET) && id->target != uid.target)
19362306a36Sopenharmony_ci		return false;
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_ci	if ((id->match_flags & SSAM_MATCH_INSTANCE) && id->instance != uid.instance)
19662306a36Sopenharmony_ci		return false;
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_ci	if ((id->match_flags & SSAM_MATCH_FUNCTION) && id->function != uid.function)
19962306a36Sopenharmony_ci		return false;
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_ci	return true;
20262306a36Sopenharmony_ci}
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_ci/**
20562306a36Sopenharmony_ci * ssam_device_id_is_null() - Check if a device ID is null.
20662306a36Sopenharmony_ci * @id: The device ID to check.
20762306a36Sopenharmony_ci *
20862306a36Sopenharmony_ci * Check if a given device ID is null, i.e. all zeros. Used to check for the
20962306a36Sopenharmony_ci * end of ``MODULE_DEVICE_TABLE(ssam, ...)`` or similar lists.
21062306a36Sopenharmony_ci *
21162306a36Sopenharmony_ci * Return: Returns %true if the given ID represents a null ID, %false
21262306a36Sopenharmony_ci * otherwise.
21362306a36Sopenharmony_ci */
21462306a36Sopenharmony_cistatic bool ssam_device_id_is_null(const struct ssam_device_id *id)
21562306a36Sopenharmony_ci{
21662306a36Sopenharmony_ci	return id->match_flags == 0 &&
21762306a36Sopenharmony_ci		id->domain == 0 &&
21862306a36Sopenharmony_ci		id->category == 0 &&
21962306a36Sopenharmony_ci		id->target == 0 &&
22062306a36Sopenharmony_ci		id->instance == 0 &&
22162306a36Sopenharmony_ci		id->function == 0 &&
22262306a36Sopenharmony_ci		id->driver_data == 0;
22362306a36Sopenharmony_ci}
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_ci/**
22662306a36Sopenharmony_ci * ssam_device_id_match() - Find the matching ID table entry for the given UID.
22762306a36Sopenharmony_ci * @table: The table to search in.
22862306a36Sopenharmony_ci * @uid:   The UID to matched against the individual table entries.
22962306a36Sopenharmony_ci *
23062306a36Sopenharmony_ci * Find the first match for the provided device UID in the provided ID table
23162306a36Sopenharmony_ci * and return it. Returns %NULL if no match could be found.
23262306a36Sopenharmony_ci */
23362306a36Sopenharmony_ciconst struct ssam_device_id *ssam_device_id_match(const struct ssam_device_id *table,
23462306a36Sopenharmony_ci						  const struct ssam_device_uid uid)
23562306a36Sopenharmony_ci{
23662306a36Sopenharmony_ci	const struct ssam_device_id *id;
23762306a36Sopenharmony_ci
23862306a36Sopenharmony_ci	for (id = table; !ssam_device_id_is_null(id); ++id)
23962306a36Sopenharmony_ci		if (ssam_device_id_compatible(id, uid))
24062306a36Sopenharmony_ci			return id;
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_ci	return NULL;
24362306a36Sopenharmony_ci}
24462306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(ssam_device_id_match);
24562306a36Sopenharmony_ci
24662306a36Sopenharmony_ci/**
24762306a36Sopenharmony_ci * ssam_device_get_match() - Find and return the ID matching the device in the
24862306a36Sopenharmony_ci * ID table of the bound driver.
24962306a36Sopenharmony_ci * @dev: The device for which to get the matching ID table entry.
25062306a36Sopenharmony_ci *
25162306a36Sopenharmony_ci * Find the fist match for the UID of the device in the ID table of the
25262306a36Sopenharmony_ci * currently bound driver and return it. Returns %NULL if the device does not
25362306a36Sopenharmony_ci * have a driver bound to it, the driver does not have match_table (i.e. it is
25462306a36Sopenharmony_ci * %NULL), or there is no match in the driver's match_table.
25562306a36Sopenharmony_ci *
25662306a36Sopenharmony_ci * This function essentially calls ssam_device_id_match() with the ID table of
25762306a36Sopenharmony_ci * the bound device driver and the UID of the device.
25862306a36Sopenharmony_ci *
25962306a36Sopenharmony_ci * Return: Returns the first match for the UID of the device in the device
26062306a36Sopenharmony_ci * driver's match table, or %NULL if no such match could be found.
26162306a36Sopenharmony_ci */
26262306a36Sopenharmony_ciconst struct ssam_device_id *ssam_device_get_match(const struct ssam_device *dev)
26362306a36Sopenharmony_ci{
26462306a36Sopenharmony_ci	const struct ssam_device_driver *sdrv;
26562306a36Sopenharmony_ci
26662306a36Sopenharmony_ci	sdrv = to_ssam_device_driver(dev->dev.driver);
26762306a36Sopenharmony_ci	if (!sdrv)
26862306a36Sopenharmony_ci		return NULL;
26962306a36Sopenharmony_ci
27062306a36Sopenharmony_ci	if (!sdrv->match_table)
27162306a36Sopenharmony_ci		return NULL;
27262306a36Sopenharmony_ci
27362306a36Sopenharmony_ci	return ssam_device_id_match(sdrv->match_table, dev->uid);
27462306a36Sopenharmony_ci}
27562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(ssam_device_get_match);
27662306a36Sopenharmony_ci
27762306a36Sopenharmony_ci/**
27862306a36Sopenharmony_ci * ssam_device_get_match_data() - Find the ID matching the device in the
27962306a36Sopenharmony_ci * ID table of the bound driver and return its ``driver_data`` member.
28062306a36Sopenharmony_ci * @dev: The device for which to get the match data.
28162306a36Sopenharmony_ci *
28262306a36Sopenharmony_ci * Find the fist match for the UID of the device in the ID table of the
28362306a36Sopenharmony_ci * corresponding driver and return its driver_data. Returns %NULL if the
28462306a36Sopenharmony_ci * device does not have a driver bound to it, the driver does not have
28562306a36Sopenharmony_ci * match_table (i.e. it is %NULL), there is no match in the driver's
28662306a36Sopenharmony_ci * match_table, or the match does not have any driver_data.
28762306a36Sopenharmony_ci *
28862306a36Sopenharmony_ci * This function essentially calls ssam_device_get_match() and, if any match
28962306a36Sopenharmony_ci * could be found, returns its ``struct ssam_device_id.driver_data`` member.
29062306a36Sopenharmony_ci *
29162306a36Sopenharmony_ci * Return: Returns the driver data associated with the first match for the UID
29262306a36Sopenharmony_ci * of the device in the device driver's match table, or %NULL if no such match
29362306a36Sopenharmony_ci * could be found.
29462306a36Sopenharmony_ci */
29562306a36Sopenharmony_ciconst void *ssam_device_get_match_data(const struct ssam_device *dev)
29662306a36Sopenharmony_ci{
29762306a36Sopenharmony_ci	const struct ssam_device_id *id;
29862306a36Sopenharmony_ci
29962306a36Sopenharmony_ci	id = ssam_device_get_match(dev);
30062306a36Sopenharmony_ci	if (!id)
30162306a36Sopenharmony_ci		return NULL;
30262306a36Sopenharmony_ci
30362306a36Sopenharmony_ci	return (const void *)id->driver_data;
30462306a36Sopenharmony_ci}
30562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(ssam_device_get_match_data);
30662306a36Sopenharmony_ci
30762306a36Sopenharmony_cistatic int ssam_bus_match(struct device *dev, struct device_driver *drv)
30862306a36Sopenharmony_ci{
30962306a36Sopenharmony_ci	struct ssam_device_driver *sdrv = to_ssam_device_driver(drv);
31062306a36Sopenharmony_ci	struct ssam_device *sdev = to_ssam_device(dev);
31162306a36Sopenharmony_ci
31262306a36Sopenharmony_ci	if (!is_ssam_device(dev))
31362306a36Sopenharmony_ci		return 0;
31462306a36Sopenharmony_ci
31562306a36Sopenharmony_ci	return !!ssam_device_id_match(sdrv->match_table, sdev->uid);
31662306a36Sopenharmony_ci}
31762306a36Sopenharmony_ci
31862306a36Sopenharmony_cistatic int ssam_bus_probe(struct device *dev)
31962306a36Sopenharmony_ci{
32062306a36Sopenharmony_ci	return to_ssam_device_driver(dev->driver)
32162306a36Sopenharmony_ci		->probe(to_ssam_device(dev));
32262306a36Sopenharmony_ci}
32362306a36Sopenharmony_ci
32462306a36Sopenharmony_cistatic void ssam_bus_remove(struct device *dev)
32562306a36Sopenharmony_ci{
32662306a36Sopenharmony_ci	struct ssam_device_driver *sdrv = to_ssam_device_driver(dev->driver);
32762306a36Sopenharmony_ci
32862306a36Sopenharmony_ci	if (sdrv->remove)
32962306a36Sopenharmony_ci		sdrv->remove(to_ssam_device(dev));
33062306a36Sopenharmony_ci}
33162306a36Sopenharmony_ci
33262306a36Sopenharmony_cistruct bus_type ssam_bus_type = {
33362306a36Sopenharmony_ci	.name   = "surface_aggregator",
33462306a36Sopenharmony_ci	.match  = ssam_bus_match,
33562306a36Sopenharmony_ci	.probe  = ssam_bus_probe,
33662306a36Sopenharmony_ci	.remove = ssam_bus_remove,
33762306a36Sopenharmony_ci};
33862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(ssam_bus_type);
33962306a36Sopenharmony_ci
34062306a36Sopenharmony_ci/**
34162306a36Sopenharmony_ci * __ssam_device_driver_register() - Register a SSAM client device driver.
34262306a36Sopenharmony_ci * @sdrv:  The driver to register.
34362306a36Sopenharmony_ci * @owner: The module owning the provided driver.
34462306a36Sopenharmony_ci *
34562306a36Sopenharmony_ci * Please refer to the ssam_device_driver_register() macro for the normal way
34662306a36Sopenharmony_ci * to register a driver from inside its owning module.
34762306a36Sopenharmony_ci */
34862306a36Sopenharmony_ciint __ssam_device_driver_register(struct ssam_device_driver *sdrv,
34962306a36Sopenharmony_ci				  struct module *owner)
35062306a36Sopenharmony_ci{
35162306a36Sopenharmony_ci	sdrv->driver.owner = owner;
35262306a36Sopenharmony_ci	sdrv->driver.bus = &ssam_bus_type;
35362306a36Sopenharmony_ci
35462306a36Sopenharmony_ci	/* force drivers to async probe so I/O is possible in probe */
35562306a36Sopenharmony_ci	sdrv->driver.probe_type = PROBE_PREFER_ASYNCHRONOUS;
35662306a36Sopenharmony_ci
35762306a36Sopenharmony_ci	return driver_register(&sdrv->driver);
35862306a36Sopenharmony_ci}
35962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(__ssam_device_driver_register);
36062306a36Sopenharmony_ci
36162306a36Sopenharmony_ci/**
36262306a36Sopenharmony_ci * ssam_device_driver_unregister - Unregister a SSAM device driver.
36362306a36Sopenharmony_ci * @sdrv: The driver to unregister.
36462306a36Sopenharmony_ci */
36562306a36Sopenharmony_civoid ssam_device_driver_unregister(struct ssam_device_driver *sdrv)
36662306a36Sopenharmony_ci{
36762306a36Sopenharmony_ci	driver_unregister(&sdrv->driver);
36862306a36Sopenharmony_ci}
36962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(ssam_device_driver_unregister);
37062306a36Sopenharmony_ci
37162306a36Sopenharmony_ci
37262306a36Sopenharmony_ci/* -- Bus registration. ----------------------------------------------------- */
37362306a36Sopenharmony_ci
37462306a36Sopenharmony_ci/**
37562306a36Sopenharmony_ci * ssam_bus_register() - Register and set-up the SSAM client device bus.
37662306a36Sopenharmony_ci */
37762306a36Sopenharmony_ciint ssam_bus_register(void)
37862306a36Sopenharmony_ci{
37962306a36Sopenharmony_ci	return bus_register(&ssam_bus_type);
38062306a36Sopenharmony_ci}
38162306a36Sopenharmony_ci
38262306a36Sopenharmony_ci/**
38362306a36Sopenharmony_ci * ssam_bus_unregister() - Unregister the SSAM client device bus.
38462306a36Sopenharmony_ci */
38562306a36Sopenharmony_civoid ssam_bus_unregister(void)
38662306a36Sopenharmony_ci{
38762306a36Sopenharmony_ci	return bus_unregister(&ssam_bus_type);
38862306a36Sopenharmony_ci}
38962306a36Sopenharmony_ci
39062306a36Sopenharmony_ci
39162306a36Sopenharmony_ci/* -- Helpers for controller and hub devices. ------------------------------- */
39262306a36Sopenharmony_ci
39362306a36Sopenharmony_cistatic int ssam_device_uid_from_string(const char *str, struct ssam_device_uid *uid)
39462306a36Sopenharmony_ci{
39562306a36Sopenharmony_ci	u8 d, tc, tid, iid, fn;
39662306a36Sopenharmony_ci	int n;
39762306a36Sopenharmony_ci
39862306a36Sopenharmony_ci	n = sscanf(str, "%hhx:%hhx:%hhx:%hhx:%hhx", &d, &tc, &tid, &iid, &fn);
39962306a36Sopenharmony_ci	if (n != 5)
40062306a36Sopenharmony_ci		return -EINVAL;
40162306a36Sopenharmony_ci
40262306a36Sopenharmony_ci	uid->domain = d;
40362306a36Sopenharmony_ci	uid->category = tc;
40462306a36Sopenharmony_ci	uid->target = tid;
40562306a36Sopenharmony_ci	uid->instance = iid;
40662306a36Sopenharmony_ci	uid->function = fn;
40762306a36Sopenharmony_ci
40862306a36Sopenharmony_ci	return 0;
40962306a36Sopenharmony_ci}
41062306a36Sopenharmony_ci
41162306a36Sopenharmony_cistatic int ssam_get_uid_for_node(struct fwnode_handle *node, struct ssam_device_uid *uid)
41262306a36Sopenharmony_ci{
41362306a36Sopenharmony_ci	const char *str = fwnode_get_name(node);
41462306a36Sopenharmony_ci
41562306a36Sopenharmony_ci	/*
41662306a36Sopenharmony_ci	 * To simplify definitions of firmware nodes, we set the device name
41762306a36Sopenharmony_ci	 * based on the UID of the device, prefixed with "ssam:".
41862306a36Sopenharmony_ci	 */
41962306a36Sopenharmony_ci	if (strncmp(str, "ssam:", strlen("ssam:")) != 0)
42062306a36Sopenharmony_ci		return -ENODEV;
42162306a36Sopenharmony_ci
42262306a36Sopenharmony_ci	str += strlen("ssam:");
42362306a36Sopenharmony_ci	return ssam_device_uid_from_string(str, uid);
42462306a36Sopenharmony_ci}
42562306a36Sopenharmony_ci
42662306a36Sopenharmony_cistatic int ssam_add_client_device(struct device *parent, struct ssam_controller *ctrl,
42762306a36Sopenharmony_ci				  struct fwnode_handle *node)
42862306a36Sopenharmony_ci{
42962306a36Sopenharmony_ci	struct ssam_device_uid uid;
43062306a36Sopenharmony_ci	struct ssam_device *sdev;
43162306a36Sopenharmony_ci	int status;
43262306a36Sopenharmony_ci
43362306a36Sopenharmony_ci	status = ssam_get_uid_for_node(node, &uid);
43462306a36Sopenharmony_ci	if (status)
43562306a36Sopenharmony_ci		return status;
43662306a36Sopenharmony_ci
43762306a36Sopenharmony_ci	sdev = ssam_device_alloc(ctrl, uid);
43862306a36Sopenharmony_ci	if (!sdev)
43962306a36Sopenharmony_ci		return -ENOMEM;
44062306a36Sopenharmony_ci
44162306a36Sopenharmony_ci	sdev->dev.parent = parent;
44262306a36Sopenharmony_ci	sdev->dev.fwnode = fwnode_handle_get(node);
44362306a36Sopenharmony_ci
44462306a36Sopenharmony_ci	status = ssam_device_add(sdev);
44562306a36Sopenharmony_ci	if (status)
44662306a36Sopenharmony_ci		ssam_device_put(sdev);
44762306a36Sopenharmony_ci
44862306a36Sopenharmony_ci	return status;
44962306a36Sopenharmony_ci}
45062306a36Sopenharmony_ci
45162306a36Sopenharmony_ci/**
45262306a36Sopenharmony_ci * __ssam_register_clients() - Register client devices defined under the
45362306a36Sopenharmony_ci * given firmware node as children of the given device.
45462306a36Sopenharmony_ci * @parent: The parent device under which clients should be registered.
45562306a36Sopenharmony_ci * @ctrl: The controller with which client should be registered.
45662306a36Sopenharmony_ci * @node: The firmware node holding definitions of the devices to be added.
45762306a36Sopenharmony_ci *
45862306a36Sopenharmony_ci * Register all clients that have been defined as children of the given root
45962306a36Sopenharmony_ci * firmware node as children of the given parent device. The respective child
46062306a36Sopenharmony_ci * firmware nodes will be associated with the correspondingly created child
46162306a36Sopenharmony_ci * devices.
46262306a36Sopenharmony_ci *
46362306a36Sopenharmony_ci * The given controller will be used to instantiate the new devices. See
46462306a36Sopenharmony_ci * ssam_device_add() for details.
46562306a36Sopenharmony_ci *
46662306a36Sopenharmony_ci * Note that, generally, the use of either ssam_device_register_clients() or
46762306a36Sopenharmony_ci * ssam_register_clients() should be preferred as they directly use the
46862306a36Sopenharmony_ci * firmware node and/or controller associated with the given device. This
46962306a36Sopenharmony_ci * function is only intended for use when different device specifications (e.g.
47062306a36Sopenharmony_ci * ACPI and firmware nodes) need to be combined (as is done in the platform hub
47162306a36Sopenharmony_ci * of the device registry).
47262306a36Sopenharmony_ci *
47362306a36Sopenharmony_ci * Return: Returns zero on success, nonzero on failure.
47462306a36Sopenharmony_ci */
47562306a36Sopenharmony_ciint __ssam_register_clients(struct device *parent, struct ssam_controller *ctrl,
47662306a36Sopenharmony_ci			    struct fwnode_handle *node)
47762306a36Sopenharmony_ci{
47862306a36Sopenharmony_ci	struct fwnode_handle *child;
47962306a36Sopenharmony_ci	int status;
48062306a36Sopenharmony_ci
48162306a36Sopenharmony_ci	fwnode_for_each_child_node(node, child) {
48262306a36Sopenharmony_ci		/*
48362306a36Sopenharmony_ci		 * Try to add the device specified in the firmware node. If
48462306a36Sopenharmony_ci		 * this fails with -ENODEV, the node does not specify any SSAM
48562306a36Sopenharmony_ci		 * device, so ignore it and continue with the next one.
48662306a36Sopenharmony_ci		 */
48762306a36Sopenharmony_ci		status = ssam_add_client_device(parent, ctrl, child);
48862306a36Sopenharmony_ci		if (status && status != -ENODEV) {
48962306a36Sopenharmony_ci			fwnode_handle_put(child);
49062306a36Sopenharmony_ci			goto err;
49162306a36Sopenharmony_ci		}
49262306a36Sopenharmony_ci	}
49362306a36Sopenharmony_ci
49462306a36Sopenharmony_ci	return 0;
49562306a36Sopenharmony_cierr:
49662306a36Sopenharmony_ci	ssam_remove_clients(parent);
49762306a36Sopenharmony_ci	return status;
49862306a36Sopenharmony_ci}
49962306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(__ssam_register_clients);
50062306a36Sopenharmony_ci
50162306a36Sopenharmony_cistatic int ssam_remove_device(struct device *dev, void *_data)
50262306a36Sopenharmony_ci{
50362306a36Sopenharmony_ci	struct ssam_device *sdev = to_ssam_device(dev);
50462306a36Sopenharmony_ci
50562306a36Sopenharmony_ci	if (is_ssam_device(dev))
50662306a36Sopenharmony_ci		ssam_device_remove(sdev);
50762306a36Sopenharmony_ci
50862306a36Sopenharmony_ci	return 0;
50962306a36Sopenharmony_ci}
51062306a36Sopenharmony_ci
51162306a36Sopenharmony_ci/**
51262306a36Sopenharmony_ci * ssam_remove_clients() - Remove SSAM client devices registered as direct
51362306a36Sopenharmony_ci * children under the given parent device.
51462306a36Sopenharmony_ci * @dev: The (parent) device to remove all direct clients for.
51562306a36Sopenharmony_ci *
51662306a36Sopenharmony_ci * Remove all SSAM client devices registered as direct children under the given
51762306a36Sopenharmony_ci * device. Note that this only accounts for direct children of the device.
51862306a36Sopenharmony_ci * Refer to ssam_device_add()/ssam_device_remove() for more details.
51962306a36Sopenharmony_ci */
52062306a36Sopenharmony_civoid ssam_remove_clients(struct device *dev)
52162306a36Sopenharmony_ci{
52262306a36Sopenharmony_ci	device_for_each_child_reverse(dev, NULL, ssam_remove_device);
52362306a36Sopenharmony_ci}
52462306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(ssam_remove_clients);
525