1 /*- 2 * Copyright (c) 2024 Denis Bodor <dbodor@rollmops.ninja> 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 15 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 16 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 17 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 18 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 19 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 20 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 21 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24 * SUCH DAMAGE. 25 */ 26 27 /* 28 * i2c-tiny-usb, DIY USB to IIC bridge (using AVR or RP2040) from 29 * Till Harbaum & Nicolai Electronics 30 * See : 31 * https://github.com/harbaum/I2C-Tiny-USB 32 * and 33 * https://github.com/Nicolai-Electronics/rp2040-i2c-interface 34 */ 35 36 #include <sys/systm.h> 37 #include <sys/kernel.h> 38 #include <sys/bus.h> 39 #include <sys/module.h> 40 #include <sys/mutex.h> 41 #include <sys/condvar.h> 42 #include <sys/unistd.h> 43 #include <dev/usb/usb.h> 44 #include <dev/usb/usbdi.h> 45 #include <dev/usb/usbhid.h> 46 #include <dev/usb/usb_device.h> 47 48 #include <dev/iicbus/iiconf.h> 49 #include <dev/iicbus/iicbus.h> 50 #include "iicbus_if.h" 51 52 // commands via USB, must match command ids in the firmware 53 #define CMD_ECHO 0 54 #define CMD_GET_FUNC 1 55 #define CMD_SET_DELAY 2 56 #define CMD_GET_STATUS 3 57 #define CMD_I2C_IO 4 58 #define CMD_SET_LED 8 59 #define CMD_I2C_IO_BEGIN (1 << 0) 60 #define CMD_I2C_IO_END (1 << 1) 61 #define STATUS_IDLE 0 62 #define STATUS_ADDRESS_ACK 1 63 #define STATUS_ADDRESS_NAK 2 64 65 struct i2ctinyusb_softc { 66 struct usb_device *sc_udev; 67 device_t sc_iic_dev; 68 device_t iicbus_dev; 69 struct mtx sc_mtx; 70 }; 71 72 #define USB_VENDOR_EZPROTOTYPES 0x1c40 73 #define USB_VENDOR_FTDI 0x0403 74 75 static const STRUCT_USB_HOST_ID i2ctinyusb_devs[] = { 76 { USB_VPI(USB_VENDOR_EZPROTOTYPES, 0x0534, 0) }, 77 { USB_VPI(USB_VENDOR_FTDI, 0xc631, 0) }, 78 }; 79 80 /* Prototypes. */ 81 static int i2ctinyusb_probe(device_t dev); 82 static int i2ctinyusb_attach(device_t dev); 83 static int i2ctinyusb_detach(device_t dev); 84 static int i2ctinyusb_transfer(device_t dev, struct iic_msg *msgs, 85 uint32_t nmsgs); 86 static int i2ctinyusb_reset(device_t dev, u_char speed, u_char addr, 87 u_char *oldaddr); 88 89 static int 90 usb_read(struct i2ctinyusb_softc *sc, int cmd, int value, int index, 91 void *data, int len) 92 { 93 int error; 94 struct usb_device_request req; 95 uint16_t actlen; 96 97 req.bmRequestType = UT_READ_VENDOR_INTERFACE; 98 req.bRequest = cmd; 99 USETW(req.wValue, value); 100 USETW(req.wIndex, (index >> 1)); 101 USETW(req.wLength, len); 102 103 error = usbd_do_request_flags(sc->sc_udev, &sc->sc_mtx, &req, data, 0, 104 &actlen, 2000); 105 106 if (error) 107 actlen = -1; 108 109 return (actlen); 110 } 111 112 static int 113 usb_write(struct i2ctinyusb_softc *sc, int cmd, int value, int index, 114 void *data, int len) 115 { 116 int error; 117 struct usb_device_request req; 118 uint16_t actlen; 119 120 req.bmRequestType = UT_WRITE_VENDOR_INTERFACE; 121 req.bRequest = cmd; 122 USETW(req.wValue, value); 123 USETW(req.wIndex, (index >> 1)); 124 USETW(req.wLength, len); 125 126 error = usbd_do_request_flags(sc->sc_udev, &sc->sc_mtx, &req, data, 0, 127 &actlen, 2000); 128 129 if (error) { 130 actlen = -1; 131 } 132 133 return (actlen); 134 } 135 136 static int 137 i2ctinyusb_probe(device_t dev) 138 { 139 struct usb_attach_arg *uaa; 140 141 uaa = device_get_ivars(dev); 142 143 if (uaa->usb_mode != USB_MODE_HOST) 144 return (ENXIO); 145 146 if (usbd_lookup_id_by_uaa(i2ctinyusb_devs, sizeof(i2ctinyusb_devs), 147 uaa) == 0) { 148 device_set_desc(dev, "I2C-Tiny-USB I2C interface"); 149 return (BUS_PROBE_DEFAULT); 150 } 151 152 return (ENXIO); 153 } 154 155 static int 156 i2ctinyusb_attach(device_t dev) 157 { 158 struct i2ctinyusb_softc *sc; 159 struct usb_attach_arg *uaa; 160 int err; 161 162 sc = device_get_softc(dev); 163 164 uaa = device_get_ivars(dev); 165 device_set_usb_desc(dev); 166 167 sc->sc_udev = uaa->device; 168 mtx_init(&sc->sc_mtx, "i2ctinyusb lock", NULL, MTX_DEF | MTX_RECURSE); 169 170 sc->iicbus_dev = device_add_child(dev, "iicbus", -1); 171 if (sc->iicbus_dev == NULL) { 172 device_printf(dev, "iicbus creation failed\n"); 173 err = ENXIO; 174 goto detach; 175 } 176 err = bus_generic_attach(dev); 177 178 return (0); 179 180 detach: 181 i2ctinyusb_detach(dev); 182 return (err); 183 } 184 185 static int 186 i2ctinyusb_detach(device_t dev) 187 { 188 struct i2ctinyusb_softc *sc; 189 int err; 190 191 sc = device_get_softc(dev); 192 193 err = bus_generic_detach(dev); 194 if (err != 0) 195 return (err); 196 device_delete_children(dev); 197 198 mtx_destroy(&sc->sc_mtx); 199 200 return (0); 201 } 202 203 static int 204 i2ctinyusb_transfer(device_t dev, struct iic_msg *msgs, uint32_t nmsgs) 205 { 206 struct i2ctinyusb_softc *sc; 207 uint32_t i; 208 int ret = 0; 209 int cmd = CMD_I2C_IO; 210 struct iic_msg *pmsg; 211 unsigned char pstatus; 212 213 sc = device_get_softc(dev); 214 215 mtx_lock(&sc->sc_mtx); 216 217 for (i = 0; i < nmsgs; i++) { 218 pmsg = &msgs[i]; 219 if (i == 0) 220 cmd |= CMD_I2C_IO_BEGIN; 221 if (i == nmsgs - 1) 222 cmd |= CMD_I2C_IO_END; 223 224 if ((msgs[i].flags & IIC_M_RD) != 0) { 225 if ((ret = usb_read(sc, cmd, pmsg->flags, pmsg->slave, pmsg->buf, 226 pmsg->len)) != pmsg->len) { 227 printf("Read error: got %u\n", ret); 228 ret = EIO; 229 goto out; 230 } 231 } else { 232 if ((ret = usb_write(sc, cmd, pmsg->flags, pmsg->slave, pmsg->buf, 233 pmsg->len)) != pmsg->len) { 234 printf("Write error: got %u\n", ret); 235 ret = EIO; 236 goto out; 237 } 238 239 } 240 // check status 241 if ((ret = usb_read(sc, CMD_GET_STATUS, 0, 0, &pstatus, 1)) != 1) { 242 ret = EIO; 243 goto out; 244 } 245 246 if (pstatus == STATUS_ADDRESS_NAK) { 247 ret = EIO; 248 goto out; 249 } 250 } 251 252 ret = 0; 253 254 out: 255 mtx_unlock(&sc->sc_mtx); 256 return (ret); 257 } 258 259 static int 260 i2ctinyusb_reset(device_t dev, u_char speed, u_char addr, u_char *oldaddr) 261 { 262 struct i2ctinyusb_softc *sc; 263 int ret; 264 265 sc = device_get_softc(dev); 266 267 mtx_lock(&sc->sc_mtx); 268 ret = usb_write(sc, CMD_SET_DELAY, 10, 0, NULL, 0); 269 mtx_unlock(&sc->sc_mtx); 270 271 if (ret < 0) 272 printf("i2ctinyusb_reset error!\n"); 273 274 return (0); 275 } 276 277 static device_method_t i2ctinyusb_methods[] = { 278 /* Device interface */ 279 DEVMETHOD(device_probe, i2ctinyusb_probe), 280 DEVMETHOD(device_attach, i2ctinyusb_attach), 281 DEVMETHOD(device_detach, i2ctinyusb_detach), 282 283 /* I2C methods */ 284 DEVMETHOD(iicbus_transfer, i2ctinyusb_transfer), 285 DEVMETHOD(iicbus_reset, i2ctinyusb_reset), 286 DEVMETHOD(iicbus_callback, iicbus_null_callback), 287 288 DEVMETHOD_END 289 }; 290 291 static driver_t i2ctinyusb_driver = { 292 .name = "iichb", 293 .methods = i2ctinyusb_methods, 294 .size = sizeof(struct i2ctinyusb_softc), 295 }; 296 297 DRIVER_MODULE(i2ctinyusb, uhub, i2ctinyusb_driver, NULL, NULL); 298 MODULE_DEPEND(i2ctinyusb, usb, 1, 1, 1); 299 MODULE_DEPEND(i2ctinyusb, iicbus, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER); 300 MODULE_VERSION(i2ctinyusb, 1); 301 302 /* vi: set ts=8 sw=8: */ 303