aboutsummaryrefslogtreecommitdiff
path: root/sys/dev/ichiic
diff options
context:
space:
mode:
authorAndriy Gapon <avg@FreeBSD.org>2016-10-30 12:15:33 +0000
committerAndriy Gapon <avg@FreeBSD.org>2016-10-30 12:15:33 +0000
commit448897d366c218f9fd6208427eef1e6dd51f78d0 (patch)
treeaa67d0a23670c7d9084ad98a835517e4c1653deb /sys/dev/ichiic
parent3c1f73b18d318cc7be8c1b9c19933c2eaa1f1771 (diff)
Notes
Diffstat (limited to 'sys/dev/ichiic')
-rw-r--r--sys/dev/ichiic/ig4_iic.c250
-rw-r--r--sys/dev/ichiic/ig4_pci.c9
-rw-r--r--sys/dev/ichiic/ig4_var.h5
3 files changed, 253 insertions, 11 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);
diff --git a/sys/dev/ichiic/ig4_pci.c b/sys/dev/ichiic/ig4_pci.c
index 7038cae6478f..0d796e5980d7 100644
--- a/sys/dev/ichiic/ig4_pci.c
+++ b/sys/dev/ichiic/ig4_pci.c
@@ -60,6 +60,7 @@ __FBSDID("$FreeBSD$");
#include <dev/pci/pcivar.h>
#include <dev/pci/pcireg.h>
#include <dev/smbus/smbconf.h>
+#include <dev/iicbus/iiconf.h>
#include "smbus_if.h"
@@ -180,6 +181,10 @@ static device_method_t ig4iic_pci_methods[] = {
DEVMETHOD(smbus_bread, ig4iic_smb_bread),
DEVMETHOD(smbus_trans, ig4iic_smb_trans),
+ DEVMETHOD(iicbus_transfer, ig4iic_transfer),
+ DEVMETHOD(iicbus_reset, ig4iic_reset),
+ DEVMETHOD(iicbus_callback, iicbus_null_callback),
+
DEVMETHOD_END
};
@@ -191,7 +196,9 @@ static driver_t ig4iic_pci_driver = {
static devclass_t ig4iic_pci_devclass;
-DRIVER_MODULE(ig4iic, pci, ig4iic_pci_driver, ig4iic_pci_devclass, 0, 0);
+DRIVER_MODULE_ORDERED(ig4iic, pci, ig4iic_pci_driver, ig4iic_pci_devclass, 0, 0,
+ SI_ORDER_ANY);
MODULE_DEPEND(ig4iic, pci, 1, 1, 1);
MODULE_DEPEND(ig4iic, smbus, SMBUS_MINVER, SMBUS_PREFVER, SMBUS_MAXVER);
+MODULE_DEPEND(ig4iic, iicbus, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER);
MODULE_VERSION(ig4iic, 1);
diff --git a/sys/dev/ichiic/ig4_var.h b/sys/dev/ichiic/ig4_var.h
index bb1a357e2c32..b35f2f97296f 100644
--- a/sys/dev/ichiic/ig4_var.h
+++ b/sys/dev/ichiic/ig4_var.h
@@ -42,6 +42,7 @@
#include "device_if.h"
#include "pci_if.h"
#include "smbus_if.h"
+#include "iicbus_if.h"
#define IG4_RBUFSIZE 128
#define IG4_RBUFMASK (IG4_RBUFSIZE - 1)
@@ -51,7 +52,7 @@ enum ig4_op { IG4_IDLE, IG4_READ, IG4_WRITE };
struct ig4iic_softc {
device_t dev;
struct intr_config_hook enum_hook;
- device_t smb;
+ device_t iicbus;
struct resource *regs_res;
int regs_rid;
struct resource *intr_res;
@@ -115,5 +116,7 @@ extern smbus_pcall_t ig4iic_smb_pcall;
extern smbus_bwrite_t ig4iic_smb_bwrite;
extern smbus_bread_t ig4iic_smb_bread;
extern smbus_trans_t ig4iic_smb_trans;
+extern iicbus_transfer_t ig4iic_transfer;
+extern iicbus_reset_t ig4iic_reset;
#endif