linux/drivers/base/devcon.c
<<
>>
Prefs
   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);
 232