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 <heikki.krogerus@linux.intel.com> | |
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 | ||
f2d9b66d HK |
41 | /** |
42 | * device_connection_find_match - Find physical connection to a device | |
43 | * @dev: Device with the connection | |
44 | * @con_id: Identifier for the connection | |
45 | * @data: Data for the match function | |
46 | * @match: Function to check and convert the connection description | |
47 | * | |
48 | * Find a connection with unique identifier @con_id between @dev and another | |
49 | * device. @match will be used to convert the connection description to data the | |
50 | * caller is expecting to be returned. | |
51 | */ | |
52 | void *device_connection_find_match(struct device *dev, const char *con_id, | |
637e9e52 | 53 | void *data, devcon_match_fn_t match) |
f2d9b66d | 54 | { |
637e9e52 | 55 | struct fwnode_handle *fwnode = dev_fwnode(dev); |
f2d9b66d HK |
56 | const char *devname = dev_name(dev); |
57 | struct device_connection *con; | |
58 | void *ret = NULL; | |
59 | int ep; | |
60 | ||
61 | if (!match) | |
62 | return NULL; | |
63 | ||
637e9e52 HK |
64 | if (fwnode) { |
65 | ret = fwnode_graph_devcon_match(fwnode, con_id, data, match); | |
66 | if (ret) | |
67 | return ret; | |
68 | } | |
69 | ||
f2d9b66d HK |
70 | mutex_lock(&devcon_lock); |
71 | ||
72 | list_for_each_entry(con, &devcon_list, list) { | |
73 | ep = match_string(con->endpoint, 2, devname); | |
74 | if (ep < 0) | |
75 | continue; | |
76 | ||
77 | if (con_id && strcmp(con->id, con_id)) | |
78 | continue; | |
79 | ||
80 | ret = match(con, !ep, data); | |
81 | if (ret) | |
82 | break; | |
83 | } | |
84 | ||
85 | mutex_unlock(&devcon_lock); | |
86 | ||
87 | return ret; | |
88 | } | |
89 | EXPORT_SYMBOL_GPL(device_connection_find_match); | |
90 | ||
91 | extern struct bus_type platform_bus_type; | |
92 | extern struct bus_type pci_bus_type; | |
93 | extern struct bus_type i2c_bus_type; | |
94 | extern struct bus_type spi_bus_type; | |
95 | ||
96 | static struct bus_type *generic_match_buses[] = { | |
97 | &platform_bus_type, | |
98 | #ifdef CONFIG_PCI | |
99 | &pci_bus_type, | |
100 | #endif | |
101 | #ifdef CONFIG_I2C | |
102 | &i2c_bus_type, | |
103 | #endif | |
104 | #ifdef CONFIG_SPI_MASTER | |
105 | &spi_bus_type, | |
106 | #endif | |
107 | NULL, | |
108 | }; | |
109 | ||
80e04837 HK |
110 | static int device_fwnode_match(struct device *dev, void *fwnode) |
111 | { | |
112 | return dev_fwnode(dev) == fwnode; | |
113 | } | |
114 | ||
115 | static void *device_connection_fwnode_match(struct device_connection *con) | |
116 | { | |
117 | struct bus_type *bus; | |
118 | struct device *dev; | |
119 | ||
120 | for (bus = generic_match_buses[0]; bus; bus++) { | |
121 | dev = bus_find_device(bus, NULL, (void *)con->fwnode, | |
122 | device_fwnode_match); | |
123 | if (dev && !strncmp(dev_name(dev), con->id, strlen(con->id))) | |
124 | return dev; | |
125 | ||
126 | put_device(dev); | |
127 | } | |
128 | return NULL; | |
129 | } | |
130 | ||
f2d9b66d HK |
131 | /* This tries to find the device from the most common bus types by name. */ |
132 | static void *generic_match(struct device_connection *con, int ep, void *data) | |
133 | { | |
134 | struct bus_type *bus; | |
135 | struct device *dev; | |
136 | ||
80e04837 HK |
137 | if (con->fwnode) |
138 | return device_connection_fwnode_match(con); | |
139 | ||
f2d9b66d HK |
140 | for (bus = generic_match_buses[0]; bus; bus++) { |
141 | dev = bus_find_device_by_name(bus, NULL, con->endpoint[ep]); | |
142 | if (dev) | |
143 | return dev; | |
144 | } | |
145 | ||
146 | /* | |
147 | * We only get called if a connection was found, tell the caller to | |
148 | * wait for the other device to show up. | |
149 | */ | |
150 | return ERR_PTR(-EPROBE_DEFER); | |
151 | } | |
152 | ||
153 | /** | |
154 | * device_connection_find - Find two devices connected together | |
155 | * @dev: Device with the connection | |
156 | * @con_id: Identifier for the connection | |
157 | * | |
158 | * Find a connection with unique identifier @con_id between @dev and | |
159 | * another device. On success returns handle to the device that is connected | |
160 | * to @dev, with the reference count for the found device incremented. Returns | |
161 | * NULL if no matching connection was found, or ERR_PTR(-EPROBE_DEFER) when a | |
162 | * connection was found but the other device has not been enumerated yet. | |
163 | */ | |
164 | struct device *device_connection_find(struct device *dev, const char *con_id) | |
165 | { | |
166 | return device_connection_find_match(dev, con_id, NULL, generic_match); | |
167 | } | |
168 | EXPORT_SYMBOL_GPL(device_connection_find); | |
169 | ||
170 | /** | |
171 | * device_connection_add - Register a connection description | |
172 | * @con: The connection description to be registered | |
173 | */ | |
174 | void device_connection_add(struct device_connection *con) | |
175 | { | |
176 | mutex_lock(&devcon_lock); | |
177 | list_add_tail(&con->list, &devcon_list); | |
178 | mutex_unlock(&devcon_lock); | |
179 | } | |
180 | EXPORT_SYMBOL_GPL(device_connection_add); | |
181 | ||
182 | /** | |
183 | * device_connections_remove - Unregister connection description | |
184 | * @con: The connection description to be unregistered | |
185 | */ | |
186 | void device_connection_remove(struct device_connection *con) | |
187 | { | |
188 | mutex_lock(&devcon_lock); | |
189 | list_del(&con->list); | |
190 | mutex_unlock(&devcon_lock); | |
191 | } | |
192 | EXPORT_SYMBOL_GPL(device_connection_remove); |