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$");
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	if (ret < 0)
45		return (ret);
46	return (0);
47}
48
49/*
50 * I2C over AUX CH
51 */
52
53/*
54 * Send the address. If the I2C link is running, this 'restarts'
55 * the connection with the new address, this is used for doing
56 * a write followed by a read (as needed for DDC)
57 */
58static int
59iic_dp_aux_address(device_t idev, u16 address, bool reading)
60{
61	struct iic_dp_aux_data *aux_data;
62	int mode, ret;
63
64	aux_data = device_get_softc(idev);
65	mode = MODE_I2C_START;
66	if (reading)
67		mode |= MODE_I2C_READ;
68	else
69		mode |= MODE_I2C_WRITE;
70	aux_data->address = address;
71	aux_data->running = true;
72	ret = iic_dp_aux_transaction(idev, mode, 0, NULL);
73	return (ret);
74}
75
76/*
77 * Stop the I2C transaction. This closes out the link, sending
78 * a bare address packet with the MOT bit turned off
79 */
80static void
81iic_dp_aux_stop(device_t idev, bool reading)
82{
83	struct iic_dp_aux_data *aux_data;
84	int mode;
85
86	aux_data = device_get_softc(idev);
87	mode = MODE_I2C_STOP;
88	if (reading)
89		mode |= MODE_I2C_READ;
90	else
91		mode |= MODE_I2C_WRITE;
92	if (aux_data->running) {
93		(void)iic_dp_aux_transaction(idev, mode, 0, NULL);
94		aux_data->running = false;
95	}
96}
97
98/*
99 * Write a single byte to the current I2C address, the
100 * the I2C link must be running or this returns -EIO
101 */
102static int
103iic_dp_aux_put_byte(device_t idev, u8 byte)
104{
105	struct iic_dp_aux_data *aux_data;
106	int ret;
107
108	aux_data = device_get_softc(idev);
109
110	if (!aux_data->running)
111		return (-EIO);
112
113	ret = iic_dp_aux_transaction(idev, MODE_I2C_WRITE, byte, NULL);
114	return (ret);
115}
116
117/*
118 * Read a single byte from the current I2C address, the
119 * I2C link must be running or this returns -EIO
120 */
121static int
122iic_dp_aux_get_byte(device_t idev, u8 *byte_ret)
123{
124	struct iic_dp_aux_data *aux_data;
125	int ret;
126
127	aux_data = device_get_softc(idev);
128
129	if (!aux_data->running)
130		return (-EIO);
131
132	ret = iic_dp_aux_transaction(idev, MODE_I2C_READ, 0, byte_ret);
133	return (ret);
134}
135
136static int
137iic_dp_aux_xfer(device_t idev, struct iic_msg *msgs, uint32_t num)
138{
139	u8 *buf;
140	int b, m, ret;
141	u16 len;
142	bool reading;
143
144	ret = 0;
145	reading = false;
146
147	for (m = 0; m < num; m++) {
148		len = msgs[m].len;
149		buf = msgs[m].buf;
150		reading = (msgs[m].flags & IIC_M_RD) != 0;
151		ret = iic_dp_aux_address(idev, msgs[m].slave >> 1, reading);
152		if (ret < 0)
153			break;
154		if (reading) {
155			for (b = 0; b < len; b++) {
156				ret = iic_dp_aux_get_byte(idev, &buf[b]);
157				if (ret != 0)
158					break;
159			}
160		} else {
161			for (b = 0; b < len; b++) {
162				ret = iic_dp_aux_put_byte(idev, buf[b]);
163				if (ret < 0)
164					break;
165			}
166		}
167		if (ret != 0)
168			break;
169	}
170	iic_dp_aux_stop(idev, reading);
171	DRM_DEBUG_KMS("dp_aux_xfer return %d\n", ret);
172	return (-ret);
173}
174
175static void
176iic_dp_aux_reset_bus(device_t idev)
177{
178
179	(void)iic_dp_aux_address(idev, 0, false);
180	(void)iic_dp_aux_stop(idev, false);
181}
182
183static int
184iic_dp_aux_reset(device_t idev, u_char speed, u_char addr, u_char *oldaddr)
185{
186
187	iic_dp_aux_reset_bus(idev);
188	return (0);
189}
190
191static int
192iic_dp_aux_prepare_bus(device_t idev)
193{
194
195	/* adapter->retries = 3; */
196	iic_dp_aux_reset_bus(idev);
197	return (0);
198}
199
200static int
201iic_dp_aux_probe(device_t idev)
202{
203
204	return (BUS_PROBE_DEFAULT);
205}
206
207static int
208iic_dp_aux_attach(device_t idev)
209{
210	struct iic_dp_aux_data *aux_data;
211
212	aux_data = device_get_softc(idev);
213	aux_data->port = device_add_child(idev, "iicbus", -1);
214	if (aux_data->port == NULL)
215		return (ENXIO);
216	device_quiet(aux_data->port);
217	bus_generic_attach(idev);
218	return (0);
219}
220
221int
222iic_dp_aux_add_bus(device_t dev, const char *name,
223    int (*ch)(device_t idev, int mode, uint8_t write_byte, uint8_t *read_byte),
224    void *priv, device_t *bus, device_t *adapter)
225{
226	device_t ibus;
227	struct iic_dp_aux_data *data;
228	int idx, error;
229	static int dp_bus_counter;
230
231	mtx_lock(&Giant);
232
233	idx = atomic_fetchadd_int(&dp_bus_counter, 1);
234	ibus = device_add_child(dev, "drm_iic_dp_aux", idx);
235	if (ibus == NULL) {
236		mtx_unlock(&Giant);
237		DRM_ERROR("drm_iic_dp_aux bus %d creation error\n", idx);
238		return (-ENXIO);
239	}
240	device_quiet(ibus);
241	error = device_probe_and_attach(ibus);
242	if (error != 0) {
243		device_delete_child(dev, ibus);
244		mtx_unlock(&Giant);
245		DRM_ERROR("drm_iic_dp_aux bus %d attach failed, %d\n",
246		    idx, error);
247		return (-error);
248	}
249	data = device_get_softc(ibus);
250	data->running = false;
251	data->address = 0;
252	data->aux_ch = ch;
253	data->priv = priv;
254	error = iic_dp_aux_prepare_bus(ibus);
255	if (error == 0) {
256		*bus = ibus;
257		*adapter = data->port;
258	}
259	mtx_unlock(&Giant);
260	return (-error);
261}
262
263static device_method_t drm_iic_dp_aux_methods[] = {
264	DEVMETHOD(device_probe,		iic_dp_aux_probe),
265	DEVMETHOD(device_attach,	iic_dp_aux_attach),
266	DEVMETHOD(device_detach,	bus_generic_detach),
267	DEVMETHOD(iicbus_reset,		iic_dp_aux_reset),
268	DEVMETHOD(iicbus_transfer,	iic_dp_aux_xfer),
269	DEVMETHOD_END
270};
271static driver_t drm_iic_dp_aux_driver = {
272	"drm_iic_dp_aux",
273	drm_iic_dp_aux_methods,
274	sizeof(struct iic_dp_aux_data)
275};
276static devclass_t drm_iic_dp_aux_devclass;
277DRIVER_MODULE_ORDERED(drm_iic_dp_aux, drmn, drm_iic_dp_aux_driver,
278    drm_iic_dp_aux_devclass, 0, 0, SI_ORDER_SECOND);
279