forked from mirrors/linux
		
	 637e9e52b1
			
		
	
	
		637e9e52b1
		
	
	
	
	
		
			
			If connections between devices are described in OF graph or ACPI device graph, we can find them by using the fwnode_graph_*() functions. Acked-by: Hans de Goede <hdegoede@redhat.com> Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com> Reviewed-by: Jun Li <jun.li@nxp.com> Tested-by: Jun Li <jun.li@nxp.com> Signed-off-by: Heikki Krogerus <heikki.krogerus@linux.intel.com> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
		
			
				
	
	
		
			192 lines
		
	
	
	
		
			4.8 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			192 lines
		
	
	
	
		
			4.8 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // SPDX-License-Identifier: GPL-2.0
 | |
| /**
 | |
|  * Device connections
 | |
|  *
 | |
|  * Copyright (C) 2018 Intel Corporation
 | |
|  * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com>
 | |
|  */
 | |
| 
 | |
| #include <linux/device.h>
 | |
| #include <linux/property.h>
 | |
| 
 | |
| static DEFINE_MUTEX(devcon_lock);
 | |
| static LIST_HEAD(devcon_list);
 | |
| 
 | |
| typedef void *(*devcon_match_fn_t)(struct device_connection *con, int ep,
 | |
| 				   void *data);
 | |
| 
 | |
| static void *
 | |
| fwnode_graph_devcon_match(struct fwnode_handle *fwnode, const char *con_id,
 | |
| 			  void *data, devcon_match_fn_t match)
 | |
