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 #include <sys/types.h>
25 #include <sys/kobj.h>
26 #include <sys/bus.h>
27 #include <dev/iicbus/iic.h>
28 #include "iicbus_if.h"
29 #include <dev/iicbus/iiconf.h>
30 #include <dev/drm2/drmP.h>
31 #include <dev/drm2/drm_dp_helper.h>
32
33 static int
iic_dp_aux_transaction(device_t idev,int mode,uint8_t write_byte,uint8_t * read_byte)34 iic_dp_aux_transaction(device_t idev, int mode, uint8_t write_byte,
35 uint8_t *read_byte)
36 {
37 struct iic_dp_aux_data *aux_data;
38 int ret;
39
40 aux_data = device_get_softc(idev);
41 ret = (*aux_data->aux_ch)(idev, mode, write_byte, read_byte);
42 if (ret < 0)
43 return (ret);
44 return (0);
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
iic_dp_aux_address(device_t idev,u16 address,bool reading)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
iic_dp_aux_stop(device_t idev,bool reading)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
iic_dp_aux_put_byte(device_t idev,u8 byte)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
iic_dp_aux_get_byte(device_t idev,u8 * byte_ret)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
iic_dp_aux_xfer(device_t idev,struct iic_msg * msgs,uint32_t num)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
iic_dp_aux_reset_bus(device_t idev)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
iic_dp_aux_reset(device_t idev,u_char speed,u_char addr,u_char * oldaddr)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
iic_dp_aux_prepare_bus(device_t idev)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
iic_dp_aux_probe(device_t idev)199 iic_dp_aux_probe(device_t idev)
200 {
201
202 return (BUS_PROBE_DEFAULT);
203 }
204
205 static int
iic_dp_aux_attach(device_t idev)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 int
iic_dp_aux_add_bus(device_t dev,const char * name,int (* ch)(device_t idev,int mode,uint8_t write_byte,uint8_t * read_byte),void * priv,device_t * bus,device_t * adapter)220 iic_dp_aux_add_bus(device_t dev, const char *name,
221 int (*ch)(device_t idev, int mode, uint8_t write_byte, uint8_t *read_byte),
222 void *priv, device_t *bus, device_t *adapter)
223 {
224 device_t ibus;
225 struct iic_dp_aux_data *data;
226 int idx, error;
227 static int dp_bus_counter;
228
229 bus_topo_lock();
230
231 idx = atomic_fetchadd_int(&dp_bus_counter, 1);
232 ibus = device_add_child(dev, "drm_iic_dp_aux", idx);
233 if (ibus == NULL) {
234 bus_topo_unlock();
235 DRM_ERROR("drm_iic_dp_aux bus %d creation error\n", idx);
236 return (-ENXIO);
237 }
238 device_quiet(ibus);
239 error = device_probe_and_attach(ibus);
240 if (error != 0) {
241 device_delete_child(dev, ibus);
242 bus_topo_unlock();
243 DRM_ERROR("drm_iic_dp_aux bus %d attach failed, %d\n",
244 idx, error);
245 return (-error);
246 }
247 data = device_get_softc(ibus);
248 data->running = false;
249 data->address = 0;
250 data->aux_ch = ch;
251 data->priv = priv;
252 error = iic_dp_aux_prepare_bus(ibus);
253 if (error == 0) {
254 *bus = ibus;
255 *adapter = data->port;
256 }
257 bus_topo_unlock();
258 return (-error);
259 }
260
261 static device_method_t drm_iic_dp_aux_methods[] = {
262 DEVMETHOD(device_probe, iic_dp_aux_probe),
263 DEVMETHOD(device_attach, iic_dp_aux_attach),
264 DEVMETHOD(device_detach, bus_generic_detach),
265 DEVMETHOD(iicbus_reset, iic_dp_aux_reset),
266 DEVMETHOD(iicbus_transfer, iic_dp_aux_xfer),
267 DEVMETHOD_END
268 };
269
270 static driver_t drm_iic_dp_aux_driver = {
271 "drm_iic_dp_aux",
272 drm_iic_dp_aux_methods,
273 sizeof(struct iic_dp_aux_data)
274 };
275
276 DRIVER_MODULE_ORDERED(drm_iic_dp_aux, drmn, drm_iic_dp_aux_driver, 0, 0,
277 SI_ORDER_SECOND);
278