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