xref: /freebsd/sys/dev/drm2/drm_dp_iic_helper.c (revision fac1f59384114a3fe14076815fc4ede68d6bb1ba)
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 	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  */
58 static int
59 iic_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  */
80 static void
81 iic_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  */
102 static int
103 iic_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  */
121 static int
122 iic_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 
136 static int
137 iic_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 
175 static void
176 iic_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 
183 static int
184 iic_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 
191 static int
192 iic_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 
200 static int
201 iic_dp_aux_probe(device_t idev)
202 {
203 
204 	return (BUS_PROBE_DEFAULT);
205 }
206 
207 static int
208 iic_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 
221 int
222 iic_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 	bus_topo_lock();
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 		bus_topo_unlock();
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 		bus_topo_unlock();
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 	bus_topo_unlock();
260 	return (-error);
261 }
262 
263 static 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 };
271 
272 static driver_t drm_iic_dp_aux_driver = {
273 	"drm_iic_dp_aux",
274 	drm_iic_dp_aux_methods,
275 	sizeof(struct iic_dp_aux_data)
276 };
277 
278 DRIVER_MODULE_ORDERED(drm_iic_dp_aux, drmn, drm_iic_dp_aux_driver, 0, 0,
279     SI_ORDER_SECOND);
280