+/*
+ * I2C/TWI comminications driver
+ * Provide read/write to an I2C/TWI device (in this case the ATMega
+ * co-processor). Uses the hardware TWI device in interrupt mode.
+ * NOTES
+ * This code does not support single byte read/write operation.
+ * This code does not support internal register addressing.
+ * Runs at high priority interrupt to minimise chance of early
+ * write termination (have never seen this but...).
+ * For read operations we do not wait for the complete event before
+ * marking the read as over. We do this because the time window for
+ * a read when talking to the ATMega is very tight, so finishing
+ * slightly early avoids a data over-run. It is a little iffy though!
+ */
+
#include "mytypes.h"
#include "twi.h"
#include "aic.h"
-// added by takashic 2008-11-14
// Calculate required clock divisor
#define I2CClk 400000L
#define CLDIV (((CLOCK_FREQUENCY/I2CClk)/2)-3)
+// Pins
+#define TWCK (1 << 4)
+#define TWD (1 << 3)
+
extern void twi_isr_entry(void);
static enum {
TWI_UNINITIALISED = 0,
+ TWI_FAILED,
TWI_IDLE,
- TWI_TX_BUSY,
- TWI_TX_DONE,
+ TWI_DONE,
TWI_RX_BUSY,
- TWI_RX_DONE,
- TWI_FAILED
+ TWI_TX_BUSY,
} twi_state;
-static volatile U32 twi_pending;
-static volatile U8 *twi_ptr;
+static U32 twi_pending;
+static U8 *twi_ptr;
+static U32 twi_mask;
-static volatile struct {
+// Accumlate stats
+#ifdef USE_STATS
+static struct {
U32 rx_done;
U32 tx_done;
U32 bytes_tx;
U32 ovre;
U32 nack;
} twi_stats;
+#define STATS(code) code;
+#else
+#define STATS(code)
+#endif
-
+/**
+ * Return the status of the twi device.
+ * 0 == Ready for use
+ * 1 == Busy
+ * -1 == Error or closed
+ */
int
-twi_busy(void)
+twi_status(void)
{
- return (twi_state == TWI_TX_BUSY || twi_state == TWI_RX_BUSY);
-}
-
-int
-twi_ok(void)
-{
- return (twi_state >= TWI_IDLE && twi_state <= TWI_RX_DONE);
+ return (twi_state > TWI_DONE ? 1 : (twi_state < TWI_IDLE ? -1 : 0));
}
+/**
+ * Process TWI interrupts.
+ * Assumes that only valid interrupts will be enabled and that twi_mask
+ * will have been set to only contain the valid bits for the current
+ * I/O state. This means that we do not have to test this state at
+ * interrupt time.
+ */
void
twi_isr_C(void)
{
- volatile U32 status = *AT91C_TWI_SR;
-
- if ((status & AT91C_TWI_RXRDY) && twi_state == TWI_RX_BUSY) {
-
-
- if (twi_pending) {
- twi_stats.bytes_rx++;
- *twi_ptr = *AT91C_TWI_RHR;
- twi_ptr++;
- twi_pending--;
- if (twi_pending == 1) {
- /* second last byte -- issue a stop on the next byte */
- *AT91C_TWI_CR = AT91C_TWI_STOP;
- }
- if (!twi_pending) {
- twi_stats.rx_done++;
- twi_state = TWI_RX_DONE;
- }
+ U32 status = *AT91C_TWI_SR & twi_mask;
+ if (status & AT91C_TWI_RXRDY) {
+ STATS(twi_stats.bytes_rx++)
+ *twi_ptr++ = *AT91C_TWI_RHR;
+ twi_pending--;
+ if (twi_pending == 1) {
+ /* second last byte -- issue a stop on the next byte */
+ *AT91C_TWI_CR = AT91C_TWI_STOP;
+ }
+ if (!twi_pending) {
+ // All bytes have been sent. Mark operation as complete.
+ STATS(twi_stats.rx_done++)
+ twi_state = TWI_DONE;
+ *AT91C_TWI_IDR = AT91C_TWI_RXRDY;
}
-
}
-
- if ((status & AT91C_TWI_TXRDY) && twi_state == TWI_TX_BUSY) {
+ else if (status & AT91C_TWI_TXRDY) {
if (twi_pending) {
/* Still Stuff to send */
- *AT91C_TWI_CR = AT91C_TWI_MSEN | AT91C_TWI_START;
- if (twi_pending == 1) {
- *AT91C_TWI_CR = AT91C_TWI_STOP;
- }
- *AT91C_TWI_THR = *twi_ptr;
- twi_stats.bytes_tx++;
-
- twi_ptr++;
+ *AT91C_TWI_THR = *twi_ptr++;
twi_pending--;
-
+ STATS(twi_stats.bytes_tx++)
} else {
- /* everything has been sent */
- twi_state = TWI_TX_DONE;
- *AT91C_TWI_IDR = ~0;
- twi_stats.tx_done++;
+ // everything has been sent, now wait for complete
+ STATS(twi_stats.tx_done++);
+ *AT91C_TWI_IDR = AT91C_TWI_TXRDY;
+ *AT91C_TWI_IER = AT91C_TWI_TXCOMP;
+ twi_mask = AT91C_TWI_TXCOMP|AT91C_TWI_NACK;
}
}
-
-#if 0
- if (status & AT91C_TWI_OVRE) {
- /* */
- twi_stats.ovre++;
- *AT91C_TWI_CR = AT91C_TWI_STOP;
- *AT91C_TWI_IDR = ~0;
- twi_state = TWI_FAILED;
-
+ else if (status & AT91C_TWI_TXCOMP) {
+ twi_state = TWI_DONE;
+ *AT91C_TWI_IDR = AT91C_TWI_TXCOMP;
}
-#else /* patch */
-#endif
-
-#if 0
- if (status & AT91C_TWI_UNRE) {
- /* */
- twi_stats.unre++;
- *AT91C_TWI_IDR = ~0;
- twi_state = TWI_FAILED;
- }
-#else /* patch */
-#endif
if (status & AT91C_TWI_NACK) {
- /* */
- twi_stats.nack++;
+ STATS(twi_stats.nack++)
*AT91C_TWI_IDR = ~0;
twi_state = TWI_UNINITIALISED;
}
}
-
+/**
+ * Force a device reset.
+ */
void
twi_reset(void)
{
- volatile U32 clocks = 9;
+ U32 clocks = 9;
*AT91C_TWI_IDR = ~0;
- *AT91C_PMC_PCER = (1 << AT91C_PERIPHERAL_ID_PIOA) | /* Need PIO too */
- (1 << AT91C_PERIPHERAL_ID_TWI); /* TWI clock domain */
+ *AT91C_PMC_PCER = (1 << AT91C_ID_PIOA) | /* Need PIO too */
+ (1 << AT91C_ID_TWI); /* TWI clock domain */
/* Set up pin as an IO pin for clocking till clean */
- *AT91C_PIOA_MDER = (1 << 3) | (1 << 4);
- *AT91C_PIOA_PER = (1 << 3) | (1 << 4);
- *AT91C_PIOA_ODR = (1 << 3);
- *AT91C_PIOA_OER = (1 << 4);
+ *AT91C_PIOA_MDER = TWD | TWCK;
+ *AT91C_PIOA_PER = TWD | TWCK;
+ *AT91C_PIOA_ODR = TWD;
+ *AT91C_PIOA_OER = TWCK;
- while (clocks > 0 && !(*AT91C_PIOA_PDSR & (1 << 3))) {
+ while (clocks > 0 && !(*AT91C_PIOA_PDSR & TWD)) {
- *AT91C_PIOA_CODR = (1 << 4);
+ *AT91C_PIOA_CODR = TWCK;
systick_wait_ns(1500);
- *AT91C_PIOA_SODR = (1 << 4);
+ *AT91C_PIOA_SODR = TWCK;
systick_wait_ns(1500);
clocks--;
}
- *AT91C_PIOA_PDR = (1 << 3) | (1 << 4);
- *AT91C_PIOA_ASR = (1 << 3) | (1 << 4);
+ *AT91C_PIOA_PDR = TWD | TWCK;
+ *AT91C_PIOA_ASR = TWD | TWCK;
- *AT91C_TWI_CR = 0x88; /* Disable & reset */
+ *AT91C_TWI_CR = AT91C_TWI_SWRST|AT91C_TWI_MSDIS;/* Disable & reset */
-// changed by takashic 2008-11-14
-// *AT91C_TWI_CWGR = 0x020f0f; /* Set for 380kHz */
*AT91C_TWI_CWGR = ((CLDIV << 8)|CLDIV); /* Set for 400kHz */
-
- *AT91C_TWI_CR = 0x04; /* Enable as master */
+ *AT91C_TWI_CR = AT91C_TWI_MSEN; /* Enable as master */
+ *AT91C_TWI_IER = AT91C_TWI_NACK;
+ twi_mask = 0;
}
+/**
+ * Initialize the device.
+ */
int
twi_init(void)
{
- volatile int i_state;
+ int i_state;
i_state = interrupts_get_and_disable();
- /* Todo: set up interrupt */
*AT91C_TWI_IDR = ~0; /* Disable all interrupt sources */
- aic_mask_off(AT91C_PERIPHERAL_ID_TWI);
- aic_set_vector(AT91C_PERIPHERAL_ID_TWI, AIC_INT_LEVEL_ABOVE_NORMAL,
- (U32) twi_isr_entry);
- aic_mask_on(AT91C_PERIPHERAL_ID_TWI);
+ aic_mask_off(AT91C_ID_TWI);
+ aic_set_vector(AT91C_ID_TWI, AIC_INT_LEVEL_ABOVE_NORMAL,
+ (int)twi_isr_entry);
+ aic_mask_on(AT91C_ID_TWI);
twi_reset();
}
-
+/**
+ * Start a read operation to the device. The operation will complete
+ * asynchronously and can be monitored using twi_status. Note that we
+ * do not support single byte reads, or internal register addresses.
+ */
void
-twi_start_read(U32 dev_addr, U32 int_addr_bytes, U32 int_addr, U8 *data,
- U32 nBytes)
+twi_start_read(U32 dev_addr, U8 *data, U32 nBytes)
{
- volatile U32 mode =
- ((dev_addr & 0x7f) << 16) | ((int_addr_bytes & 3) << 8) | (1 << 12);
- volatile U32 dummy;
-
- if (!twi_busy()) {
-
+ if (twi_state < TWI_RX_BUSY) {
twi_state = TWI_RX_BUSY;
- *AT91C_TWI_IDR = ~0; /* Disable all interrupts */
twi_ptr = data;
twi_pending = nBytes;
- dummy = *AT91C_TWI_SR;
- dummy = *AT91C_TWI_RHR;
-// *AT91C_AIC_ICCR = ( 1<< AT91C_PERIPHERAL_ID_TWI);
- *AT91C_TWI_MMR = mode;
- *AT91C_TWI_CR = AT91C_TWI_START | AT91C_TWI_MSEN;
-// dummy = *AT91C_TWI_SR;
- *AT91C_TWI_IER = 0x01C2;
+ *AT91C_TWI_MMR = AT91C_TWI_IADRSZ_NO|AT91C_TWI_MREAD|((dev_addr & 0x7f) << 16);
+ twi_mask = AT91C_TWI_RXRDY|AT91C_TWI_NACK;
+ *AT91C_TWI_CR = AT91C_TWI_START;
+ *AT91C_TWI_IER = AT91C_TWI_RXRDY;
}
}
+/**
+ * Start a write operation to the device. The operation will complete
+ * asynchronously and can be monitored using twi_status. Note that we
+ * do not support single byte reads, or internal register addresses.
+ */
void
-twi_start_write(U32 dev_addr, U32 int_addr_bytes, U32 int_addr,
- const U8 *data, U32 nBytes)
+twi_start_write(U32 dev_addr, const U8 *data, U32 nBytes)
{
- volatile U32 mode = ((dev_addr & 0x7f) << 16) | ((int_addr_bytes & 3) << 8);
-
- if (!twi_busy()) {
+ if (twi_state < TWI_RX_BUSY) {
twi_state = TWI_TX_BUSY;
- *AT91C_TWI_IDR = ~0; /* Disable all interrupts */
- twi_ptr = data;
+ twi_ptr = (U8 *)data;
twi_pending = nBytes;
- *AT91C_TWI_MMR = mode;
- *AT91C_TWI_CR = AT91C_TWI_START | AT91C_TWI_MSEN;
- *AT91C_TWI_IER = 0x1C4;
+ *AT91C_TWI_MMR = AT91C_TWI_IADRSZ_NO|((dev_addr & 0x7f) << 16);
+ *AT91C_TWI_THR = *twi_ptr++;
+ twi_pending--;
+ STATS(twi_stats.bytes_tx++)
+ twi_mask = AT91C_TWI_TXRDY|AT91C_TWI_NACK;
+ *AT91C_TWI_IER = AT91C_TWI_TXRDY;
}
}