#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/bus.h>
#include <sys/module.h>
#include <sys/mutex.h>
#include <sys/condvar.h>
#include <sys/unistd.h>
#include <dev/usb/usb.h>
#include <dev/usb/usbdi.h>
#include <dev/usb/usbhid.h>
#include <dev/usb/usb_device.h>
#include <dev/iicbus/iiconf.h>
#include <dev/iicbus/iicbus.h>
#include "iicbus_if.h"
#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) },
};
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", DEVICE_UNIT_ANY);
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);
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;
}
}
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[] = {
DEVMETHOD(device_probe, i2ctinyusb_probe),
DEVMETHOD(device_attach, i2ctinyusb_attach),
DEVMETHOD(device_detach, i2ctinyusb_detach),
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);