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