drm_dp_iic_helper.c revision 296548
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: head/sys/dev/drm2/drm_dp_iic_helper.c 296548 2016-03-08 20:33:02Z dumbbell $"); 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