/*- * Copyright (c) 2024 Denis Bodor * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * i2c-tiny-usb, DIY USB to IIC bridge (using AVR or RP2040) from * Till Harbaum & Nicolai Electronics * See : * https://github.com/harbaum/I2C-Tiny-USB * and * https://github.com/Nicolai-Electronics/rp2040-i2c-interface */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "iicbus_if.h" // commands via USB, must match command ids in the firmware #define CMD_ECHO 0 #define CMD_GET_FUNC 1 #define CMD_SET_DELAY 2 #define CMD_GET_STATUS 3 #define CMD_I2C_IO 4 #define CMD_SET_LED 8 #define CMD_I2C_IO_BEGIN (1 << 0) #define CMD_I2C_IO_END (1 << 1) #define STATUS_IDLE 0 #define STATUS_ADDRESS_ACK 1 #define STATUS_ADDRESS_NAK 2 struct i2ctinyusb_softc { struct usb_device *sc_udev; device_t sc_iic_dev; device_t iicbus_dev; struct mtx sc_mtx; }; #define USB_VENDOR_EZPROTOTYPES 0x1c40 #define USB_VENDOR_FTDI 0x0403 static const STRUCT_USB_HOST_ID i2ctinyusb_devs[] = { { USB_VPI(USB_VENDOR_EZPROTOTYPES, 0x0534, 0) }, { USB_VPI(USB_VENDOR_FTDI, 0xc631, 0) }, }; /* Prototypes. */ static int i2ctinyusb_probe(device_t dev); static int i2ctinyusb_attach(device_t dev); static int i2ctinyusb_detach(device_t dev); static int i2ctinyusb_transfer(device_t dev, struct iic_msg *msgs, uint32_t nmsgs); static int i2ctinyusb_reset(device_t dev, u_char speed, u_char addr, u_char *oldaddr); static int usb_read(struct i2ctinyusb_softc *sc, int cmd, int value, int index, void *data, int len) { int error; struct usb_device_request req; uint16_t actlen; req.bmRequestType = UT_READ_VENDOR_INTERFACE; req.bRequest = cmd; USETW(req.wValue, value); USETW(req.wIndex, (index >> 1)); USETW(req.wLength, len); error = usbd_do_request_flags(sc->sc_udev, &sc->sc_mtx, &req, data, 0, &actlen, 2000); if (error) actlen = -1; return (actlen); } static int usb_write(struct i2ctinyusb_softc *sc, int cmd, int value, int index, void *data, int len) { int error; struct usb_device_request req; uint16_t actlen; req.bmRequestType = UT_WRITE_VENDOR_INTERFACE; req.bRequest = cmd; USETW(req.wValue, value); USETW(req.wIndex, (index >> 1)); USETW(req.wLength, len); error = usbd_do_request_flags(sc->sc_udev, &sc->sc_mtx, &req, data, 0, &actlen, 2000); if (error) { actlen = -1; } return (actlen); } static int i2ctinyusb_probe(device_t dev) { struct usb_attach_arg *uaa; uaa = device_get_ivars(dev); if (uaa->usb_mode != USB_MODE_HOST) return (ENXIO); if (usbd_lookup_id_by_uaa(i2ctinyusb_devs, sizeof(i2ctinyusb_devs), uaa) == 0) { device_set_desc(dev, "I2C-Tiny-USB I2C interface"); return (BUS_PROBE_DEFAULT); } return (ENXIO); } static int i2ctinyusb_attach(device_t dev) { struct i2ctinyusb_softc *sc; struct usb_attach_arg *uaa; int err; sc = device_get_softc(dev); uaa = device_get_ivars(dev); device_set_usb_desc(dev); sc->sc_udev = uaa->device; mtx_init(&sc->sc_mtx, "i2ctinyusb lock", NULL, MTX_DEF | MTX_RECURSE); sc->iicbus_dev = device_add_child(dev, "iicbus", -1); if (sc->iicbus_dev == NULL) { device_printf(dev, "iicbus creation failed\n"); err = ENXIO; goto detach; } bus_attach_children(dev); return (0); detach: i2ctinyusb_detach(dev); return (err); } static int i2ctinyusb_detach(device_t dev) { struct i2ctinyusb_softc *sc; int err; sc = device_get_softc(dev); err = bus_generic_detach(dev); if (err != 0) return (err); device_delete_children(dev); mtx_destroy(&sc->sc_mtx); return (0); } static int i2ctinyusb_transfer(device_t dev, struct iic_msg *msgs, uint32_t nmsgs) { struct i2ctinyusb_softc *sc; uint32_t i; int ret = 0; int cmd = CMD_I2C_IO; struct iic_msg *pmsg; unsigned char pstatus; sc = device_get_softc(dev); mtx_lock(&sc->sc_mtx); for (i = 0; i < nmsgs; i++) { pmsg = &msgs[i]; if (i == 0) cmd |= CMD_I2C_IO_BEGIN; if (i == nmsgs - 1) cmd |= CMD_I2C_IO_END; if ((msgs[i].flags & IIC_M_RD) != 0) { if ((ret = usb_read(sc, cmd, pmsg->flags, pmsg->slave, pmsg->buf, pmsg->len)) != pmsg->len) { printf("Read error: got %u\n", ret); ret = EIO; goto out; } } else { if ((ret = usb_write(sc, cmd, pmsg->flags, pmsg->slave, pmsg->buf, pmsg->len)) != pmsg->len) { printf("Write error: got %u\n", ret); ret = EIO; goto out; } } // check status if ((ret = usb_read(sc, CMD_GET_STATUS, 0, 0, &pstatus, 1)) != 1) { ret = EIO; goto out; } if (pstatus == STATUS_ADDRESS_NAK) { ret = EIO; goto out; } } ret = 0; out: mtx_unlock(&sc->sc_mtx); return (ret); } static int i2ctinyusb_reset(device_t dev, u_char speed, u_char addr, u_char *oldaddr) { struct i2ctinyusb_softc *sc; int ret; sc = device_get_softc(dev); mtx_lock(&sc->sc_mtx); ret = usb_write(sc, CMD_SET_DELAY, 10, 0, NULL, 0); mtx_unlock(&sc->sc_mtx); if (ret < 0) printf("i2ctinyusb_reset error!\n"); return (0); } static device_method_t i2ctinyusb_methods[] = { /* Device interface */ DEVMETHOD(device_probe, i2ctinyusb_probe), DEVMETHOD(device_attach, i2ctinyusb_attach), DEVMETHOD(device_detach, i2ctinyusb_detach), /* I2C methods */ DEVMETHOD(iicbus_transfer, i2ctinyusb_transfer), DEVMETHOD(iicbus_reset, i2ctinyusb_reset), DEVMETHOD(iicbus_callback, iicbus_null_callback), DEVMETHOD_END }; static driver_t i2ctinyusb_driver = { .name = "iichb", .methods = i2ctinyusb_methods, .size = sizeof(struct i2ctinyusb_softc), }; DRIVER_MODULE(i2ctinyusb, uhub, i2ctinyusb_driver, NULL, NULL); MODULE_DEPEND(i2ctinyusb, usb, 1, 1, 1); MODULE_DEPEND(i2ctinyusb, iicbus, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER); MODULE_VERSION(i2ctinyusb, 1); /* vi: set ts=8 sw=8: */