diff options
author | Amaury Pouly <amaury.pouly@gmail.com> | 2016-05-02 21:57:55 +0100 |
---|---|---|
committer | Gerrit Rockbox <gerrit@rockbox.org> | 2016-06-01 22:55:37 +0200 |
commit | 4d42e3685c55a6de9d05003f8482f7fb1be022fd (patch) | |
tree | 121c5fbe8a7f63a452fc523571962ecc60bd625c /firmware/target/arm | |
parent | 615c638c7da1be395a13dd107be004b03a9c390d (diff) | |
download | rockbox-4d42e3685c55a6de9d05003f8482f7fb1be022fd.tar.gz 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')
-rw-r--r-- | firmware/target/arm/imx233/fmradio-imx233.c | 2 | ||||
-rw-r--r-- | firmware/target/arm/imx233/i2c-imx233.c | 362 | ||||
-rw-r--r-- | firmware/target/arm/imx233/i2c-imx233.h | 70 |
3 files changed, 356 insertions, 78 deletions
diff --git a/firmware/target/arm/imx233/fmradio-imx233.c b/firmware/target/arm/imx233/fmradio-imx233.c index 5263256ca3..85bad41653 100644 --- a/firmware/target/arm/imx233/fmradio-imx233.c +++ b/firmware/target/arm/imx233/fmradio-imx233.c @@ -24,7 +24,7 @@ #include "fmradio-imx233.h" #include "fmradio-target.h" #include "pinctrl-imx233.h" -#include "i2c.h" +#include "i2c-imx233.h" #include "generic_i2c.h" #ifndef IMX233_FMRADIO_I2C 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); } diff --git a/firmware/target/arm/imx233/i2c-imx233.h b/firmware/target/arm/imx233/i2c-imx233.h index 263e93aa77..33e38e38da 100644 --- a/firmware/target/arm/imx233/i2c-imx233.h +++ b/firmware/target/arm/imx233/i2c-imx233.h @@ -24,7 +24,6 @@ #include "cpu.h" #include "system.h" #include "system-target.h" -#include "i2c.h" enum imx233_i2c_error_t { @@ -35,14 +34,71 @@ enum imx233_i2c_error_t I2C_NO_SLAVE_ACK = -4, I2C_SLAVE_NAK = -5, I2C_BUFFER_FULL = -6, + I2C_SKIP = -7, /* transfer skipped before of previous error in transaction */ }; +/** Important notes on the driver. + * + * The driver supports both synchronous and asynchronous transfers. Asynchronous + * transfer functions can safely be called from IRQ context. Beware that the completion + * callback of asynchronous transfers may be called from IRQ context. + * + * The driver supports queuing so it is safe to call several transfer functions + * concurrently. + */ + void imx233_i2c_init(void); -/* start building a transfer, will acquire an exclusive lock */ -void imx233_i2c_begin(void); -/* add stage */ -enum imx233_i2c_error_t imx233_i2c_add(bool start, bool transmit, void *buffer, unsigned size, bool stop); -/* end building a transfer and start the transfer */ -enum imx233_i2c_error_t imx233_i2c_end(unsigned timeout); + +/** legacy API: + * read/write functions return 0 on success */ +int i2c_write(int device, const unsigned char* buf, int count); +int i2c_read(int device, unsigned char* buf, int count); +int i2c_readmem(int device, int address, unsigned char* buf, int count); +int i2c_writemem(int device, int address, const unsigned char* buf, int count); + +/** Advanced API */ +struct imx233_i2c_xfer_t; +typedef void (*imx233_i2c_cb_t)(struct imx233_i2c_xfer_t *xfer, enum imx233_i2c_error_t status); + +/** Transfer mode. There are currently two types of transfers, to make + * programming simpler: + * - write: write count[0] bytes from data[0], and then count[1] bytes from data[1] + * (if count[1] > 0). The second stage is useful to avoid allocating a single + * buffer to hold address + data for example. + * - read: write count[0] bytes from data[0], and then read count[1] bytes from data[1]. + * If count[0] = 0 then the write stage is ignored. + */ +enum imx233_i2x_xfer_mode_t +{ + I2C_WRITE, + I2C_READ, +}; + +/** This data structure represents one transfer. The exact shape of the transfer + * depends on the mode. + * NOTE a single transfer is limited to 512 bytes of data (RX + TX) + * A transaction is made up of several transfers, chained together. + */ +struct imx233_i2c_xfer_t +{ + struct imx233_i2c_xfer_t *next; /* next transfer, or NULL */ + imx233_i2c_cb_t callback; /* NULL for no callack */ + int dev_addr; /* device address */ + bool fast_mode; /* 400 kHz 'fast' mode or 100 kHz 'normal' mode */ + enum imx233_i2x_xfer_mode_t mode; /* transfer mode */ + int count[2]; /* count: depends on mode */ + void *data[2]; /* data: depends on mode */ + unsigned tmo_ms; /* timeout in milliseconds (0 means infinite) */ + /* internal */ + struct imx233_i2c_xfer_t *last; /* last in transaction */ +}; + +/* Queue one or more tranfer. If several transfer are queued (transaction) + * they are guaranteed to be executed "as one" (ie without interleaving). Furthermore, + * if a transfer of the transfaction fails, the remaining transfers of the transactions + * will NOT be executed (and their callback will be called with SKIP status). + * Return 0 if queueing was successful. Note that the transfer may still fail, + * in which case the callback will have a nonzero status code. */ +void imx233_i2c_transfer(struct imx233_i2c_xfer_t *xfer); #endif // __I2C_IMX233_H__ |