Branch data Line data Source code
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 : :
12 : : static DEFINE_MUTEX(devcon_lock);
13 : : static LIST_HEAD(devcon_list);
14 : :
15 : : static void *
16 : 0 : fwnode_graph_devcon_match(struct fwnode_handle *fwnode, const char *con_id,
17 : : void *data, devcon_match_fn_t match)
18 : : {
19 : 0 : struct device_connection con = { .id = con_id };
20 : : struct fwnode_handle *ep;
21 : : void *ret;
22 : :
23 [ # # ]: 0 : fwnode_graph_for_each_endpoint(fwnode, ep) {
24 : 0 : con.fwnode = fwnode_graph_get_remote_port_parent(ep);
25 [ # # ]: 0 : if (!fwnode_device_is_available(con.fwnode))
26 : 0 : continue;
27 : :
28 : 0 : ret = match(&con, -1, data);
29 : 0 : fwnode_handle_put(con.fwnode);
30 [ # # ]: 0 : if (ret) {
31 : 0 : fwnode_handle_put(ep);
32 : 0 : return ret;
33 : : }
34 : : }
35 : : return NULL;
36 : : }
37 : :
38 : : static void *
39 : 0 : fwnode_devcon_match(struct fwnode_handle *fwnode, const char *con_id,
40 : : void *data, devcon_match_fn_t match)
41 : : {
42 : 0 : struct device_connection con = { };
43 : : void *ret;
44 : : int i;
45 : :
46 : 0 : for (i = 0; ; i++) {
47 : 0 : con.fwnode = fwnode_find_reference(fwnode, con_id, i);
48 [ # # ]: 0 : if (IS_ERR(con.fwnode))
49 : : break;
50 : :
51 : 0 : ret = match(&con, -1, data);
52 : 0 : fwnode_handle_put(con.fwnode);
53 [ # # ]: 0 : if (ret)
54 : 0 : return ret;
55 : 0 : }
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 : : */
71 : 0 : void *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 [ # # ]: 0 : if (!fwnode || !match)
78 : : return NULL;
79 : :
80 : 0 : ret = fwnode_graph_devcon_match(fwnode, con_id, data, match);
81 [ # # ]: 0 : if (ret)
82 : : return ret;
83 : :
84 : 0 : return fwnode_devcon_match(fwnode, con_id, data, match);
85 : : }
86 : : EXPORT_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 : : */
99 : 0 : void *device_connection_find_match(struct device *dev, const char *con_id,
100 : : void *data, devcon_match_fn_t match)
101 : : {
102 : 0 : 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 [ # # ]: 0 : if (!match)
109 : : return NULL;
110 : :
111 : 0 : ret = fwnode_connection_find_match(fwnode, con_id, data, match);
112 [ # # ]: 0 : if (ret)
113 : : return ret;
114 : :
115 : 0 : mutex_lock(&devcon_lock);
116 : :
117 [ # # ]: 0 : list_for_each_entry(con, &devcon_list, list) {
118 : 0 : ep = match_string(con->endpoint, 2, devname);
119 [ # # ]: 0 : if (ep < 0)
120 : 0 : continue;
121 : :
122 [ # # # # ]: 0 : if (con_id && strcmp(con->id, con_id))
123 : 0 : continue;
124 : :
125 : 0 : ret = match(con, !ep, data);
126 [ # # ]: 0 : if (ret)
127 : : break;
128 : : }
129 : :
130 : 0 : mutex_unlock(&devcon_lock);
131 : :
132 : 0 : return ret;
133 : : }
134 : : EXPORT_SYMBOL_GPL(device_connection_find_match);
135 : :
136 : : extern struct bus_type platform_bus_type;
137 : : extern struct bus_type pci_bus_type;
138 : : extern struct bus_type i2c_bus_type;
139 : : extern struct bus_type spi_bus_type;
140 : :
141 : : static 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 : :
155 : 0 : static void *device_connection_fwnode_match(struct device_connection *con)
156 : : {
157 : : struct bus_type *bus;
158 : : struct device *dev;
159 : :
160 [ # # ]: 0 : for (bus = generic_match_buses[0]; bus; bus++) {
161 : 0 : dev = bus_find_device_by_fwnode(bus, con->fwnode);
162 [ # # # # ]: 0 : if (dev && !strncmp(dev_name(dev), con->id, strlen(con->id)))
163 : 0 : return dev;
164 : :
165 : 0 : put_device(dev);
166 : : }
167 : : return NULL;
168 : : }
169 : :
170 : : /* This tries to find the device from the most common bus types by name. */
171 : 0 : static void *generic_match(struct device_connection *con, int ep, void *data)
172 : : {
173 : : struct bus_type *bus;
174 : : struct device *dev;
175 : :
176 [ # # ]: 0 : if (con->fwnode)
177 : 0 : return device_connection_fwnode_match(con);
178 : :
179 [ # # ]: 0 : for (bus = generic_match_buses[0]; bus; bus++) {
180 : 0 : dev = bus_find_device_by_name(bus, NULL, con->endpoint[ep]);
181 [ # # ]: 0 : if (dev)
182 : 0 : 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 : : */
203 : 0 : struct device *device_connection_find(struct device *dev, const char *con_id)
204 : : {
205 : 0 : return device_connection_find_match(dev, con_id, NULL, generic_match);
206 : : }
207 : : EXPORT_SYMBOL_GPL(device_connection_find);
208 : :
209 : : /**
210 : : * device_connection_add - Register a connection description
211 : : * @con: The connection description to be registered
212 : : */
213 : 0 : void device_connection_add(struct device_connection *con)
214 : : {
215 : 0 : mutex_lock(&devcon_lock);
216 : 0 : list_add_tail(&con->list, &devcon_list);
217 : 0 : mutex_unlock(&devcon_lock);
218 : 0 : }
219 : : EXPORT_SYMBOL_GPL(device_connection_add);
220 : :
221 : : /**
222 : : * device_connections_remove - Unregister connection description
223 : : * @con: The connection description to be unregistered
224 : : */
225 : 0 : void device_connection_remove(struct device_connection *con)
226 : : {
227 : 0 : mutex_lock(&devcon_lock);
228 : : list_del(&con->list);
229 : 0 : mutex_unlock(&devcon_lock);
230 : 0 : }
231 : : EXPORT_SYMBOL_GPL(device_connection_remove);
|