Merge tag 'nfs-for-5.7-1' of git://git.linux-nfs.org/projects/trondmy/linux-nfs
[platform/kernel/linux-starfive.git] / drivers / base / devcon.c
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 fwnode_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
38 static void *
39 fwnode_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  */
71 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         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 }
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 void *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 }
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 static 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. */
171 static 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  */
203 struct 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 }
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 void 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 }
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 void device_connection_remove(struct device_connection *con)
226 {
227         mutex_lock(&devcon_lock);
228         list_del(&con->list);
229         mutex_unlock(&devcon_lock);
230 }
231 EXPORT_SYMBOL_GPL(device_connection_remove);