diff options
Diffstat (limited to 'sys/dev/ichiic/ig4_iic.c')
| -rw-r--r-- | sys/dev/ichiic/ig4_iic.c | 250 |
1 files changed, 241 insertions, 9 deletions
diff --git a/sys/dev/ichiic/ig4_iic.c b/sys/dev/ichiic/ig4_iic.c index 44bc64ead3e9..87eee02ebe33 100644 --- a/sys/dev/ichiic/ig4_iic.c +++ b/sys/dev/ichiic/ig4_iic.c @@ -61,6 +61,8 @@ __FBSDID("$FreeBSD$"); #include <dev/pci/pcivar.h> #include <dev/pci/pcireg.h> #include <dev/smbus/smbconf.h> +#include <dev/iicbus/iicbus.h> +#include <dev/iicbus/iiconf.h> #include <dev/ichiic/ig4_reg.h> #include <dev/ichiic/ig4_var.h> @@ -120,7 +122,7 @@ set_controller(ig4iic_softc_t *sc, uint32_t ctl) reg_write(sc, IG4_REG_INTR_MASK, 0); reg_write(sc, IG4_REG_I2C_EN, ctl); - error = SMB_ETIMEOUT; + error = IIC_ETIMEOUT; for (retry = 100; retry > 0; --retry) { v = reg_read(sc, IG4_REG_ENABLE_STATUS); @@ -148,7 +150,7 @@ wait_status(ig4iic_softc_t *sc, uint32_t status) u_int count_us = 0; u_int limit_us = 25000; /* 25ms */ - error = SMB_ETIMEOUT; + error = IIC_ETIMEOUT; for (;;) { /* @@ -484,6 +486,236 @@ done: } /* + * IICBUS API FUNCTIONS + */ +static int +ig4iic_xfer_start(ig4iic_softc_t *sc, uint16_t slave) +{ + /* XXX 10-bit address support? */ + set_slave_addr(sc, slave >> 1, 0); + return (0); +} + +static int +ig4iic_read(ig4iic_softc_t *sc, uint8_t *buf, uint16_t len, + bool repeated_start, bool stop) +{ + uint32_t cmd; + uint16_t i; + int error; + + if (len == 0) + return (0); + + cmd = IG4_DATA_COMMAND_RD; + cmd |= repeated_start ? IG4_DATA_RESTART : 0; + cmd |= stop && len == 1 ? IG4_DATA_STOP : 0; + + /* Issue request for the first byte (could be last as well). */ + reg_write(sc, IG4_REG_DATA_CMD, cmd); + + for (i = 0; i < len; i++) { + /* + * Maintain a pipeline by queueing the allowance for the next + * read before waiting for the current read. + */ + cmd = IG4_DATA_COMMAND_RD; + if (i < len - 1) { + cmd = IG4_DATA_COMMAND_RD; + cmd |= stop && i == len - 2 ? IG4_DATA_STOP : 0; + reg_write(sc, IG4_REG_DATA_CMD, cmd); + } + error = wait_status(sc, IG4_STATUS_RX_NOTEMPTY); + if (error) + break; + buf[i] = data_read(sc); + } + + (void)reg_read(sc, IG4_REG_TX_ABRT_SOURCE); + return (error); +} + +static int +ig4iic_write(ig4iic_softc_t *sc, uint8_t *buf, uint16_t len, + bool repeated_start, bool stop) +{ + uint32_t cmd; + uint16_t i; + int error; + + if (len == 0) + return (0); + + cmd = repeated_start ? IG4_DATA_RESTART : 0; + for (i = 0; i < len; i++) { + error = wait_status(sc, IG4_STATUS_TX_NOTFULL); + if (error) + break; + cmd |= buf[i]; + cmd |= stop && i == len - 1 ? IG4_DATA_STOP : 0; + reg_write(sc, IG4_REG_DATA_CMD, cmd); + cmd = 0; + } + + (void)reg_read(sc, IG4_REG_TX_ABRT_SOURCE); + return (error); +} + +int +ig4iic_transfer(device_t dev, struct iic_msg *msgs, uint32_t nmsgs) +{ + ig4iic_softc_t *sc = device_get_softc(dev); + const char *reason = NULL; + uint32_t i; + int error; + int unit; + bool rpstart; + bool stop; + + /* + * The hardware interface imposes limits on allowed I2C messages. + * It is not possible to explicitly send a start or stop. + * They are automatically sent (or not sent, depending on the + * configuration) when a data byte is transferred. + * For this reason it's impossible to send a message with no data + * at all (like an SMBus quick message). + * The start condition is automatically generated after the stop + * condition, so it's impossible to not have a start after a stop. + * The repeated start condition is automatically sent if a change + * of the transfer direction happens, so it's impossible to have + * a change of direction without a (repeated) start. + * The repeated start can be forced even without the change of + * direction. + * Changing the target slave address requires resetting the hardware + * state, so it's impossible to do that without the stop followed + * by the start. + */ + for (i = 0; i < nmsgs; i++) { +#if 0 + if (i == 0 && (msgs[i].flags & IIC_M_NOSTART) != 0) { + reason = "first message without start"; + break; + } + if (i == nmsgs - 1 && (msgs[i].flags & IIC_M_NOSTOP) != 0) { + reason = "last message without stop"; + break; + } +#endif + if (msgs[i].len == 0) { + reason = "message with no data"; + break; + } + if (i > 0) { + if ((msgs[i].flags & IIC_M_NOSTART) != 0 && + (msgs[i - 1].flags & IIC_M_NOSTOP) == 0) { + reason = "stop not followed by start"; + break; + } + if ((msgs[i - 1].flags & IIC_M_NOSTOP) != 0 && + msgs[i].slave != msgs[i - 1].slave) { + reason = "change of slave without stop"; + break; + } + if ((msgs[i].flags & IIC_M_NOSTART) != 0 && + (msgs[i].flags & IIC_M_RD) != + (msgs[i - 1].flags & IIC_M_RD)) { + reason = "change of direction without repeated" + " start"; + break; + } + } + } + if (reason != NULL) { + if (bootverbose) + device_printf(dev, "%s\n", reason); + return (IIC_ENOTSUPP); + } + + sx_xlock(&sc->call_lock); + mtx_lock(&sc->io_lock); + + /* Debugging - dump registers. */ + if (ig4_dump) { + unit = device_get_unit(dev); + if (ig4_dump & (1 << unit)) { + ig4_dump &= ~(1 << unit); + ig4iic_dump(sc); + } + } + + /* + * Clear any previous abort condition that may have been holding + * the txfifo in reset. + */ + reg_read(sc, IG4_REG_CLR_TX_ABORT); + + /* + * Clean out any previously received data. + */ + if (sc->rpos != sc->rnext && bootverbose) { + device_printf(sc->dev, "discarding %d bytes of spurious data\n", + sc->rnext - sc->rpos); + } + sc->rpos = 0; + sc->rnext = 0; + + rpstart = false; + error = 0; + for (i = 0; i < nmsgs; i++) { + if ((msgs[i].flags & IIC_M_NOSTART) == 0) { + error = ig4iic_xfer_start(sc, msgs[i].slave); + } else { + if (!sc->slave_valid || + (msgs[i].slave >> 1) != sc->last_slave) { + device_printf(dev, "start condition suppressed" + "but slave address is not set up"); + error = EINVAL; + break; + } + rpstart = false; + } + if (error != 0) + break; + + stop = (msgs[i].flags & IIC_M_NOSTOP) == 0; + if (msgs[i].flags & IIC_M_RD) + error = ig4iic_read(sc, msgs[i].buf, msgs[i].len, + rpstart, stop); + else + error = ig4iic_write(sc, msgs[i].buf, msgs[i].len, + rpstart, stop); + if (error != 0) + break; + + rpstart = !stop; + } + + mtx_unlock(&sc->io_lock); + sx_unlock(&sc->call_lock); + return (error); +} + +int +ig4iic_reset(device_t dev, u_char speed, u_char addr, u_char *oldaddr) +{ + ig4iic_softc_t *sc = device_get_softc(dev); + + sx_xlock(&sc->call_lock); + mtx_lock(&sc->io_lock); + + /* TODO handle speed configuration? */ + if (oldaddr != NULL) + *oldaddr = sc->last_slave << 1; + set_slave_addr(sc, addr >> 1, 0); + if (addr == IIC_UNKNOWN) + sc->slave_valid = false; + + mtx_unlock(&sc->io_lock); + sx_unlock(&sc->call_lock); + return (0); +} + +/* * SMBUS API FUNCTIONS * * Called from ig4iic_pci_attach/detach() @@ -549,9 +781,9 @@ ig4iic_attach(ig4iic_softc_t *sc) IG4_CTL_RESTARTEN | IG4_CTL_SPEED_STD); - sc->smb = device_add_child(sc->dev, "smbus", -1); - if (sc->smb == NULL) { - device_printf(sc->dev, "smbus driver not found\n"); + sc->iicbus = device_add_child(sc->dev, "iicbus", -1); + if (sc->iicbus == NULL) { + device_printf(sc->dev, "iicbus driver not found\n"); error = ENXIO; goto done; } @@ -624,15 +856,15 @@ ig4iic_detach(ig4iic_softc_t *sc) if (error) return (error); } - if (sc->smb) - device_delete_child(sc->dev, sc->smb); + if (sc->iicbus) + device_delete_child(sc->dev, sc->iicbus); if (sc->intr_handle) bus_teardown_intr(sc->dev, sc->intr_res, sc->intr_handle); sx_xlock(&sc->call_lock); mtx_lock(&sc->io_lock); - sc->smb = NULL; + sc->iicbus = NULL; sc->intr_handle = NULL; reg_write(sc, IG4_REG_INTR_MASK, 0); set_controller(sc, 0); @@ -976,4 +1208,4 @@ ig4iic_dump(ig4iic_softc_t *sc) } #undef REGDUMP -DRIVER_MODULE(smbus, ig4iic, smbus_driver, smbus_devclass, NULL, NULL); +DRIVER_MODULE(iicbus, ig4iic, iicbus_driver, iicbus_devclass, NULL, NULL); |
