summaryrefslogtreecommitdiffstats
path: root/drivers/base/devcon.c
blob: d427e806cd73bf4f5cd6284a6e5126139087a2e3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
// 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);