summaryrefslogtreecommitdiffstats
path: root/firmware/target/arm/imx233/i2c-imx233.c
diff options
context:
space:
mode:
authorAmaury Pouly <amaury.pouly@gmail.com>2016-05-02 21:57:55 +0100
committerGerrit Rockbox <gerrit@rockbox.org>2016-06-01 22:55:37 +0200
commit4d42e3685c55a6de9d05003f8482f7fb1be022fd (patch)
tree121c5fbe8a7f63a452fc523571962ecc60bd625c /firmware/target/arm/imx233/i2c-imx233.c
parent615c638c7da1be395a13dd107be004b03a9c390d (diff)
downloadrockbox-4d42e3685c55a6de9d05003f8482f7fb1be022fd.tar.gz
rockbox-4d42e3685c55a6de9d05003f8482f7fb1be022fd.tar.bz2
rockbox-4d42e3685c55a6de9d05003f8482f7fb1be022fd.zip
imx233: rewrite i2c driver
The new driver provides several new features: - asynchronous transfer - transactions (several transfers executed at once) - queueing The style still provides the legacy interface. Change-Id: I6d8ecc89d1f7057847c9b2dc69b76cd45c9c8407
Diffstat (limited to 'firmware/target/arm/imx233/i2c-imx233.c')
-rw-r--r--firmware/target/arm/imx233/i2c-imx233.c362
1 files changed, 292 insertions, 70 deletions
diff --git a/firmware/target/arm/imx233/i2c-imx233.c b/firmware/target/arm/imx233/i2c-imx233.c
index d4e20d8a21..d6f0830ede 100644
--- a/firmware/target/arm/imx233/i2c-imx233.c
+++ b/firmware/target/arm/imx233/i2c-imx233.c
@@ -45,6 +45,166 @@
* are cache aligned and will copy back the data to user buffers at the end.
* The I2C_BUFFER_SIZE define controls the size of the buffer. All transfers
* should probably fit within 512 bytes.
+ *
+ * On top of this, transfers are queued using the 'next' field of imx233_i2c_xfer_t.
+ * Each time a transfer is programmed, it is translated to dma transfers using
+ * the dma API.
+ */
+
+/**
+ * Internal DMA API to build the transfer.
+ * NOTE the api does not perform any locking, it is up to the caller to ensure
+ * that there only one transfer beint built at any time.
+ */
+
+/* start building a transfer */
+static void imx233_i2c_begin(void);
+/* add a stage
+ * NOTE for transmit, the data is copied to a buffer so the buffer can be freed
+ * afer this function return. For receive, buffer must exists until transfer is
+ * complete. This function assumes any receive transfer is final (master will
+ * send NAK). */
+static void imx233_i2c_add(bool start, bool transmit, void *buffer, unsigned size, bool stop);
+/* end building a transfer and start the transfer */
+static void imx233_i2c_kick(void);
+/* abort transfer (will call imx233_i2c_transfer_complete) */
+static void imx233_i2c_abort(void);
+/* set speed */
+static void imx233_i2c_set_speed(bool fast_mode);
+/* callback function when transfer is finished */
+static void imx233_i2c_transfer_complete(enum imx233_i2c_error_t status);
+
+/**
+ * Advanced API
+ */
+
+/* NOTE these variables are not marked as volatile because all functions
+ * do all operation with IRQ disabled, so they won't change their value
+ * in the middle of a function */
+static struct imx233_i2c_xfer_t *i2c_head; /* pointer to the current transfer */
+static struct imx233_i2c_xfer_t *i2c_tail; /* pointer to the last transfer */
+static struct timeout i2c_tmo; /* timeout */
+
+/* timeout callback */
+static int imx233_i2c_timeout(struct timeout *tmo);
+
+/* called in IRQ context or with IRQ disabled */
+static void imx233_i2c_start(void)
+{
+ uint8_t addr_wr = i2c_head->dev_addr;
+ uint8_t addr_rd = i2c_head->dev_addr | 1;
+ /* translate transfer using DMA API */
+ imx233_i2c_set_speed(i2c_head->fast_mode);
+ imx233_i2c_begin();
+ if(i2c_head->mode == I2C_WRITE)
+ {
+ /* START + addr */
+ imx233_i2c_add(true, true, &addr_wr, 1, false);
+ /* data + stop if no second stage */
+ imx233_i2c_add(false, true, i2c_head->data[0], i2c_head->count[0], i2c_head->count[1] == 0);
+ /* (if second stage) data + stop */
+ if(i2c_head->count[1] > 0)
+ imx233_i2c_add(false, true, i2c_head->data[1], i2c_head->count[1], true);
+ }
+ else /* I2C_READ */
+ {
+ /* (if write stage) */
+ if(i2c_head->count[0] > 0)
+ {
+ /* START + addr */
+ imx233_i2c_add(true, true, &addr_wr, 1, false);
+ /* data */
+ imx233_i2c_add(false, true, i2c_head->data[0], i2c_head->count[0], false);
+ }
+ /* START + addr */
+ imx233_i2c_add(true, true, &addr_rd, 1, false);
+ /* read data + stop */
+ imx233_i2c_add(false, false, i2c_head->data[1], i2c_head->count[1], true);
+ }
+ /* kick transfer */
+ imx233_i2c_kick();
+ /* setup timer for timeout */
+ if(i2c_head->tmo_ms > 0)
+ timeout_register(&i2c_tmo, imx233_i2c_timeout, i2c_head->tmo_ms * HZ / 1000, 0);
+}
+
+/* unqueue head and notify completion, called with IRQ disabled */
+static void imx233_i2c_unqueue_head(enum imx233_i2c_error_t status)
+{
+ /* notify */
+ if(i2c_head->callback)
+ i2c_head->callback(i2c_head, status);
+ /* unqueue */
+ i2c_head = i2c_head->next;
+}
+
+/* callback function when transfer is finished, called with IRQ disabled */
+static void imx233_i2c_transfer_complete(enum imx233_i2c_error_t status)
+{
+ /* cancel timeout
+ * NOTE because IRQ are disabled, the timeout callback will not be called
+ * until we enable them back, at which point we will have disabled the timeout
+ * so the completion routine will not be called twice. */
+ if(i2c_head->tmo_ms > 0)
+ timeout_cancel(&i2c_tmo);
+ /* notify completion and unqueue
+ * WARNING completion callback can queue other transfers, so the only part
+ * of the queue that cannot change is this transaction, everything else can
+ * change */
+ struct imx233_i2c_xfer_t *this_xfer = i2c_head;
+ struct imx233_i2c_xfer_t *last_xfer = i2c_head->last; /* in transaction */
+ /* unqueue head */
+ imx233_i2c_unqueue_head(status);
+ /* in case of failure, skip others */
+ if(status != I2C_SUCCESS && this_xfer != last_xfer)
+ while(i2c_head != last_xfer)
+ imx233_i2c_unqueue_head(I2C_SKIP);
+ /* if there is anything left, start it */
+ if(i2c_head)
+ imx233_i2c_start();
+}
+
+static int imx233_i2c_timeout(struct timeout *tmo)
+{
+ (void) tmo;
+ imx233_i2c_abort();
+ return 0; /* do not fire again */
+}
+
+void imx233_i2c_transfer(struct imx233_i2c_xfer_t *xfer)
+{
+ /* avoid any race with the irq handler */
+ unsigned long cpsr = disable_irq_save();
+ /* before queuing, update link to last transfer in each transfer */
+ struct imx233_i2c_xfer_t *last = xfer;
+ while(last->next)
+ last = last->next;
+ struct imx233_i2c_xfer_t *tmp = xfer;
+ while(tmp)
+ {
+ tmp->last = last;
+ tmp = tmp->next;
+ }
+ /* no transfer pending: start one */
+ if(i2c_head == NULL)
+ {
+ i2c_head = xfer;
+ i2c_tail = last;
+ /* kick transfer now */
+ imx233_i2c_start();
+ }
+ /* pending transer: queue and let the irq handler process it for us */
+ else
+ {
+ i2c_tail->next = xfer;
+ i2c_tail = last;
+ }
+
+ restore_irq(cpsr);
+}
+
+/**
+ * DMA API implementation
*/
/* Used for DMA */
@@ -53,7 +213,7 @@ struct i2c_dma_command_t
struct apb_dma_command_t dma;
/* PIO words */
uint32_t ctrl0;
- /* copy buffer copy */
+ /* copy buffer pointers */
void *src;
void *dst;
/* padded to next multiple of cache line size (32 bytes) */
@@ -67,31 +227,9 @@ __ENSURE_STRUCT_CACHE_FRIENDLY(struct i2c_dma_command_t)
/* Current transfer */
static int i2c_nr_stages;
static struct i2c_dma_command_t i2c_stage[I2C_NR_STAGES];
-static struct mutex i2c_mutex;
-static struct semaphore i2c_sema;
static uint8_t i2c_buffer[I2C_BUFFER_SIZE] CACHEALIGN_ATTR;
static uint32_t i2c_buffer_end; /* current end */
-void INT_I2C_DMA(void)
-{
- /* reset dma channel on error */
- if(imx233_dma_is_channel_error_irq(APB_I2C))
- imx233_dma_reset_channel(APB_I2C);
- /* clear irq flags */
- imx233_dma_clear_channel_interrupt(APB_I2C);
- semaphore_release(&i2c_sema);
-}
-
-void INT_I2C_ERROR(void)
-{
- /* reset dma channel on error */
- if(imx233_dma_is_channel_error_irq(APB_I2C))
- imx233_dma_reset_channel(APB_I2C);
- /* clear irq flags */
- imx233_dma_clear_channel_interrupt(APB_I2C);
- semaphore_release(&i2c_sema);
-}
-
static void imx233_i2c_reset(void)
{
/* clear softreset */
@@ -107,10 +245,6 @@ static void imx233_i2c_reset(void)
BF_SET(I2C_CTRL1, ACK_MODE);
#endif
BF_SET(I2C_CTRL0, CLKGATE);
- /* Fast-mode @ 400K */
- HW_I2C_TIMING0 = 0x000F0007; /* tHIGH=0.6us, read at 0.3us */
- HW_I2C_TIMING1 = 0x001F000F; /* tLOW=1.3us, write at 0.6us */
- HW_I2C_TIMING2 = 0x0015000D;
}
void imx233_i2c_init(void)
@@ -120,31 +254,27 @@ void imx233_i2c_init(void)
imx233_pinctrl_setup_vpin(VPIN_I2C_SCL, "i2c scl", PINCTRL_DRIVE_4mA, true);
imx233_pinctrl_setup_vpin(VPIN_I2C_SDA, "i2c sda", PINCTRL_DRIVE_4mA, true);
imx233_i2c_reset();
- mutex_init(&i2c_mutex);
- semaphore_init(&i2c_sema, 1, 0);
+ i2c_head = i2c_tail = NULL;
}
-void imx233_i2c_begin(void)
+static void imx233_i2c_begin(void)
{
- mutex_lock(&i2c_mutex);
/* wakeup */
BF_CLR(I2C_CTRL0, CLKGATE);
i2c_nr_stages = 0;
i2c_buffer_end = 0;
}
-enum imx233_i2c_error_t imx233_i2c_add(bool start, bool transmit, void *buffer, unsigned size, bool stop)
+static void imx233_i2c_add(bool start, bool transmit,
+ void *buffer, unsigned size, bool stop)
{
if(i2c_nr_stages == I2C_NR_STAGES)
- return I2C_ERROR;
+ panicf("i2c: too many stages");
/* align buffer end on cache boundary */
uint32_t start_off = CACHEALIGN_UP(i2c_buffer_end);
uint32_t end_off = start_off + size;
if(end_off > I2C_BUFFER_SIZE)
- {
- panicf("die");
- return I2C_BUFFER_FULL;
- }
+ panicf("i2c: transfer is too big");
i2c_buffer_end = end_off;
if(transmit)
{
@@ -175,7 +305,6 @@ enum imx233_i2c_error_t imx233_i2c_add(bool start, bool transmit, void *buffer,
XFER_COUNT(size), DIRECTION(transmit), SEND_NAK_ON_LAST(!transmit),
PRE_SEND_START(start), POST_SEND_STOP(stop), MASTER_MODE(1));
i2c_nr_stages++;
- return I2C_SUCCESS;
}
static enum imx233_i2c_error_t imx233_i2c_finalize(void)
@@ -190,10 +319,10 @@ static enum imx233_i2c_error_t imx233_i2c_finalize(void)
return I2C_SUCCESS;
}
-enum imx233_i2c_error_t imx233_i2c_end(unsigned timeout)
+static void imx233_i2c_kick(void)
{
if(i2c_nr_stages == 0)
- return I2C_ERROR;
+ panicf("i2c: empty kick");
i2c_stage[i2c_nr_stages - 1].dma.cmd |= BM_APB_CHx_CMD_SEMAPHORE | BM_APB_CHx_CMD_IRQONCMPLT;
BF_CLR(I2C_CTRL1, SLAVE_IRQ, SLAVE_STOP_IRQ, MASTER_LOSS_IRQ, EARLY_TERM_IRQ,
@@ -203,15 +332,24 @@ enum imx233_i2c_error_t imx233_i2c_end(unsigned timeout)
imx233_icoll_enable_interrupt(INT_SRC_I2C_ERROR, true);
imx233_dma_enable_channel_interrupt(APB_I2C, true);
imx233_dma_start_command(APB_I2C, &i2c_stage[0].dma);
+}
+static void imx233_i2c_abort(void)
+{
+ /* FIXME there is a race condition here: if dma irq fires right before we
+ * reset the channel, it will most likely trigger an IRQ anyway. It is
+ * extremely unlikely but ideally, we should check this in the IRQ handler
+ * with an id/counter. */
+ imx233_dma_reset_channel(APB_I2C);
+ imx233_i2c_reset();
+ imx233_i2c_transfer_complete(I2C_TIMEOUT);
+}
+
+static enum imx233_i2c_error_t imx233_i2c_end(void)
+{
enum imx233_i2c_error_t ret;
- if(semaphore_wait(&i2c_sema, timeout) == OBJ_WAIT_TIMEDOUT)
- {
- imx233_dma_reset_channel(APB_I2C);
- imx233_i2c_reset();
- ret = I2C_TIMEOUT;
- }
- else if(BF_RD(I2C_CTRL1, MASTER_LOSS_IRQ))
+ /* check for various errors */
+ if(BF_RD(I2C_CTRL1, MASTER_LOSS_IRQ))
ret = I2C_MASTER_LOSS;
else if(BF_RD(I2C_CTRL1, NO_SLAVE_ACK_IRQ))
{
@@ -229,48 +367,132 @@ enum imx233_i2c_error_t imx233_i2c_end(unsigned timeout)
ret = imx233_i2c_finalize();
/* sleep */
BF_SET(I2C_CTRL0, CLKGATE);
- mutex_unlock(&i2c_mutex);
return ret;
}
+static void imx233_i2c_set_speed(bool fast_mode)
+{
+ /* See I2C specification for standard- and fast-mode timings
+ * Clock is derived APBX which we assume to be running at 24 MHz. */
+ if(fast_mode)
+ {
+ /* Fast-mode @ 400 kHz */
+ HW_I2C_TIMING0 = 0x000f0007; /* HIGH_COUNT=0.6us, RCV_COUNT=0.2us */
+ HW_I2C_TIMING1 = 0x001f000f; /* LOW_COUNT=1.3us, XMIT_COUNT=0.6us */
+ HW_I2C_TIMING2 = 0x0015000d; /* BUS_FREE=0.9us LEADIN_COUNT=0.55us */
+ }
+ else
+ {
+ /* Standard-mode @ 100 kHz */
+ HW_I2C_TIMING0 = 0x00780030; /* HIGH_COUNT=5us, RCV_COUNT=2us */
+ HW_I2C_TIMING1 = 0x00800030; /* LOW_COUNT=5.3us, XMIT_COUNT=2us */
+ HW_I2C_TIMING2 = 0x00300030; /* BUS_FREE=2us LEADIN_COUNT=2us */
+ }
+}
+
+static void imx233_i2c_irq(bool err)
+{
+ if(err)
+ panicf("i2c: dma error");
+ /* reset dma channel on error */
+ if(imx233_dma_is_channel_error_irq(APB_I2C))
+ imx233_dma_reset_channel(APB_I2C);
+ /* clear irq flags */
+ imx233_dma_clear_channel_interrupt(APB_I2C);
+ /* handle completion */
+ imx233_i2c_transfer_complete(imx233_i2c_end());
+}
+
+void INT_I2C_DMA(void)
+{
+ imx233_i2c_irq(false);
+}
+
+void INT_I2C_ERROR(void)
+{
+ imx233_i2c_irq(true);
+}
+
+/** Public API */
+
void i2c_init(void)
{
}
+struct imx233_i2c_sync_xfer_t
+{
+ struct imx233_i2c_xfer_t xfer;
+ struct semaphore sema;
+ volatile enum imx233_i2c_error_t status;
+};
+
+/* synchronous callback: record status and release semaphore */
+static void i2c_sync_callback(struct imx233_i2c_xfer_t *xfer, enum imx233_i2c_error_t status)
+{
+ struct imx233_i2c_sync_xfer_t *sxfer = (void *)xfer;
+ sxfer->status = status;
+ semaphore_release(&sxfer->sema);
+}
+
+static int i2c_sync_transfer(struct imx233_i2c_sync_xfer_t *xfer)
+{
+ semaphore_init(&xfer->sema, 1, 0);
+ /* common init */
+ xfer->xfer.next = NULL;
+ xfer->xfer.callback = &i2c_sync_callback;
+ xfer->xfer.fast_mode = true;
+ xfer->xfer.tmo_ms = 1000;
+ /* kick */
+ imx233_i2c_transfer(&xfer->xfer);
+ /* wait */
+ semaphore_wait(&xfer->sema, TIMEOUT_BLOCK);
+ return (int)xfer->status;
+}
+
int i2c_write(int device, const unsigned char* buf, int count)
{
- uint8_t addr = device;
- imx233_i2c_begin();
- imx233_i2c_add(true, true, &addr, 1, false); /* start + dev addr */
- imx233_i2c_add(false, true, (void *)buf, count, true); /* data + stop */
- return imx233_i2c_end(10);
+ struct imx233_i2c_sync_xfer_t xfer;
+ xfer.xfer.dev_addr = device;
+ xfer.xfer.mode = I2C_WRITE;
+ xfer.xfer.count[0] = count;
+ xfer.xfer.data[0] = (void *)buf;
+ xfer.xfer.count[1] = 0;
+ return i2c_sync_transfer(&xfer);
}
int i2c_read(int device, unsigned char* buf, int count)
{
- uint8_t addr = device | 1;
- imx233_i2c_begin();
- imx233_i2c_add(true, true, &addr, 1, false); /* start + dev addr */
- imx233_i2c_add(false, false, buf, count, true); /* data + stop */
- return imx233_i2c_end(10);
+ struct imx233_i2c_sync_xfer_t xfer;
+ xfer.xfer.dev_addr = device;
+ xfer.xfer.mode = I2C_READ;
+ xfer.xfer.count[0] = 0;
+ xfer.xfer.count[1] = count;
+ xfer.xfer.data[1] = buf;
+ return i2c_sync_transfer(&xfer);
}
int i2c_readmem(int device, int address, unsigned char* buf, int count)
{
- uint8_t start[2] = {device, address};
- uint8_t addr_rd = device | 1;
- imx233_i2c_begin();
- imx233_i2c_add(true, true, start, 2, false); /* start + dev addr + addr */
- imx233_i2c_add(true, true, &addr_rd, 1, false); /* start + dev addr */
- imx233_i2c_add(false, false, buf, count, true); /* data + stop */
- return imx233_i2c_end(10);
+ uint8_t addr = address; /* assume 1 byte */
+ struct imx233_i2c_sync_xfer_t xfer;
+ xfer.xfer.dev_addr = device;
+ xfer.xfer.mode = I2C_READ;
+ xfer.xfer.count[0] = 1;
+ xfer.xfer.data[0] = &addr;
+ xfer.xfer.count[1] = count;
+ xfer.xfer.data[1] = buf;
+ return i2c_sync_transfer(&xfer);
}
int i2c_writemem(int device, int address, const unsigned char* buf, int count)
{
- uint8_t start[2] = {device, address};
- imx233_i2c_begin();
- imx233_i2c_add(true, true, start, 2, false); /* start + dev addr + addr */
- imx233_i2c_add(false, true, (void *)buf, count, true); /* data + stop */
- return imx233_i2c_end(10);
+ uint8_t addr = address; /* assume 1 byte */
+ struct imx233_i2c_sync_xfer_t xfer;
+ xfer.xfer.dev_addr = device;
+ xfer.xfer.mode = I2C_WRITE;
+ xfer.xfer.count[0] = 1;
+ xfer.xfer.data[0] = &addr;
+ xfer.xfer.count[1] = count;
+ xfer.xfer.data[1] = (void *)buf;
+ return i2c_sync_transfer(&xfer);
}