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