at v5.8 6.0 kB view raw
1// SPDX-License-Identifier: GPL-2.0 2/** 3 * Device connections 4 * 5 * Copyright (C) 2018 Intel Corporation 6 * Author: Heikki Krogerus <heikki.krogerus@linux.intel.com> 7 */ 8 9#include <linux/device.h> 10#include <linux/property.h> 11 12static DEFINE_MUTEX(devcon_lock); 13static LIST_HEAD(devcon_list); 14 15static void * 16fwnode_graph_devcon_match(struct fwnode_handle *fwnode, const char *con_id, 17 void *data, devcon_match_fn_t match) 18{ 19 struct device_connection con = { .id = con_id }; 20 struct fwnode_handle *ep; 21 void *ret; 22 23 fwnode_graph_for_each_endpoint(fwnode, ep) { 24 con.fwnode = fwnode_graph_get_remote_port_parent(ep); 25 if (!fwnode_device_is_available(con.fwnode)) 26 continue; 27 28 ret = match(&con, -1, data); 29 fwnode_handle_put(con.fwnode); 30 if (ret) { 31 fwnode_handle_put(ep); 32 return ret; 33 } 34 } 35 return NULL; 36} 37 38static void * 39fwnode_devcon_match(struct fwnode_handle *fwnode, const char *con_id, 40 void *data, devcon_match_fn_t match) 41{ 42 struct device_connection con = { }; 43 void *ret; 44 int i; 45 46 for (i = 0; ; i++) { 47 con.fwnode = fwnode_find_reference(fwnode, con_id, i); 48 if (IS_ERR(con.fwnode)) 49 break; 50 51 ret = match(&con, -1, data); 52 fwnode_handle_put(con.fwnode); 53 if (ret) 54 return ret; 55 } 56 57 return NULL; 58} 59 60/** 61 * fwnode_connection_find_match - Find connection from a device node 62 * @fwnode: Device node with the connection 63 * @con_id: Identifier for the connection 64 * @data: Data for the match function 65 * @match: Function to check and convert the connection description 66 * 67 * Find a connection with unique identifier @con_id between @fwnode and another 68 * device node. @match will be used to convert the connection description to 69 * data the caller is expecting to be returned. 70 */ 71void *fwnode_connection_find_match(struct fwnode_handle *fwnode, 72 const char *con_id, void *data, 73 devcon_match_fn_t match) 74{ 75 void *ret; 76 77 if (!fwnode || !match) 78 return NULL; 79 80 ret = fwnode_graph_devcon_match(fwnode, con_id, data, match); 81 if (ret) 82 return ret; 83 84 return fwnode_devcon_match(fwnode, con_id, data, match); 85} 86EXPORT_SYMBOL_GPL(fwnode_connection_find_match); 87 88/** 89 * device_connection_find_match - Find physical connection to a device 90 * @dev: Device with the connection 91 * @con_id: Identifier for the connection 92 * @data: Data for the match function 93 * @match: Function to check and convert the connection description 94 * 95 * Find a connection with unique identifier @con_id between @dev and another 96 * device. @match will be used to convert the connection description to data the 97 * caller is expecting to be returned. 98 */ 99void *device_connection_find_match(struct device *dev, const char *con_id, 100 void *data, devcon_match_fn_t match) 101{ 102 struct fwnode_handle *fwnode = dev_fwnode(dev); 103 const char *devname = dev_name(dev); 104 struct device_connection *con; 105 void *ret = NULL; 106 int ep; 107 108 if (!match) 109 return NULL; 110 111 ret = fwnode_connection_find_match(fwnode, con_id, data, match); 112 if (ret) 113 return ret; 114 115 mutex_lock(&devcon_lock); 116 117 list_for_each_entry(con, &devcon_list, list) { 118 ep = match_string(con->endpoint, 2, devname); 119 if (ep < 0) 120 continue; 121 122 if (con_id && strcmp(con->id, con_id)) 123 continue; 124 125 ret = match(con, !ep, data); 126 if (ret) 127 break; 128 } 129 130 mutex_unlock(&devcon_lock); 131 132 return ret; 133} 134EXPORT_SYMBOL_GPL(device_connection_find_match); 135 136extern struct bus_type platform_bus_type; 137extern struct bus_type pci_bus_type; 138extern struct bus_type i2c_bus_type; 139extern struct bus_type spi_bus_type; 140 141static struct bus_type *generic_match_buses[] = { 142 &platform_bus_type, 143#ifdef CONFIG_PCI 144 &pci_bus_type, 145#endif 146#ifdef CONFIG_I2C 147 &i2c_bus_type, 148#endif 149#ifdef CONFIG_SPI_MASTER 150 &spi_bus_type, 151#endif 152 NULL, 153}; 154 155static void *device_connection_fwnode_match(struct device_connection *con) 156{ 157 struct bus_type *bus; 158 struct device *dev; 159 160 for (bus = generic_match_buses[0]; bus; bus++) { 161 dev = bus_find_device_by_fwnode(bus, con->fwnode); 162 if (dev && !strncmp(dev_name(dev), con->id, strlen(con->id))) 163 return dev; 164 165 put_device(dev); 166 } 167 return NULL; 168} 169 170/* This tries to find the device from the most common bus types by name. */ 171static void *generic_match(struct device_connection *con, int ep, void *data) 172{ 173 struct bus_type *bus; 174 struct device *dev; 175 176 if (con->fwnode) 177 return device_connection_fwnode_match(con); 178 179 for (bus = generic_match_buses[0]; bus; bus++) { 180 dev = bus_find_device_by_name(bus, NULL, con->endpoint[ep]); 181 if (dev) 182 return dev; 183 } 184 185 /* 186 * We only get called if a connection was found, tell the caller to 187 * wait for the other device to show up. 188 */ 189 return ERR_PTR(-EPROBE_DEFER); 190} 191 192/** 193 * device_connection_find - Find two devices connected together 194 * @dev: Device with the connection 195 * @con_id: Identifier for the connection 196 * 197 * Find a connection with unique identifier @con_id between @dev and 198 * another device. On success returns handle to the device that is connected 199 * to @dev, with the reference count for the found device incremented. Returns 200 * NULL if no matching connection was found, or ERR_PTR(-EPROBE_DEFER) when a 201 * connection was found but the other device has not been enumerated yet. 202 */ 203struct device *device_connection_find(struct device *dev, const char *con_id) 204{ 205 return device_connection_find_match(dev, con_id, NULL, generic_match); 206} 207EXPORT_SYMBOL_GPL(device_connection_find); 208 209/** 210 * device_connection_add - Register a connection description 211 * @con: The connection description to be registered 212 */ 213void device_connection_add(struct device_connection *con) 214{ 215 mutex_lock(&devcon_lock); 216 list_add_tail(&con->list, &devcon_list); 217 mutex_unlock(&devcon_lock); 218} 219EXPORT_SYMBOL_GPL(device_connection_add); 220 221/** 222 * device_connections_remove - Unregister connection description 223 * @con: The connection description to be unregistered 224 */ 225void device_connection_remove(struct device_connection *con) 226{ 227 mutex_lock(&devcon_lock); 228 list_del(&con->list); 229 mutex_unlock(&devcon_lock); 230} 231EXPORT_SYMBOL_GPL(device_connection_remove);