drm_dp_iic_helper.c revision 249249
1/*
2 * Copyright �� 2009 Keith Packard
3 *
4 * Permission to use, copy, modify, distribute, and sell this software and its
5 * documentation for any purpose is hereby granted without fee, provided that
6 * the above copyright notice appear in all copies and that both that copyright
7 * notice and this permission notice appear in supporting documentation, and
8 * that the name of the copyright holders not be used in advertising or
9 * publicity pertaining to distribution of the software without specific,
10 * written prior permission.  The copyright holders make no representations
11 * about the suitability of this software for any purpose.  It is provided "as
12 * is" without express or implied warranty.
13 *
14 * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
15 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
16 * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
17 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
18 * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
19 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
20 * OF THIS SOFTWARE.
21 */
22
23#include <sys/cdefs.h>
24__FBSDID("$FreeBSD: head/sys/dev/drm2/drm_dp_iic_helper.c 249249 2013-04-08 08:37:57Z dumbbell $");
25
26#include <sys/types.h>
27#include <sys/kobj.h>
28#include <sys/bus.h>
29#include <dev/iicbus/iic.h>
30#include "iicbus_if.h"
31#include <dev/iicbus/iiconf.h>
32#include <dev/drm2/drmP.h>
33#include <dev/drm2/drm_dp_helper.h>
34
35static int
36iic_dp_aux_transaction(device_t idev, int mode, uint8_t write_byte,
37    uint8_t *read_byte)
38{
39	struct iic_dp_aux_data *aux_data;
40	int ret;
41
42	aux_data = device_get_softc(idev);
43	ret = (*aux_data->aux_ch)(idev, mode, write_byte, read_byte);
44	return (ret);
45}
46
47/*
48 * I2C over AUX CH
49 */
50
51/*
52 * Send the address. If the I2C link is running, this 'restarts'
53 * the connection with the new address, this is used for doing
54 * a write followed by a read (as needed for DDC)
55 */
56static int
57iic_dp_aux_address(device_t idev, u16 address, bool reading)
58{
59	struct iic_dp_aux_data *aux_data;
60	int mode, ret;
61
62	aux_data = device_get_softc(idev);
63	mode = MODE_I2C_START;
64	if (reading)
65		mode |= MODE_I2C_READ;
66	else
67		mode |= MODE_I2C_WRITE;
68	aux_data->address = address;
69	aux_data->running = true;
70	ret = iic_dp_aux_transaction(idev, mode, 0, NULL);
71	return (ret);
72}
73
74/*
75 * Stop the I2C transaction. This closes out the link, sending
76 * a bare address packet with the MOT bit turned off
77 */
78static void
79iic_dp_aux_stop(device_t idev, bool reading)
80{
81	struct iic_dp_aux_data *aux_data;
82	int mode;
83
84	aux_data = device_get_softc(idev);
85	mode = MODE_I2C_STOP;
86	if (reading)
87		mode |= MODE_I2C_READ;
88	else
89		mode |= MODE_I2C_WRITE;
90	if (aux_data->running) {
91		(void)iic_dp_aux_transaction(idev, mode, 0, NULL);
92		aux_data->running = false;
93	}
94}
95
96/*
97 * Write a single byte to the current I2C address, the
98 * the I2C link must be running or this returns -EIO
99 */
100static int
101iic_dp_aux_put_byte(device_t idev, u8 byte)
102{
103	struct iic_dp_aux_data *aux_data;
104	int ret;
105
106	aux_data = device_get_softc(idev);
107
108	if (!aux_data->running)
109		return (EIO);
110
111	ret = iic_dp_aux_transaction(idev, MODE_I2C_WRITE, byte, NULL);
112	return (ret);
113}
114
115/*
116 * Read a single byte from the current I2C address, the
117 * I2C link must be running or this returns -EIO
118 */
119static int
120iic_dp_aux_get_byte(device_t idev, u8 *byte_ret)
121{
122	struct iic_dp_aux_data *aux_data;
123	int ret;
124
125	aux_data = device_get_softc(idev);
126
127	if (!aux_data->running)
128		return (EIO);
129
130	ret = iic_dp_aux_transaction(idev, MODE_I2C_READ, 0, byte_ret);
131	return (ret);
132}
133
134static int
135iic_dp_aux_xfer(device_t idev, struct iic_msg *msgs, uint32_t num)
136{
137	u8 *buf;
138	int b, m, ret;
139	u16 len;
140	bool reading;
141
142	ret = 0;
143	reading = false;
144
145	for (m = 0; m < num; m++) {
146		len = msgs[m].len;
147		buf = msgs[m].buf;
148		reading = (msgs[m].flags & IIC_M_RD) != 0;
149		ret = iic_dp_aux_address(idev, msgs[m].slave >> 1, reading);
150		if (ret != 0)
151			break;
152		if (reading) {
153			for (b = 0; b < len; b++) {
154				ret = iic_dp_aux_get_byte(idev, &buf[b]);
155				if (ret != 0)
156					break;
157			}
158		} else {
159			for (b = 0; b < len; b++) {
160				ret = iic_dp_aux_put_byte(idev, buf[b]);
161				if (ret != 0)
162					break;
163			}
164		}
165		if (ret != 0)
166			break;
167	}
168	iic_dp_aux_stop(idev, reading);
169	DRM_DEBUG_KMS("dp_aux_xfer return %d\n", ret);
170	return (ret);
171}
172
173static void
174iic_dp_aux_reset_bus(device_t idev)
175{
176
177	(void)iic_dp_aux_address(idev, 0, false);
178	(void)iic_dp_aux_stop(idev, false);
179}
180
181static int
182iic_dp_aux_reset(device_t idev, u_char speed, u_char addr, u_char *oldaddr)
183{
184
185	iic_dp_aux_reset_bus(idev);
186	return (0);
187}
188
189static int
190iic_dp_aux_prepare_bus(device_t idev)
191{
192
193	/* adapter->retries = 3; */
194	iic_dp_aux_reset_bus(idev);
195	return (0);
196}
197
198static int
199iic_dp_aux_probe(device_t idev)
200{
201
202	return (BUS_PROBE_DEFAULT);
203}
204
205static int
206iic_dp_aux_attach(device_t idev)
207{
208	struct iic_dp_aux_data *aux_data;
209
210	aux_data = device_get_softc(idev);
211	aux_data->port = device_add_child(idev, "iicbus", -1);
212	if (aux_data->port == NULL)
213		return (ENXIO);
214	device_quiet(aux_data->port);
215	bus_generic_attach(idev);
216	return (0);
217}
218
219static int
220iic_dp_aux_detach(device_t idev)
221{
222	struct iic_dp_aux_data *aux_data;
223	device_t port;
224
225	aux_data = device_get_softc(idev);
226
227	port = aux_data->port;
228	bus_generic_detach(idev);
229	if (port != NULL)
230		device_delete_child(idev, port);
231
232	return (0);
233}
234
235int
236iic_dp_aux_add_bus(device_t dev, const char *name,
237    int (*ch)(device_t idev, int mode, uint8_t write_byte, uint8_t *read_byte),
238    void *priv, device_t *bus, device_t *adapter)
239{
240	device_t ibus;
241	struct iic_dp_aux_data *data;
242	int idx, error;
243	static int dp_bus_counter;
244
245	mtx_lock(&Giant);
246
247	idx = atomic_fetchadd_int(&dp_bus_counter, 1);
248	ibus = device_add_child(dev, "drm_iic_dp_aux", idx);
249	if (ibus == NULL) {
250		mtx_unlock(&Giant);
251		DRM_ERROR("drm_iic_dp_aux bus %d creation error\n", idx);
252		return (-ENXIO);
253	}
254	device_quiet(ibus);
255	error = device_probe_and_attach(ibus);
256	if (error != 0) {
257		device_delete_child(dev, ibus);
258		mtx_unlock(&Giant);
259		DRM_ERROR("drm_iic_dp_aux bus %d attach failed, %d\n",
260		    idx, error);
261		return (-error);
262	}
263	data = device_get_softc(ibus);
264	data->running = false;
265	data->address = 0;
266	data->aux_ch = ch;
267	data->priv = priv;
268	error = iic_dp_aux_prepare_bus(ibus);
269	if (error == 0) {
270		*bus = ibus;
271		*adapter = data->port;
272	}
273	mtx_unlock(&Giant);
274	return (error);
275}
276
277static device_method_t drm_iic_dp_aux_methods[] = {
278	DEVMETHOD(device_probe,		iic_dp_aux_probe),
279	DEVMETHOD(device_attach,	iic_dp_aux_attach),
280	DEVMETHOD(device_detach,	iic_dp_aux_detach),
281	DEVMETHOD(iicbus_reset,		iic_dp_aux_reset),
282	DEVMETHOD(iicbus_transfer,	iic_dp_aux_xfer),
283	DEVMETHOD_END
284};
285static driver_t drm_iic_dp_aux_driver = {
286	"drm_iic_dp_aux",
287	drm_iic_dp_aux_methods,
288	sizeof(struct iic_dp_aux_data)
289};
290static devclass_t drm_iic_dp_aux_devclass;
291DRIVER_MODULE_ORDERED(drm_iic_dp_aux, drmn, drm_iic_dp_aux_driver,
292    drm_iic_dp_aux_devclass, 0, 0, SI_ORDER_SECOND);
293