| // 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); |
| |
| 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; |
| } |
| |
| static void * |
| fwnode_devcon_match(struct fwnode_handle *fwnode, const char *con_id, |
| void *data, devcon_match_fn_t match) |
| { |
| struct device_connection con = { }; |
| void *ret; |
| int i; |
| |
| for (i = 0; ; i++) { |
| con.fwnode = fwnode_find_reference(fwnode, con_id, i); |
| if (IS_ERR(con.fwnode)) |
| break; |
| |
| ret = match(&con, -1, data); |
| fwnode_handle_put(con.fwnode); |
| if (ret) |
| return ret; |
| } |
| |
| return NULL; |
| } |
| |
| /** |
| * fwnode_connection_find_match - Find connection from a device node |
| * @fwnode: Device node 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 @fwnode and another |
| * device node. @match will be used to convert the connection description to |
| * data the caller is expecting to be returned. |
| */ |
| void *fwnode_connection_find_match(struct fwnode_handle *fwnode, |
| const char *con_id, void *data, |
| devcon_match_fn_t match) |
| { |
| void *ret; |
| |
| if (!fwnode || !match) |
| return NULL; |
| |
| ret = fwnode_graph_devcon_match(fwnode, con_id, data, match); |
| if (ret) |
| return ret; |
| |
| return fwnode_devcon_match(fwnode, con_id, data, match); |
| } |
| EXPORT_SYMBOL_GPL(fwnode_connection_find_match); |
| |
| /** |
| * 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; |
| |
| ret = fwnode_connection_find_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 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_by_fwnode(bus, con->fwnode); |
| 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); |