diff options
| author | Tobias Diedrich <ranma+coreboot@tdiedrich.de> | 2010-03-23 04:40:38 +0000 |
|---|---|---|
| committer | Tobias Diedrich <ranma+coreboot@tdiedrich.de> | 2010-03-23 04:40:38 +0000 |
| commit | 946d3d177e060d375d6e435879bc203a01e1b2de (patch) | |
| tree | 58121491aa3186c2a7a24087b6db88709aa7dd6d | |
| parent | dafca1405e6fa342b5740e74be43f0969b4606c8 (diff) | |
| download | rockbox-946d3d177e060d375d6e435879bc203a01e1b2de.zip rockbox-946d3d177e060d375d6e435879bc203a01e1b2de.tar.gz rockbox-946d3d177e060d375d6e435879bc203a01e1b2de.tar.bz2 rockbox-946d3d177e060d375d6e435879bc203a01e1b2de.tar.xz | |
Rewrite ascodec_as3514.c to use interrupts.
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@25297 a1c6a512-1295-4272-9138-f99709370657
| -rw-r--r-- | firmware/target/arm/as3525/ascodec-as3525.c | 309 | ||||
| -rw-r--r-- | firmware/target/arm/as3525/ascodec-target.h | 51 | ||||
| -rw-r--r-- | firmware/target/arm/as3525/system-as3525.c | 1 |
3 files changed, 314 insertions, 47 deletions
diff --git a/firmware/target/arm/as3525/ascodec-as3525.c b/firmware/target/arm/as3525/ascodec-as3525.c index 3019ffd..ca81d78 100644 --- a/firmware/target/arm/as3525/ascodec-as3525.c +++ b/firmware/target/arm/as3525/ascodec-as3525.c @@ -47,6 +47,7 @@ #include "ascodec-target.h" #include "clock-target.h" #include "kernel.h" +#include "system.h" #include "as3525.h" #include "i2c.h" @@ -63,8 +64,61 @@ #define I2C2_INT_CLR *((volatile unsigned int *)(I2C_AUDIO_BASE + 0x40)) #define I2C2_SADDR *((volatile unsigned int *)(I2C_AUDIO_BASE + 0x44)) +#define I2C2_CNTRL_MASTER 0x01 +#define I2C2_CNTRL_READ 0x02 +#define I2C2_CNTRL_WRITE 0x00 +#define I2C2_CNTRL_RESET 0x10 +#define I2C2_CNTRL_REPSTARTEN 0x40 + +#define I2C2_CNTRL_DEFAULT (I2C2_CNTRL_MASTER|I2C2_CNTRL_REPSTARTEN|I2C2_CNTRL_RESET) + +#define I2C2_IRQ_TXEMPTY 0x04 +#define I2C2_IRQ_RXFULL 0x08 +#define I2C2_IRQ_RXOVER 0x10 +#define I2C2_IRQ_ACKTIMEO 0x80 + +#define REQ_UNFINISHED 0 +#define REQ_FINISHED 1 +#define REQ_RETRY 2 + static struct mutex as_mtx; +static unsigned char *req_data_ptr = NULL; +static struct ascodec_request *req_head = NULL; +static struct ascodec_request *req_tail = NULL; + +static void ascodec_start_req(struct ascodec_request *req); +static int ascodec_continue_req(struct ascodec_request *req, int irq_status); +static void ascodec_finish_req(struct ascodec_request *req); + +void INT_I2C_AUDIO(void) +{ + int irq_status = I2C2_MIS; + int status = REQ_FINISHED; + + if (req_head != NULL) + status = ascodec_continue_req(req_head, irq_status); + + I2C2_INT_CLR |= irq_status; /* clear interrupt status */ + + if (status != REQ_UNFINISHED) { + /* mask rx/tx interrupts */ + I2C2_IMR &= ~(I2C2_IRQ_TXEMPTY|I2C2_IRQ_RXFULL| + I2C2_IRQ_RXOVER|I2C2_IRQ_ACKTIMEO); + + if (status == REQ_FINISHED) + ascodec_finish_req(req_head); + + /* + * If status == REQ_RETRY, this will restart + * the request because we didn't remove it from + * the request list + */ + if (req_head) + ascodec_start_req(req_head); + } +} + void i2c_init(void) { } @@ -74,6 +128,8 @@ void ascodec_init(void) { int prescaler; + mutex_init(&as_mtx); + /* enable clock */ CGU_PERI |= CGU_I2C_AUDIO_MASTER_CLOCK_ENABLE; @@ -85,9 +141,11 @@ void ascodec_init(void) /* set i2c slave address of codec part */ I2C2_SLAD0 = AS3514_I2C_ADDR << 1; - I2C2_CNTRL = 0x51; - - mutex_init(&as_mtx); + I2C2_CNTRL = I2C2_CNTRL_DEFAULT; + + I2C2_IMR = 0x00; /* disable interrupts */ + I2C2_INT_CLR |= I2C2_RIS; /* clear interrupt status */ + VIC_INT_ENABLE = INTERRUPT_I2C_AUDIO; } @@ -97,78 +155,235 @@ static int i2c_busy(void) return (I2C2_SR & 1); } -/* returns 0 on success, <0 otherwise */ -int ascodec_write(unsigned int index, unsigned int value) +void ascodec_req_init(struct ascodec_request *req, int type, + unsigned int index, unsigned int cnt) +{ + wakeup_init(&req->wkup); + req->next = NULL; + req->callback = NULL; + req->type = type; + req->index = index; + req->cnt = cnt; +} + +void ascodec_submit(struct ascodec_request *req) { - ascodec_lock(); + int oldlevel = disable_irq_save(); - /* wait if still busy */ + req->status = 0; + + if (req_head == NULL) { + req_tail = req_head = req; + ascodec_start_req(req); + } else { + req_tail->next = req; + req_tail = req; + } + + restore_irq(oldlevel); +} + +static void ascodec_start_req(struct ascodec_request *req) +{ + int unmask = 0; + + /* enable clock */ + CGU_PERI |= CGU_I2C_AUDIO_MASTER_CLOCK_ENABLE; + + /* start transfer */ + I2C2_SADDR = req->index; + if (req->type == ASCODEC_REQ_READ) { + req_data_ptr = req->data; + /* start transfer */ + I2C2_CNTRL = I2C2_CNTRL_DEFAULT | I2C2_CNTRL_READ; + unmask = I2C2_IRQ_RXFULL|I2C2_IRQ_RXOVER; + } else { + req_data_ptr = &req->data[1]; + I2C2_CNTRL = I2C2_CNTRL_DEFAULT | I2C2_CNTRL_WRITE; + I2C2_DATA = req->data[0]; + unmask = I2C2_IRQ_TXEMPTY|I2C2_IRQ_ACKTIMEO; + } + + I2C2_DACNT = req->cnt; + I2C2_IMR |= unmask; /* enable interrupts */ +} + +static int ascodec_continue_req(struct ascodec_request *req, int irq_status) +{ + if ((irq_status & (I2C2_IRQ_RXOVER|I2C2_IRQ_ACKTIMEO)) > 0) { + /* some error occured, restart the request */ + return REQ_RETRY; + } + if (req->type == ASCODEC_REQ_READ && + (irq_status & I2C2_IRQ_RXFULL) > 0) { + *(req_data_ptr++) = I2C2_DATA; + } else { + if (req->cnt > 1 && + (irq_status & I2C2_IRQ_TXEMPTY) > 0) { + I2C2_DATA = *(req_data_ptr++); + } + } + + req->index++; + if (--req->cnt > 0) + return REQ_UNFINISHED; + + return REQ_FINISHED; +} + +static void ascodec_finish_req(struct ascodec_request *req) +{ + /* + * Wait if still busy, unfortunately this happens since + * the controller is running at a low divisor, so it's + * still busy when we serviced the interrupt. + * I tried upping the i2c speed to 4MHz which + * made the number of busywait cycles much smaller + * (none for reads and only a few for writes), + * but who knows if it's reliable at that frequency. ;) + * For one thing, 8MHz doesn't work, so 4MHz is likely + * borderline. + * In general writes need much more wait cycles than reads + * for some reason, possibly because we read the data register + * for reads, which will likely block the processor while + * the i2c controller responds to the register read. + */ while (i2c_busy()); + /* disable clock */ + CGU_PERI &= ~CGU_I2C_AUDIO_MASTER_CLOCK_ENABLE; + + req->status = 1; + + if (req->callback) { + req->callback(req->data, req_data_ptr - req->data); + } + wakeup_signal(&req->wkup); + + req_head = req->next; + req->next = NULL; + if (req_head == NULL) + req_tail = NULL; + +} + +static int irq_disabled(void) +{ + unsigned long cpsr; + + asm volatile ("mrs %0, cpsr" : "=r"(cpsr)); + + return (cpsr & IRQ_STATUS) == IRQ_DISABLED; +} + +static void ascodec_wait(struct ascodec_request *req) +{ + if (!irq_disabled()) { + wakeup_wait(&req->wkup, TIMEOUT_BLOCK); + return; + } + + while (req->status == 0) { + if (I2C2_MIS) INT_I2C_AUDIO(); + } +} + +/* + * The request struct passed in must be allocated statically. + * If you call ascodec_async_write from different places, each + * call needs it's own request struct. + * This comment is duplicated in .c and .h for your convenience. + */ +void ascodec_async_write(unsigned int index, unsigned int value, + struct ascodec_request *req) +{ if (index == AS3514_CVDD_DCDC3) { /* prevent setting of the LREG_CP_not bit */ value &= ~(1 << 5); } - - /* start transfer */ - I2C2_SADDR = index; - I2C2_CNTRL &= ~(1 << 1); - I2C2_DATA = value; - I2C2_DACNT = 1; - - /* wait for transfer */ - while (I2C2_DACNT != 0); - ascodec_unlock(); + ascodec_req_init(req, ASCODEC_REQ_WRITE, index, 1); + req->data[0] = value; + ascodec_submit(req); +} + +/* returns 0 on success, <0 otherwise */ +int ascodec_write(unsigned int index, unsigned int value) +{ + struct ascodec_request req; + + ascodec_async_write(index, value, &req); + ascodec_wait(&req); return 0; } +/* + * The request struct passed in must be allocated statically. + * If you call ascodec_async_read from different places, each + * call needs it's own request struct. + * If len is bigger than ASCODEC_REQ_MAXLEN it will be + * set to ASCODEC_REQ_MAXLEN. + * This comment is duplicated in .c and .h for your convenience. + */ +void ascodec_async_read(unsigned int index, unsigned int len, + struct ascodec_request *req, ascodec_cb_fn *cb) +{ + if (len > ASCODEC_REQ_MAXLEN) + len = ASCODEC_REQ_MAXLEN; /* can't fit more in one request */ + + ascodec_req_init(req, ASCODEC_REQ_READ, index, len); + req->callback = cb; + ascodec_submit(req); +} /* returns value read on success, <0 otherwise */ int ascodec_read(unsigned int index) { - int data; - - ascodec_lock(); + struct ascodec_request req; - /* wait if still busy */ - while (i2c_busy()); + ascodec_async_read(index, 1, &req, NULL); + ascodec_wait(&req); - /* start transfer */ - I2C2_SADDR = index; - I2C2_CNTRL |= (1 << 1); - I2C2_DACNT = 1; - - /* wait for transfer*/ - while (I2C2_DACNT != 0); - data = I2C2_DATA; - - ascodec_unlock(); - - return data; + return req.data[0]; } -int ascodec_readbytes(int index, int len, unsigned char *data) +int ascodec_readbytes(unsigned int index, unsigned int len, unsigned char *data) { - int i; + int i, j; + struct ascodec_request req; - ascodec_lock(); + /* index and cnt will be filled in later, just use 0 */ + ascodec_req_init(&req, ASCODEC_REQ_READ, 0, 0); - for(i=0; i<len; i++) - { - int temp = ascodec_read(index+i); - if(temp == -1) - break; - else - data[i] = temp; - } + i = 0; + while (len > 0) { + int cnt = len > ASCODEC_REQ_MAXLEN ? ASCODEC_REQ_MAXLEN : len; + + req.index = index; + req.cnt = cnt; - ascodec_unlock(); + ascodec_submit(&req); + ascodec_wait(&req); + + for (j=0; j<cnt; j++) data[i++] = req.data[j]; + + len -= cnt; + index += cnt; + } return i; } +/* + * NOTE: + * After the conversion to interrupts, ascodec_(lock|unlock) are only used by + * adc-as3514.c to protect against other threads corrupting the result by using + * the ADC at the same time. + * Concurrent ascodec_(async_)?(read|write) calls are instead protected + * because ascodec_submit() is atomic and concurrent requests will wait + * in the queue until the current request is finished. + */ void ascodec_lock(void) { mutex_lock(&as_mtx); @@ -178,3 +393,5 @@ void ascodec_unlock(void) { mutex_unlock(&as_mtx); } + +/* vim:set ts=4 sw=4 et: */ diff --git a/firmware/target/arm/as3525/ascodec-target.h b/firmware/target/arm/as3525/ascodec-target.h index 3464bba..4b11041 100644 --- a/firmware/target/arm/as3525/ascodec-target.h +++ b/firmware/target/arm/as3525/ascodec-target.h @@ -26,6 +26,7 @@ #define _ASCODEC_TARGET_H #include "as3514.h" +#include "kernel.h" /* for struct wakeup */ /* Charge Pump and Power management Settings */ #define AS314_CP_DCDC3_SETTING \ @@ -41,13 +42,61 @@ #define CVDD_1_10 2 #define CVDD_1_05 3 +#define ASCODEC_REQ_READ 0 +#define ASCODEC_REQ_WRITE 1 + +/* + * How many bytes we using in struct ascodec_request for the data buffer. + * 4 fits the alignment best right now. + * We don't actually use more than 2 at the moment (in adc_read). + * Upper limit would be 255 since DACNT is 8 bits! + */ +#define ASCODEC_REQ_MAXLEN 4 + +typedef void (ascodec_cb_fn)(unsigned const char *data, unsigned cnt); + +struct ascodec_request { + unsigned char type; + unsigned char index; + unsigned char status; + unsigned char cnt; + unsigned char data[ASCODEC_REQ_MAXLEN]; + struct wakeup wkup; + ascodec_cb_fn *callback; + struct ascodec_request *next; +}; + void ascodec_init(void); int ascodec_write(unsigned int index, unsigned int value); int ascodec_read(unsigned int index); -int ascodec_readbytes(int index, int len, unsigned char *data); +int ascodec_readbytes(unsigned int index, unsigned int len, unsigned char *data); + +/* + * The request struct passed in must be allocated statically. + * If you call ascodec_async_write from different places, each + * call needs it's own request struct. + * This comment is duplicated in .c and .h for your convenience. + */ +void ascodec_async_write(unsigned index, unsigned int value, struct ascodec_request *req); + +/* + * The request struct passed in must be allocated statically. + * If you call ascodec_async_read from different places, each + * call needs it's own request struct. + * If len is bigger than ASCODEC_REQ_MAXLEN it will be + * set to ASCODEC_REQ_MAXLEN. + * This comment is duplicated in .c and .h for your convenience. + */ +void ascodec_async_read(unsigned index, unsigned int len, + struct ascodec_request *req, ascodec_cb_fn *cb); + +void ascodec_req_init(struct ascodec_request *req, int type, + unsigned int index, unsigned int cnt); + +void ascodec_submit(struct ascodec_request *req); void ascodec_lock(void); diff --git a/firmware/target/arm/as3525/system-as3525.c b/firmware/target/arm/as3525/system-as3525.c index 4bf5cd3..4ee3e59 100644 --- a/firmware/target/arm/as3525/system-as3525.c +++ b/firmware/target/arm/as3525/system-as3525.c @@ -111,6 +111,7 @@ struct vec_int_src vec_int_srcs[] = { INT_SRC_TIMER2, INT_TIMER2 }, { INT_SRC_DMAC, INT_DMAC }, { INT_SRC_NAND, INT_NAND }, + { INT_SRC_I2C_AUDIO, INT_I2C_AUDIO }, #ifdef HAVE_MULTIDRIVE { INT_SRC_MCI0, INT_MCI0 }, #endif |