2018-03-20 20:57:02 +08:00
|
|
|
// SPDX-License-Identifier: GPL-2.0
|
|
|
|
/**
|
|
|
|
* Device connections
|
|
|
|
*
|
|
|
|
* Copyright (C) 2018 Intel Corporation
|
|
|
|
* Author: Heikki Krogerus <heikki.krogerus@linux.intel.com>
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/device.h>
|
2019-02-13 15:45:57 +08:00
|
|
|
#include <linux/property.h>
|
2018-03-20 20:57:02 +08:00
|
|
|
|
|
|
|
static DEFINE_MUTEX(devcon_lock);
|
|
|
|
static LIST_HEAD(devcon_list);
|
|
|
|
|
2019-02-13 15:45:57 +08:00
|
|
|
typedef void *(*devcon_match_fn_t)(struct device_connection *con, int ep,
|
|
|
|
void *data);
|
|
|
|
|
|
|
|
static void *
|
|
|
|
fwnode_graph_devcon_match(struct fwnode_handle *fwnode, const char *con_id,
|
|
|
|
void *data, devcon_match_fn_t match)
|
|
|
|
{
|
|
|
|
struct device_connection con = { .id = con_id };
|
|
|
|
struct fwnode_handle *ep;
|
|
|
|
void *ret;
|
|
|
|
|
|
|
|
fwnode_graph_for_each_endpoint(fwnode, ep) {
|
|
|
|
con.fwnode = fwnode_graph_get_remote_port_parent(ep);
|
|
|
|
if (!fwnode_device_is_available(con.fwnode))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
ret = match(&con, -1, data);
|
|
|
|
fwnode_handle_put(con.fwnode);
|
|
|
|
if (ret) {
|
|
|
|
fwnode_handle_put(ep);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2019-05-31 22:15:40 +08:00
|
|
|
static void *
|
|
|
|
fwnode_devcon_match(struct fwnode_handle *fwnode, const char *con_id,
|
|
|
|
void *data, devcon_match_fn_t match)
|
|
|
|
{
|
|
|
|
struct device_connection con = { };
|
|
|
|
void *ret;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; ; i++) {
|
|
|
|
con.fwnode = fwnode_find_reference(fwnode, con_id, i);
|
|
|
|
if (IS_ERR(con.fwnode))
|
|
|
|
break;
|
|
|
|
|
|
|
|
ret = match(&con, -1, data);
|
|
|
|
fwnode_handle_put(con.fwnode);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2018-03-20 20:57:02 +08:00
|
|
|
/**
|
|
|
|
* device_connection_find_match - Find physical connection to a device
|
|
|
|
* @dev: Device with the connection
|
|
|
|
* @con_id: Identifier for the connection
|
|
|
|
* @data: Data for the match function
|
|
|
|
* @match: Function to check and convert the connection description
|
|
|
|
*
|
|
|
|
* Find a connection with unique identifier @con_id between @dev and another
|
|
|
|
* device. @match will be used to convert the connection description to data the
|
|
|
|
* caller is expecting to be returned.
|
|
|
|
*/
|
|
|
|
void *device_connection_find_match(struct device *dev, const char *con_id,
|
2019-02-13 15:45:57 +08:00
|
|
|
void *data, devcon_match_fn_t match)
|
2018-03-20 20:57:02 +08:00
|
|
|
{
|
2019-02-13 15:45:57 +08:00
|
|
|
struct fwnode_handle *fwnode = dev_fwnode(dev);
|
2018-03-20 20:57:02 +08:00
|
|
|
const char *devname = dev_name(dev);
|
|
|
|
struct device_connection *con;
|
|
|
|
void *ret = NULL;
|
|
|
|
int ep;
|
|
|
|
|
|
|
|
if (!match)
|
|
|
|
return NULL;
|
|
|
|
|
2019-02-13 15:45:57 +08:00
|
|
|
if (fwnode) {
|
|
|
|
ret = fwnode_graph_devcon_match(fwnode, con_id, data, match);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
2019-05-31 22:15:40 +08:00
|
|
|
|
|
|
|
ret = fwnode_devcon_match(fwnode, con_id, data, match);
|
|
|
|
if (ret)
|
|
|
|
return ret;
|
2019-02-13 15:45:57 +08:00
|
|
|
}
|
|
|
|
|
2018-03-20 20:57:02 +08:00
|
|
|
mutex_lock(&devcon_lock);
|
|
|
|
|
|
|
|
list_for_each_entry(con, &devcon_list, list) {
|
|
|
|
ep = match_string(con->endpoint, 2, devname);
|
|
|
|
if (ep < 0)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if (con_id && strcmp(con->id, con_id))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
ret = match(con, !ep, data);
|
|
|
|
if (ret)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
mutex_unlock(&devcon_lock);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(device_connection_find_match);
|
|
|
|
|
|
|
|
extern struct bus_type platform_bus_type;
|
|
|
|
extern struct bus_type pci_bus_type;
|
|
|
|
extern struct bus_type i2c_bus_type;
|
|
|
|
extern struct bus_type spi_bus_type;
|
|
|
|
|
|
|
|
static struct bus_type *generic_match_buses[] = {
|
|
|
|
&platform_bus_type,
|
|
|
|
#ifdef CONFIG_PCI
|
|
|
|
&pci_bus_type,
|
|
|
|
#endif
|
|
|
|
#ifdef CONFIG_I2C
|
|
|
|
&i2c_bus_type,
|
|
|
|
#endif
|
|
|
|
#ifdef CONFIG_SPI_MASTER
|
|
|
|
&spi_bus_type,
|
|
|
|
#endif
|
|
|
|
NULL,
|
|
|
|
};
|
|
|
|
|
2019-06-15 01:53:59 +08:00
|
|
|
static int device_fwnode_match(struct device *dev, const void *fwnode)
|
2019-02-13 15:45:56 +08:00
|
|
|
{
|
|
|
|
return dev_fwnode(dev) == fwnode;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void *device_connection_fwnode_match(struct device_connection *con)
|
|
|
|
{
|
|
|
|
struct bus_type *bus;
|
|
|
|
struct device *dev;
|
|
|
|
|
|
|
|
for (bus = generic_match_buses[0]; bus; bus++) {
|
|
|
|
dev = bus_find_device(bus, NULL, (void *)con->fwnode,
|
|
|
|
device_fwnode_match);
|
|
|
|
if (dev && !strncmp(dev_name(dev), con->id, strlen(con->id)))
|
|
|
|
return dev;
|
|
|
|
|
|
|
|
put_device(dev);
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2018-03-20 20:57:02 +08:00
|
|
|
/* This tries to find the device from the most common bus types by name. */
|
|
|
|
static void *generic_match(struct device_connection *con, int ep, void *data)
|
|
|
|
{
|
|
|
|
struct bus_type *bus;
|
|
|
|
struct device *dev;
|
|
|
|
|
2019-02-13 15:45:56 +08:00
|
|
|
if (con->fwnode)
|
|
|
|
return device_connection_fwnode_match(con);
|
|
|
|
|
2018-03-20 20:57:02 +08:00
|
|
|
for (bus = generic_match_buses[0]; bus; bus++) {
|
|
|
|
dev = bus_find_device_by_name(bus, NULL, con->endpoint[ep]);
|
|
|
|
if (dev)
|
|
|
|
return dev;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We only get called if a connection was found, tell the caller to
|
|
|
|
* wait for the other device to show up.
|
|
|
|
*/
|
|
|
|
return ERR_PTR(-EPROBE_DEFER);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* device_connection_find - Find two devices connected together
|
|
|
|
* @dev: Device with the connection
|
|
|
|
* @con_id: Identifier for the connection
|
|
|
|
*
|
|
|
|
* Find a connection with unique identifier @con_id between @dev and
|
|
|
|
* another device. On success returns handle to the device that is connected
|
|
|
|
* to @dev, with the reference count for the found device incremented. Returns
|
|
|
|
* NULL if no matching connection was found, or ERR_PTR(-EPROBE_DEFER) when a
|
|
|
|
* connection was found but the other device has not been enumerated yet.
|
|
|
|
*/
|
|
|
|
struct device *device_connection_find(struct device *dev, const char *con_id)
|
|
|
|
{
|
|
|
|
return device_connection_find_match(dev, con_id, NULL, generic_match);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(device_connection_find);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* device_connection_add - Register a connection description
|
|
|
|
* @con: The connection description to be registered
|
|
|
|
*/
|
|
|
|
void device_connection_add(struct device_connection *con)
|
|
|
|
{
|
|
|
|
mutex_lock(&devcon_lock);
|
|
|
|
list_add_tail(&con->list, &devcon_list);
|
|
|
|
mutex_unlock(&devcon_lock);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(device_connection_add);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* device_connections_remove - Unregister connection description
|
|
|
|
* @con: The connection description to be unregistered
|
|
|
|
*/
|
|
|
|
void device_connection_remove(struct device_connection *con)
|
|
|
|
{
|
|
|
|
mutex_lock(&devcon_lock);
|
|
|
|
list_del(&con->list);
|
|
|
|
mutex_unlock(&devcon_lock);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(device_connection_remove);
|