summaryrefslogtreecommitdiffstats
path: root/firmware/target/arm/imx31
diff options
context:
space:
mode:
authorMichael Sevakis <jethead71@rockbox.org>2011-12-29 05:15:36 +0000
committerMichael Sevakis <jethead71@rockbox.org>2011-12-29 05:15:36 +0000
commit20d81d979a22403c16c1f1c576de63af07b2ea99 (patch)
tree5d673b6185330c5b3d41aac301b0439a7a3bd71a /firmware/target/arm/imx31
parentad9a0588dbbcb91cd10ab6465d7c9405d5c032fc (diff)
downloadrockbox-20d81d979a22403c16c1f1c576de63af07b2ea99.tar.gz
rockbox-20d81d979a22403c16c1f1c576de63af07b2ea99.zip
i.MX31: Implement asynchronous version of I2C driver.
Scheme is more flexible and should help to enable threadless RDS. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@31461 a1c6a512-1295-4272-9138-f99709370657
Diffstat (limited to 'firmware/target/arm/imx31')
-rw-r--r--firmware/target/arm/imx31/i2c-imx31.c373
-rw-r--r--firmware/target/arm/imx31/i2c-imx31.h47
2 files changed, 262 insertions, 158 deletions
diff --git a/firmware/target/arm/imx31/i2c-imx31.c b/firmware/target/arm/imx31/i2c-imx31.c
index 975f951fdc..972c7d465b 100644
--- a/firmware/target/arm/imx31/i2c-imx31.c
+++ b/firmware/target/arm/imx31/i2c-imx31.c
@@ -46,14 +46,11 @@ static __attribute__((interrupt("IRQ"))) void I2C3_HANDLER(void);
static struct i2c_module_descriptor
{
volatile unsigned short * const base; /* Module base address */
+ struct i2c_transfer_desc *head; /* Running job */
+ struct i2c_transfer_desc *tail; /* Most recent job added */
void (* const handler)(void); /* Module interrupt handler */
- struct mutex m; /* Node mutual-exclusion */
- struct semaphore complete; /* I2C completion signal */
- unsigned char *addr_data; /* Additional addressing data */
- int addr_count; /* Addressing byte count */
- unsigned char *data; /* TX/RX buffer (actual data) */
- int data_count; /* TX/RX byte count */
- unsigned char addr; /* Address + r/w bit */
+ unsigned char addr; /* Composite address value */
+ unsigned char busy; /* Have buffers been modified? */
uint8_t enable; /* Enable count */
const uint8_t cg; /* Clock gating index */
const uint8_t ints; /* Module interrupt number */
@@ -85,226 +82,308 @@ static struct i2c_module_descriptor
#endif
};
-static void i2c_interrupt(enum i2c_module_number i2c)
+
+/* Actually begin the session at the queue head */
+static bool start_transfer(struct i2c_module_descriptor * const desc,
+ struct i2c_transfer_desc * const xfer)
{
- struct i2c_module_descriptor * const desc = &i2c_descs[i2c];
volatile unsigned short * const base = desc->base;
- unsigned short i2sr = base[I2SR];
- base[I2SR] = 0; /* Clear IIF */
+ /* If interface isn't enabled or buffers have been used, the transfer
+ cannot be started/restarted. */
+ if (desc->enable == 0 || desc->busy != 0)
+ return false;
+
+ /* Set speed */
+ base[IFDR] = xfer->node->ifdr;
+
+ /* Clear status */
+ base[I2SR] = 0;
+
+ /* Enable module, enable Interrupt, master transmitter */
+ unsigned short i2cr = I2C_I2CR_IEN | I2C_I2CR_IIEN | I2C_I2CR_MTX;
+
+ unsigned char addr = xfer->node->addr & 0xfe;
- if (desc->addr_count >= 0)
+ if (xfer->rxcount > 0)
{
- /* ADDR cycle - either done or more to send */
- if ((i2sr & I2C_I2SR_RXAK) != 0)
+ addr |= 0x01; /* Slave address/rd */
+
+ if (xfer->rxcount < 2)
{
- goto i2c_stop; /* problem */
+ /* Receiving less than two bytes - disable ACK generation */
+ i2cr |= I2C_I2CR_TXAK;
}
+ }
+
+ /* Remember composite address - contains info concerning RX or TX */
+ desc->addr = addr;
+
+ /* Set config, generate START. */
+ base[I2CR] = i2cr | I2C_I2CR_MSTA;
+
+ /* Address slave (first byte sent) and begin session. */
+ base[I2DR] = addr;
+
+ return true;
+}
+
+static void i2c_interrupt(struct i2c_module_descriptor * const desc)
+{
+ volatile unsigned short * const base = desc->base;
+ unsigned short const i2sr = base[I2SR];
+ struct i2c_transfer_desc *xfer = desc->head;
+
+ base[I2SR] = 0; /* Clear IIF */
+
+ if (i2sr & I2C_I2SR_IAL)
+ {
+ /* Bus arbitration was lost - retry current transfer until it succeeds */
+ /* Best guess as to why: at high CPU speeds, voltage hasn't had time to
+ * stabilize to register STOP if a new transfer is started very soon
+ * after a previous one has completed. AFAICT, this might happen once at
+ * most on a transfer. Switching to repeated START could be a better way
+ * to handle the cases where multiple transfers are already queued. */
+ if (start_transfer(desc, xfer))
+ return;
- if (--desc->addr_count < 0)
+ /* Restart failed - STOP */
+ base[I2CR] &= ~(I2C_I2CR_MSTA | I2C_I2CR_IIEN);
+ }
+ else if (xfer->txcount >= 0 && (base[I2CR] & I2C_I2CR_MTX))
+ {
+ /* ADDR cycle or TX */
+ if ((i2sr & I2C_I2SR_RXAK) != 0)
{
- /* Switching to data cycle */
- if (desc->addr & 0x1)
- {
- base[I2CR] &= ~I2C_I2CR_MTX; /* Switch to RX mode */
- base[I2DR]; /* Dummy read */
- return;
- }
- /* else remaining data is TX - handle below */
- goto i2c_transmit;
+ /* NACK */
}
- else
+ else if (xfer->txcount > 0)
{
- base[I2DR] = *desc->addr_data++; /* Send next addressing byte */
+ desc->busy = 1;
+ base[I2DR] = *xfer->txdata++; /* Send next TX byte */
+ xfer->txcount--;
return;
}
- }
-
- if (base[I2CR] & I2C_I2CR_MTX)
- {
- /* Transmitting data */
- if ((i2sr & I2C_I2SR_RXAK) == 0)
+ else if (desc->addr & 0x01)
{
-i2c_transmit:
- if (desc->data_count > 0)
- {
- /* More bytes to send, got ACK from previous byte */
- base[I2DR] = *desc->data++;
- desc->data_count--;
- return;
- }
+ /* ADDR cycle is complete */
+ base[I2CR] &= ~I2C_I2CR_MTX; /* Switch to RX mode */
+ base[I2DR]; /* Dummy read */
+ return;
}
- /* else done or no ACK received */
+
+ /* Transfer complete or NACK - STOP */
+ base[I2CR] &= ~(I2C_I2CR_MSTA | I2C_I2CR_IIEN);
}
- else
+ else if (xfer->rxcount > 0)
{
- /* Receiving data */
- if (--desc->data_count > 0)
+ /* RX */
+ desc->busy = 1;
+
+ if (--xfer->rxcount > 0)
{
- if (desc->data_count == 1)
+ if (xfer->rxcount == 1)
{
/* 2nd to Last byte - NACK */
base[I2CR] |= I2C_I2CR_TXAK;
}
- *desc->data++ = base[I2DR]; /* Read data from I2DR and store */
+ *xfer->rxdata++ = base[I2DR]; /* Read data from I2DR and store */
return;
}
+
+ /* Generate STOP signal before reading final byte */
+ base[I2CR] &= ~(I2C_I2CR_MSTA | I2C_I2CR_IIEN);
+ *xfer->rxdata++ = base[I2DR]; /* Read data from I2DR and store */
+ }
+
+ /* Start next transfer, if any */
+ for (;;)
+ {
+ struct i2c_transfer_desc *next = xfer->next;
+ i2c_transfer_cb_fn_type callback = xfer->callback;
+ xfer->next = NULL;
+
+ if (next == xfer)
+ {
+ /* Last job on queue */
+ desc->head = NULL;
+
+ if (callback != NULL)
+ callback(xfer);
+
+ /* Callback may have restarted transfers. */
+ }
else
{
- /* Generate STOP signal before reading data */
- base[I2CR] &= ~(I2C_I2CR_MSTA | I2C_I2CR_IIEN);
- *desc->data++ = base[I2DR]; /* Read data from I2DR and store */
- goto i2c_done;
+ /* Queue next job */
+ desc->head = next;
+
+ if (callback != NULL)
+ callback(xfer);
+
+ desc->busy = 0;
+ if (!start_transfer(desc, next))
+ {
+ xfer = next;
+ continue;
+ }
}
- }
-i2c_stop:
- /* Generate STOP signal */
- base[I2CR] &= ~(I2C_I2CR_MSTA | I2C_I2CR_IIEN);
-i2c_done:
- /* Signal thread we're done */
- semaphore_release(&desc->complete);
+ break;
+ }
}
#if (I2C_MODULE_MASK & USE_I2C1_MODULE)
static __attribute__((interrupt("IRQ"))) void I2C1_HANDLER(void)
{
- i2c_interrupt(I2C1_NUM);
+ i2c_interrupt(&i2c_descs[I2C1_NUM]);
}
#endif
#if (I2C_MODULE_MASK & USE_I2C2_MODULE)
static __attribute__((interrupt("IRQ"))) void I2C2_HANDLER(void)
{
- i2c_interrupt(I2C2_NUM);
+ i2c_interrupt(&i2c_descs[I2C2_NUM]);
}
#endif
#if (I2C_MODULE_MASK & USE_I2C3_MODULE)
static __attribute__((interrupt("IRQ"))) void I2C3_HANDLER(void)
{
- i2c_interrupt(I2C3_NUM);
+ i2c_interrupt(&i2c_descs[I2C3_NUM]);
}
#endif
-static int i2c_transfer(struct i2c_node * const node,
- struct i2c_module_descriptor *const desc)
+/* Send and/or receive data on the specified node asynchronously */
+bool i2c_transfer(struct i2c_transfer_desc *xfer)
{
- volatile unsigned short * const base = desc->base;
- int count = desc->data_count;
- uint16_t i2cr;
-
- /* Make sure bus is idle. */
- while (base[I2SR] & I2C_I2SR_IBB);
-
- /* Set speed */
- base[IFDR] = node->ifdr;
-
- /* Enable module */
- base[I2CR] = I2C_I2CR_IEN;
-
- /* Enable Interrupt, Master */
- i2cr = I2C_I2CR_IEN | I2C_I2CR_IIEN | I2C_I2CR_MTX;
-
- if ((desc->addr & 0x1) && desc->data_count < 2)
+ if (xfer == NULL || xfer->next != NULL || xfer->node == NULL ||
+ xfer->rxcount < 0 || xfer->txcount < 0 ||
+ (xfer->rxcount <= 0 && xfer->txcount <= 0))
{
- /* Receiving less than two bytes - disable ACK generation */
- i2cr |= I2C_I2CR_TXAK;
+ /* Can't pass a busy descriptor, requires a node, < 0 sizes
+ or all-0 sizes not permitted. */
+ return false;
}
- /* Set config */
- base[I2CR] = i2cr;
-
- /* Generate START */
- base[I2CR] = i2cr | I2C_I2CR_MSTA;
-
- /* Address slave (first byte sent) and begin session. */
- base[I2DR] = desc->addr;
+ bool retval = true;
+ struct i2c_module_descriptor * const desc = &i2c_descs[xfer->node->num];
+ unsigned long cpsr = disable_irq_save();
- /* Wait for transfer to complete */
- if (semaphore_wait(&desc->complete, HZ) == OBJ_WAIT_SUCCEEDED)
+ if (desc->head == NULL)
{
- count -= desc->data_count;
+ /* No transfers in progress; start interface. */
+ desc->busy = 0;
+ retval = start_transfer(desc, xfer);
+
+ if (retval)
+ {
+ /* Start ok: actually put it in the queue. */
+ desc->head = xfer;
+ desc->tail = xfer;
+ xfer->next = xfer; /* First, self-reference terminate */
+ }
}
else
{
- /* Generate STOP if timeout */
- base[I2CR] &= ~(I2C_I2CR_MSTA | I2C_I2CR_IIEN);
- count = -1;
+ /* Already running: simply add to end and the final INT on the
+ * running transfer will pick it up. */
+ desc->tail->next = xfer; /* Add to tail */
+ desc->tail = xfer; /* New tail */
+ xfer->next = xfer; /* Self-reference terminate */
}
- desc->addr_count = 0;
+ restore_irq(cpsr);
- return count;
+ return retval;
}
-int i2c_read(struct i2c_node *node, int reg,
- unsigned char *data, int data_count)
+/** Synchronous transfers support **/
+static void i2c_sync_xfer_complete_cb(struct i2c_transfer_desc *xfer)
{
- struct i2c_module_descriptor *const desc = &i2c_descs[node->num];
- unsigned char ad[1];
+ semaphore_release(&((struct i2c_sync_transfer_desc *)xfer)->sema);
+}
- mutex_lock(&desc->m);
+static inline bool i2c_sync_wait_for_xfer_complete(struct i2c_sync_transfer_desc *xfer)
+{
+ return semaphore_wait(&xfer->sema, TIMEOUT_BLOCK) == OBJ_WAIT_SUCCEEDED;
+}
+
+int i2c_read(struct i2c_node *node, int addr, unsigned char *data,
+ int data_count)
+{
+ struct i2c_sync_transfer_desc xfer;
- desc->addr = (node->addr & 0xfe) | 0x1; /* Slave address/rd */
+ xfer.xfer.node = node;
- if (reg >= 0)
+ unsigned char ad;
+
+ if (addr >= 0)
{
/* Sub-address */
- desc->addr_count = 1;
- desc->addr_data = ad;
- ad[0] = reg;
+ ad = addr;
+ xfer.xfer.txdata = &ad;
+ xfer.xfer.txcount = 1;
+ }
+ else
+ {
+ /* Raw read from slave */
+ xfer.xfer.txcount = 0;
}
- /* else raw read from slave */
- desc->data = data;
- desc->data_count = data_count;
+ xfer.xfer.rxdata = data;
+ xfer.xfer.rxcount = data_count;
+ xfer.xfer.callback = i2c_sync_xfer_complete_cb;
+ xfer.xfer.next = NULL;
- data_count = i2c_transfer(node, desc);
+ semaphore_init(&xfer.sema, 1, 0);
- mutex_unlock(&desc->m);
+ if (i2c_transfer(&xfer.xfer) && i2c_sync_wait_for_xfer_complete(&xfer))
+ {
+ return data_count - xfer.xfer.rxcount;
+ }
- return data_count;
+ return -1;
}
-int i2c_write(struct i2c_node *node, const unsigned char *data, int data_count)
+int i2c_write(struct i2c_node *node, const unsigned char *data,
+ int data_count)
{
- struct i2c_module_descriptor *const desc = &i2c_descs[node->num];
+ struct i2c_sync_transfer_desc xfer;
- mutex_lock(&desc->m);
+ xfer.xfer.node = node;
+ xfer.xfer.txdata = data;
+ xfer.xfer.txcount = data_count;
+ xfer.xfer.rxcount = 0;
+ xfer.xfer.callback = i2c_sync_xfer_complete_cb;
+ xfer.xfer.next = NULL;
- desc->addr = node->addr & 0xfe; /* Slave address/wr */
- desc->data = (unsigned char *)data;
- desc->data_count = data_count;
+ semaphore_init(&xfer.sema, 1, 0);
- data_count = i2c_transfer(node, desc);
-
- mutex_unlock(&desc->m);
+ if (i2c_transfer(&xfer.xfer) && i2c_sync_wait_for_xfer_complete(&xfer))
+ {
+ return data_count - xfer.xfer.txcount;
+ }
- return data_count;
+ return -1;
}
-void i2c_init(void)
+void INIT_ATTR i2c_init(void)
{
- int i;
-
/* Do one-time inits for each module that will be used - leave
- * module disabled and unclocked until something wants it */
- for (i = 0; i < I2C_NUM_I2C; i++)
+ * module disabled and unclocked until something wants it. */
+ for (int i = 0; i < I2C_NUM_I2C; i++)
{
- struct i2c_module_descriptor *const desc = &i2c_descs[i];
+ struct i2c_module_descriptor * const desc = &i2c_descs[i];
ccm_module_clock_gating(desc->cg, CGM_ON_RUN_WAIT);
- mutex_init(&desc->m);
- semaphore_init(&desc->complete, 1, 0);
desc->base[I2CR] = 0;
ccm_module_clock_gating(desc->cg, CGM_OFF);
}
}
+/* Enable or disable the node - modules will be switched on/off accordingly. */
void i2c_enable_node(struct i2c_node *node, bool enable)
{
- struct i2c_module_descriptor *const desc = &i2c_descs[node->num];
-
- mutex_lock(&desc->m);
+ struct i2c_module_descriptor * const desc = &i2c_descs[node->num];
if (enable)
{
@@ -312,6 +391,8 @@ void i2c_enable_node(struct i2c_node *node, bool enable)
{
/* First enable */
ccm_module_clock_gating(desc->cg, CGM_ON_RUN_WAIT);
+ desc->base[I2CR] = I2C_I2CR_IEN;
+ desc->base[I2SR] = 0;
avic_enable_int(desc->ints, INT_TYPE_IRQ, INT_PRIO_DEFAULT,
desc->handler);
}
@@ -321,24 +402,16 @@ void i2c_enable_node(struct i2c_node *node, bool enable)
if (desc->enable > 0 && --desc->enable == 0)
{
/* Last enable */
- while (desc->base[I2SR] & I2C_I2SR_IBB); /* Wait for STOP */
+
+ /* Wait for last tranfer */
+ while (*(void ** volatile)&desc->head != NULL);
+
+ /* Wait for STOP */
+ while (desc->base[I2SR] & I2C_I2SR_IBB);
+
desc->base[I2CR] &= ~I2C_I2CR_IEN;
avic_disable_int(desc->ints);
ccm_module_clock_gating(desc->cg, CGM_OFF);
}
}
-
- mutex_unlock(&desc->m);
-}
-
-void i2c_lock_node(struct i2c_node *node)
-{
- struct i2c_module_descriptor *const desc = &i2c_descs[node->num];
- mutex_lock(&desc->m);
-}
-
-void i2c_unlock_node(struct i2c_node *node)
-{
- struct i2c_module_descriptor *const desc = &i2c_descs[node->num];
- mutex_unlock(&desc->m);
}
diff --git a/firmware/target/arm/imx31/i2c-imx31.h b/firmware/target/arm/imx31/i2c-imx31.h
index a29c7ce96b..f73a8e6320 100644
--- a/firmware/target/arm/imx31/i2c-imx31.h
+++ b/firmware/target/arm/imx31/i2c-imx31.h
@@ -50,15 +50,46 @@ struct i2c_node
unsigned char addr; /* Slave address on module */
};
+struct i2c_transfer_desc;
+
+typedef void (*i2c_transfer_cb_fn_type)(struct i2c_transfer_desc *);
+
+/* Basic transfer descriptor for normal asynchronous read/write */
+struct i2c_transfer_desc
+{
+ struct i2c_node *node;
+ const unsigned char *txdata;
+ int txcount;
+ unsigned char *rxdata;
+ int rxcount;
+ i2c_transfer_cb_fn_type callback;
+ struct i2c_transfer_desc *next;
+};
+
+/* Extended transfer descriptor for synchronous read/write that handles
+ thread wait and wakeup */
+struct i2c_sync_transfer_desc
+{
+ struct i2c_transfer_desc xfer;
+ struct semaphore sema;
+};
+
+/* One-time init of i2c driver */
void i2c_init(void);
-/* Enable or disable the node - modules will be switch on/off accordingly. */
+
+/* Enable or disable the node - modules will be switched on/off accordingly. */
void i2c_enable_node(struct i2c_node *node, bool enable);
-/* If addr < 0, then raw read */
-int i2c_read(struct i2c_node *node, int addr, unsigned char *data, int count);
-int i2c_write(struct i2c_node *node, const unsigned char *data, int count);
-/* Gain mutually-exclusive access to the node and module to perform multiple
- * operations atomically */
-void i2c_lock_node(struct i2c_node *node);
-void i2c_unlock_node(struct i2c_node *node);
+
+/* Send and/or receive data on the specified node asynchronously */
+bool i2c_transfer(struct i2c_transfer_desc *xfer);
+
+/* Read bytes from a device on the I2C bus, with optional sub-addressing
+ * If addr < 0, then raw read without sub-addressing */
+int i2c_read(struct i2c_node *node, int addr, unsigned char *data,
+ int data_count);
+
+/* Write bytes to a device on the I2C bus */
+int i2c_write(struct i2c_node *node, const unsigned char *data,
+ int data_count);
#endif /* I2C_IMX31_H */