137 lines
3.5 KiB
C
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);
|