Have meg-fx i2c driver use the wakeup functionality by making it interrupt-based (and serves as a simple usage example).
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@16886 a1c6a512-1295-4272-9138-f99709370657
diff --git a/firmware/target/arm/s3c2440/gigabeat-fx/i2c-meg-fx.c b/firmware/target/arm/s3c2440/gigabeat-fx/i2c-meg-fx.c
index 45140c0..7e4e608 100644
--- a/firmware/target/arm/s3c2440/gigabeat-fx/i2c-meg-fx.c
+++ b/firmware/target/arm/s3c2440/gigabeat-fx/i2c-meg-fx.c
@@ -19,79 +19,55 @@
#include "system.h"
#include "i2c-meg-fx.h"
-/* Only implements sending bytes for now. Adding receiving bytes should be
- straightforward if needed. No yielding is present since the calls only
- involve setting audio codec registers - a very rare event. */
-
-/* Wait for a condition on the bus, optionally returning it */
-#define COND_RET _c;
-#define COND_VOID
-#define WAIT_COND(cond, ret) \
- ({ \
- int _t = current_tick + 2; \
- bool _c; \
- while (1) { \
- _c = !!(cond); \
- if (_c || TIME_AFTER(current_tick, _t)) \
- break; \
- } \
- ret \
- })
-
-static int i2c_getack(void)
-{
- /* Wait for ACK: 0 = ack received, 1 = ack not received */
- WAIT_COND(IICCON & I2C_TXRX_INTPND, COND_VOID);
- return IICSTAT & I2C_ACK_L;
-}
-
-static int i2c_start(void)
-{
- /* Generate START */
- IICSTAT = I2C_MODE_MASTER | I2C_MODE_TX | I2C_START | I2C_RXTX_ENB;
- return i2c_getack();
-}
+static struct wakeup i2c_wake; /* Transfer completion signal */
+static struct mutex i2c_mtx; /* Mutual exclusion */
+static unsigned char *buf_ptr; /* Next byte to transfer */
+static int buf_count; /* Number of bytes remaining to transfer */
static void i2c_stop(void)
{
/* Generate STOP */
IICSTAT = I2C_MODE_MASTER | I2C_MODE_TX | I2C_RXTX_ENB;
- /* Clear pending interrupt to continue */
- IICCON &= ~I2C_TXRX_INTPND;
-}
-static int i2c_outb(unsigned char byte)
-{
- /* Write byte to shift register */
- IICDS = byte;
- /* Clear pending interrupt to continue */
- IICCON &= ~I2C_TXRX_INTPND;
- return i2c_getack();
+ /* No more interrupts, clear pending interrupt to continue */
+ IICCON &= ~(I2C_TXRX_INTPND | I2C_TXRX_INTENB);
}
void i2c_write(int addr, const unsigned char *buf, int count)
{
+ if (count <= 0)
+ return;
+
+ mutex_lock(&i2c_mtx);
+
/* Turn on I2C clock */
CLKCON |= (1 << 16);
/* Set mode to master transmitter and enable lines */
IICSTAT = I2C_MODE_MASTER | I2C_MODE_TX | I2C_RXTX_ENB;
- /* Wait for bus to be available */
- if (WAIT_COND(!(IICSTAT & I2C_BUSY), COND_RET))
+ /* Set buffer start and count */
+ buf_ptr = (unsigned char *)buf;
+ buf_count = count;
+
+ /* Send slave address and then data */
+ SRCPND = IIC_MASK;
+ INTPND = IIC_MASK;
+
+ IICCON |= I2C_TXRX_INTENB;
+
+ /* Load slave address into shift register */
+ IICDS = addr & 0xfe;
+
+ /* Generate START */
+ IICSTAT = I2C_MODE_MASTER | I2C_MODE_TX | I2C_START | I2C_RXTX_ENB;
+
+ if (wakeup_wait(&i2c_wake, HZ) != WAIT_SUCCEEDED)
{
- /* Send slave address and then data */
- IICCON &= ~I2C_TXRX_INTPND;
- IICCON |= I2C_TXRX_INTENB;
-
- IICDS = addr & 0xfe;
-
- if (i2c_start() == 0)
- while (count-- > 0 && i2c_outb(*buf++) == 0);
-
+ /* Something went wrong - stop transmission */
+ int oldlevel = disable_irq_save();
i2c_stop();
-
- IICCON &= ~I2C_TXRX_INTENB;
+ restore_irq(oldlevel);
}
/* Go back to slave receive mode and disable lines */
@@ -99,12 +75,23 @@
/* Turn off I2C clock */
CLKCON &= ~(1 << 16);
+
+ mutex_unlock(&i2c_mtx);
}
void i2c_init(void)
{
- /* We poll I2C interrupts */
- INTMSK |= (1 << 27);
+ /* Init kernel objects */
+ wakeup_init(&i2c_wake);
+ mutex_init(&i2c_mtx);
+
+ /* Clear pending source */
+ SRCPND = IIC_MASK;
+ INTPND = IIC_MASK;
+
+ /* Enable i2c interrupt in controller */
+ INTMOD &= ~IIC_MASK;
+ INTMSK &= ~IIC_MASK;
/* Turn on I2C clock */
CLKCON |= (1 << 16);
@@ -123,3 +110,33 @@
/* Turn off I2C clock */
CLKCON &= ~(1 << 16);
}
+
+void IIC(void)
+{
+ for (;;)
+ {
+ /* If ack was received from last byte and bytes are remaining */
+ if (--buf_count >= 0 && (IICSTAT & I2C_ACK_L) == 0)
+ {
+ /* Write next byte to shift register */
+ IICDS = *buf_ptr++;
+
+ /* Clear pending interrupt to continue */
+ IICCON &= ~I2C_TXRX_INTPND;
+ break;
+ }
+
+ /* Finished */
+
+ /* Generate STOP */
+ i2c_stop();
+
+ /* Signal thread */
+ wakeup_signal(&i2c_wake);
+ break;
+ }
+
+ /* Ack */
+ SRCPND = IIC_MASK;
+ INTPND = IIC_MASK;
+}