summaryrefslogtreecommitdiffstats
path: root/sys/dev/iicbus
diff options
context:
space:
mode:
authorian <ian@FreeBSD.org>2017-09-11 17:01:26 +0000
committerian <ian@FreeBSD.org>2017-09-11 17:01:26 +0000
commit553c914e823395aa57d5e88febce89da2421bf8e (patch)
tree2eebcea3eb9b867eab58b06a77ff4a980c292879 /sys/dev/iicbus
parentf1f01794675bbb08bbb5e7b46e94cdfa850833b1 (diff)
downloadFreeBSD-src-553c914e823395aa57d5e88febce89da2421bf8e.zip
FreeBSD-src-553c914e823395aa57d5e88febce89da2421bf8e.tar.gz
MFC r321583, r321584:
Add a pair of convenience routines for doing simple "register" read/writes on i2c devices, where the "register" can be any length. Add support for tracking nested calls to iicbus_request/release_bus(). Usually it is sufficient to use iicbus_transfer_excl(), or one of the higher-level convenience functions that use it, to reserve the bus for the duration of each register access. Occasionally it is important that a series of accesses or read-modify-write operations must be done without any other intervening access to the device, to prevent corrupting state.
Diffstat (limited to 'sys/dev/iicbus')
-rw-r--r--sys/dev/iicbus/iicbus.h1
-rw-r--r--sys/dev/iicbus/iiconf.c113
-rw-r--r--sys/dev/iicbus/iiconf.h10
3 files changed, 90 insertions, 34 deletions
diff --git a/sys/dev/iicbus/iicbus.h b/sys/dev/iicbus/iicbus.h
index 1085319..c73e475 100644
--- a/sys/dev/iicbus/iicbus.h
+++ b/sys/dev/iicbus/iicbus.h
@@ -39,6 +39,7 @@ struct iicbus_softc
{
device_t dev; /* Myself */
device_t owner; /* iicbus owner device structure */
+ u_int owncount; /* iicbus ownership nesting count */
u_char started; /* address of the 'started' slave
* 0 if no start condition succeeded */
u_char strict; /* deny operations that violate the
diff --git a/sys/dev/iicbus/iiconf.c b/sys/dev/iicbus/iiconf.c
index 27c8464..d32b640 100644
--- a/sys/dev/iicbus/iiconf.c
+++ b/sys/dev/iicbus/iiconf.c
@@ -113,26 +113,30 @@ iicbus_request_bus(device_t bus, device_t dev, int how)
IICBUS_LOCK(sc);
- while ((error == 0) && (sc->owner != NULL))
+ while (error == 0 && sc->owner != NULL && sc->owner != dev)
error = iicbus_poll(sc, how);
if (error == 0) {
- sc->owner = dev;
- /*
- * Drop the lock around the call to the bus driver.
- * This call should be allowed to sleep in the IIC_WAIT case.
- * Drivers might also need to grab locks that would cause LOR
- * if our lock is held.
- */
- IICBUS_UNLOCK(sc);
- /* Ask the underlying layers if the request is ok */
- error = IICBUS_CALLBACK(device_get_parent(bus),
- IIC_REQUEST_BUS, (caddr_t)&how);
- IICBUS_LOCK(sc);
-
- if (error != 0) {
- sc->owner = NULL;
- wakeup_one(sc);
+ ++sc->owncount;
+ if (sc->owner == NULL) {
+ sc->owner = dev;
+ /*
+ * Drop the lock around the call to the bus driver, it
+ * should be allowed to sleep in the IIC_WAIT case.
+ * Drivers might also need to grab locks that would
+ * cause a LOR if our lock is held.
+ */
+ IICBUS_UNLOCK(sc);
+ /* Ask the underlying layers if the request is ok */
+ error = IICBUS_CALLBACK(device_get_parent(bus),
+ IIC_REQUEST_BUS, (caddr_t)&how);
+ IICBUS_LOCK(sc);
+
+ if (error != 0) {
+ sc->owner = NULL;
+ sc->owncount = 0;
+ wakeup_one(sc);
+ }
}
}
@@ -150,7 +154,6 @@ int
iicbus_release_bus(device_t bus, device_t dev)
{
struct iicbus_softc *sc = (struct iicbus_softc *)device_get_softc(bus);
- int error;
IICBUS_LOCK(sc);
@@ -159,26 +162,16 @@ iicbus_release_bus(device_t bus, device_t dev)
return (IIC_EBUSBSY);
}
- /*
- * Drop the lock around the call to the bus driver.
- * This call should be allowed to sleep in the IIC_WAIT case.
- * Drivers might also need to grab locks that would cause LOR
- * if our lock is held.
- */
- IICBUS_UNLOCK(sc);
- /* Ask the underlying layers if the release is ok */
- error = IICBUS_CALLBACK(device_get_parent(bus), IIC_RELEASE_BUS, NULL);
-
- if (error == 0) {
+ if (--sc->owncount == 0) {
+ /* Drop the lock while informing the low-level driver. */
+ IICBUS_UNLOCK(sc);
+ IICBUS_CALLBACK(device_get_parent(bus), IIC_RELEASE_BUS, NULL);
IICBUS_LOCK(sc);
sc->owner = NULL;
-
- /* wakeup a waiting thread */
wakeup_one(sc);
- IICBUS_UNLOCK(sc);
}
-
- return (error);
+ IICBUS_UNLOCK(sc);
+ return (0);
}
/*
@@ -470,3 +463,55 @@ iicbus_transfer_gen(device_t dev, struct iic_msg *msgs, uint32_t nmsgs)
iicbus_stop(bus);
return (error);
}
+
+int
+iicdev_readfrom(device_t slavedev, uint8_t regaddr, void *buffer,
+ uint16_t buflen, int waithow)
+{
+ struct iic_msg msgs[2];
+ uint8_t slaveaddr;
+
+ /*
+ * Two transfers back to back with a repeat-start between them; first we
+ * write the address-within-device, then we read from the device.
+ */
+ slaveaddr = iicbus_get_addr(slavedev);
+
+ msgs[0].slave = slaveaddr;
+ msgs[0].flags = IIC_M_WR | IIC_M_NOSTOP;
+ msgs[0].len = 1;
+ msgs[0].buf = &regaddr;
+
+ msgs[1].slave = slaveaddr;
+ msgs[1].flags = IIC_M_RD;
+ msgs[1].len = buflen;
+ msgs[1].buf = buffer;
+
+ return (iicbus_transfer_excl(slavedev, msgs, nitems(msgs), waithow));
+}
+
+int iicdev_writeto(device_t slavedev, uint8_t regaddr, void *buffer,
+ uint16_t buflen, int waithow)
+{
+ struct iic_msg msgs[2];
+ uint8_t slaveaddr;
+
+ /*
+ * Two transfers back to back with no stop or start between them; first
+ * we write the address then we write the data to that address, all in a
+ * single transfer from two scattered buffers.
+ */
+ slaveaddr = iicbus_get_addr(slavedev);
+
+ msgs[0].slave = slaveaddr;
+ msgs[0].flags = IIC_M_WR | IIC_M_NOSTOP;
+ msgs[0].len = 1;
+ msgs[0].buf = &regaddr;
+
+ msgs[1].slave = slaveaddr;
+ msgs[1].flags = IIC_M_WR | IIC_M_NOSTART;
+ msgs[1].len = buflen;
+ msgs[1].buf = buffer;
+
+ return (iicbus_transfer_excl(slavedev, msgs, nitems(msgs), waithow));
+}
diff --git a/sys/dev/iicbus/iiconf.h b/sys/dev/iicbus/iiconf.h
index 03b9583..856b029 100644
--- a/sys/dev/iicbus/iiconf.h
+++ b/sys/dev/iicbus/iiconf.h
@@ -133,6 +133,16 @@ int iicbus_transfer_excl(device_t bus, struct iic_msg *msgs, uint32_t nmsgs,
int how);
int iicbus_transfer_gen(device_t bus, struct iic_msg *msgs, uint32_t nmsgs);
+/*
+ * Simple register read/write routines, but the "register" can be any size.
+ * The transfers are done with iicbus_transfer_excl(). Reads use a repeat-start
+ * between sending the address and reading; writes use a single start/stop.
+ */
+int iicdev_readfrom(device_t _slavedev, uint8_t _regaddr, void *_buffer,
+ uint16_t _buflen, int _waithow);
+int iicdev_writeto(device_t _slavedev, uint8_t _regaddr, void *_buffer,
+ uint16_t _buflen, int _waithow);
+
#define IICBUS_MODVER 1
#define IICBUS_MINVER 1
#define IICBUS_MAXVER 1
OpenPOWER on IntegriCloud