alistair23-linux/drivers/base/devcon.c
Heikki Krogerus f2d9b66d84 drivers: base: Unified device connection lookup
Several frameworks - clk, gpio, phy, pmw, etc. - maintain
lookup tables for describing connections and provide custom
API for handling them. This introduces a single generic
lookup table and API for the connections.

The motivation for this commit is centralizing the
connection lookup, but the goal is to ultimately extract the
connection descriptions also from firmware by using the
fwnode_graph_* functions and other mechanisms that are
available.

Reviewed-by: Hans de Goede <hdegoede@redhat.com>
Reviewed-by: Andy Shevchenko <andy.shevchenko@gmail.com>
Signed-off-by: Heikki Krogerus <heikki.krogerus@linux.intel.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2018-03-22 13:10:29 +01:00

137 lines
3.5 KiB
C

// 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>
static DEFINE_MUTEX(devcon_lock);
static LIST_HEAD(devcon_list);
/**
* 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,
void *data,
void *(*match)(struct device_connection *con,
int ep, void *data))
{
const char *devname = dev_name(dev);
struct device_connection *con;
void *ret = NULL;
int ep;
if (!match)
return NULL;
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,
};
/* 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;
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);