| {
 | |
| 	struct device_connection con = { .id = con_id };
 | |
| 	struct fwnode_handle *ep;
 | |
| 	void *ret;
 | |
| 
 | |
| 	fwnode_graph_for_each_endpoint(fwnode, ep) {
 | |
| 		con.fwnode = fwnode_graph_get_remote_port_parent(ep);
 | |
| 		if (!fwnode_device_is_available(con.fwnode))
 | |
| 			continue;
 | |
| 
 | |
| 		ret = match(&con, -1, data);
 | |
| 		fwnode_handle_put(con.fwnode);
 | |
| 		if (ret) {
 | |
| 			fwnode_handle_put(ep);
 | |
| 			return ret;
 | |
| 		}
 | |
| 	}
 | |
| 	return NULL;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * device_connection_find_match - Find physical connection to a device
 | |
|  * @dev: Device with the connection
 | |
|  * @con_id: Identifier for the connection
 | |
|  * @data: Data for the match function
 | |
|  * @match: Function to check and convert the connection description
 | |
|  *
 | |
|  * Find a connection with unique identifier @con_id between @dev and another
 | |
|  * device. @match will be used to convert the connection description to data the
 | |
|  * caller is expecting to be returned.
 | |
|  */
 | |
| void *device_connection_find_match(struct device *dev, const char *con_id,
 | |
| 				   void *data, devcon_match_fn_t match)
 | |
| {
 | |
| 	struct fwnode_handle *fwnode = dev_fwnode(dev);
 | |
| 	const char *devname = dev_name(dev);
 | |
| 	struct device_connection *con;
 | |
| 	void *ret = NULL;
 | |
| 	int ep;
 | |
| 
 | |
| 	if (!match)
 | |
| 		return NULL;
 | |
| 
 | |
| 	if (fwnode) {
 | |
| 		ret = fwnode_graph_devcon_match(fwnode, con_id, data, match);
 | |
| 		if (ret)
 | |
| 			return ret;
 | |
| 	}
 | |
| 
 | |
| 	mutex_lock(&devcon_lock);
 | |
| 
 | |
| 	list_for_each_entry(con, &devcon_list, list) {
 | |
| 		ep = match_string(con->endpoint, 2, devname);
 | |
| 		if (ep < 0)
 | |
| 			continue;
 | |
| 
 | |
| 		if (con_id && strcmp(con->id, con_id))
 | |
| 			continue;
 | |
| 
 | |
| 		ret = match(con, !ep, data);
 | |
| 		if (ret)
 | |
| 			break;
 | |
| 	}
 | |
| 
 | |
| 	mutex_unlock(&devcon_lock);
 | |
| 
 | |
| 	return ret;
 | |
| }
 | |
| EXPORT_SYMBOL_GPL(device_connection_find_match);
 | |
| 
 | |
| extern struct bus_type platform_bus_type;
 | |
| extern struct bus_type pci_bus_type;
 | |
| extern struct bus_type i2c_bus_type;
 | |
| extern struct bus_type spi_bus_type;
 | |
| 
 | |
| static struct bus_type *generic_match_buses[] = {
 | |
| 	&platform_bus_type,
 | |
| #ifdef CONFIG_PCI
 | |
| 	&pci_bus_type,
 | |
| #endif
 | |
| #ifdef CONFIG_I2C
 | |
| 	&i2c_bus_type,
 | |
| #endif
 | |
| #ifdef CONFIG_SPI_MASTER
 | |
| 	&spi_bus_type,
 | |
| #endif
 | |
| 	NULL,
 | |
| };
 | |
| 
 | |
| static int device_fwnode_match(struct device *dev, void *fwnode)
 | |
| {
 | |
| 	return dev_fwnode(dev) == fwnode;
 | |
| }
 | |
| 
 | |
| static void *device_connection_fwnode_match(struct device_connection *con)
 | |
| {
 | |
| 	struct bus_type *bus;
 | |
| 	struct device *dev;
 | |
| 
 | |
| 	for (bus = generic_match_buses[0]; bus; bus++) {
 | |
| 		dev = bus_find_device(bus, NULL, (void *)con->fwnode,
 | |
| 				      device_fwnode_match);
 | |
| 		if (dev && !strncmp(dev_name(dev), con->id, strlen(con->id)))
 | |
| 			return dev;
 | |
| 
 | |
| 		put_device(dev);
 | |
| 	}
 | |
| 	return NULL;
 | |
| }
 | |
| 
 | |
| /* This tries to find the device from the most common bus types by name. */
 | |
| static void *generic_match(struct device_connection *con, int ep, void *data)
 | |
| {
 | |
| 	struct bus_type *bus;
 | |
| 	struct device *dev;
 | |
| 
 | |
| 	if (con->fwnode)
 | |
| 		return device_connection_fwnode_match(con);
 | |
| 
 | |
| 	for (bus = generic_match_buses[0]; bus; bus++) {
 | |
| 		dev = bus_find_device_by_name(bus, NULL, con->endpoint[ep]);
 | |
| 		if (dev)
 | |
| 			return dev;
 | |
| 	}
 | |
| 
 | |
| 	/*
 | |
| 	 * We only get called if a connection was found, tell the caller to
 | |
| 	 * wait for the other device to show up.
 | |
| 	 */
 | |
| 	return ERR_PTR(-EPROBE_DEFER);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * device_connection_find - Find two devices connected together
 | |
|  * @dev: Device with the connection
 | |
|  * @con_id: Identifier for the connection
 | |
|  *
 | |
|  * Find a connection with unique identifier @con_id between @dev and
 | |
|  * another device. On success returns handle to the device that is connected
 | |
|  * to @dev, with the reference count for the found device incremented. Returns
 | |
|  * NULL if no matching connection was found, or ERR_PTR(-EPROBE_DEFER) when a
 | |
|  * connection was found but the other device has not been enumerated yet.
 | |
|  */
 | |
| struct device *device_connection_find(struct device *dev, const char *con_id)
 | |
| {
 | |
| 	return device_connection_find_match(dev, con_id, NULL, generic_match);
 | |
| }
 | |
| EXPORT_SYMBOL_GPL(device_connection_find);
 | |
| 
 | |
| /**
 | |
|  * device_connection_add - Register a connection description
 | |
|  * @con: The connection description to be registered
 | |
|  */
 | |
| void device_connection_add(struct device_connection *con)
 | |
| {
 | |
| 	mutex_lock(&devcon_lock);
 | |
| 	list_add_tail(&con->list, &devcon_list);
 | |
| 	mutex_unlock(&devcon_lock);
 | |
| }
 | |
| EXPORT_SYMBOL_GPL(device_connection_add);
 | |
| 
 | |
| /**
 | |
|  * device_connections_remove - Unregister connection description
 | |
|  * @con: The connection description to be unregistered
 | |
|  */
 | |
| void device_connection_remove(struct device_connection *con)
 | |
| {
 | |
| 	mutex_lock(&devcon_lock);
 | |
| 	list_del(&con->list);
 | |
| 	mutex_unlock(&devcon_lock);
 | |
| }
 | |
| EXPORT_SYMBOL_GPL(device_connection_remove);
 |