xref: /freebsd/sys/dev/drm2/drm_dp_iic_helper.c (revision 3e102307b7637eaab1e213740399e9ce50ef3c1b)
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 
35 static int
36 iic_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  */
56 static int
57 iic_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  */
78 static void
79 iic_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  */
100 static int
101 iic_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  */
119 static int
120 iic_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 
134 static int
135 iic_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 
173 static void
174 iic_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 
181 static int
182 iic_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 
189 static int
190 iic_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 
198 static int
199 iic_dp_aux_probe(device_t idev)
200 {
201 
202 	return (BUS_PROBE_DEFAULT);
203 }
204 
205 static int
206 iic_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 
219 static int
220 iic_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 
235 int
236 iic_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 
277 static 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 };
285 static 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 };
290 static devclass_t drm_iic_dp_aux_devclass;
291 DRIVER_MODULE_ORDERED(drm_iic_dp_aux, drmn, drm_iic_dp_aux_driver,
292     drm_iic_dp_aux_devclass, 0, 0, SI_ORDER_SECOND);
293