as3525: reverting I2C2 to non-interrupts version

Interrupts version is cause of freeze on USB extraction.
Also non-interrupts version much simpler and faster.

Change-Id: I30a2993cdcaa85abfba77ca06bfacd5b6b4353e2
This commit is contained in:
Mihail Zenkov 2016-02-20 03:36:26 +00:00
parent ce90c0481a
commit c7daef36c5
4 changed files with 126 additions and 402 deletions

View file

@ -26,40 +26,18 @@
/* Read 10-bit channel data */
unsigned short adc_read(int channel)
{
unsigned short data = 0;
if ((unsigned)channel >= NUM_ADC_CHANNELS)
return 0;
unsigned char buf[2];
ascodec_lock();
/* Select channel */
if (ascodec_write(AS3514_ADC_0, (channel << 4)) >= 0)
{
unsigned char buf[2];
ascodec_write(AS3514_ADC_0, (channel << 4));
/*
* The AS3514 ADC will trigger an interrupt when the conversion
* is finished, if the corresponding enable bit in IRQ_ENRD2
* is set.
* Previously the code did not wait and this apparently did
* not pose any problems, but this should be more correct.
* Without the wait the data read back may be completely or
* partially (first one of the two bytes) stale.
*/
ascodec_wait_adc_finished();
/* Read data */
if (ascodec_readbytes(AS3514_ADC_0, 2, buf) >= 0)
{
data = (((buf[0] & 0x3) << 8) | buf[1]);
}
}
ascodec_readbytes(AS3514_ADC_0, 2, buf);
ascodec_unlock();
return data;
return (((buf[0] & 0x3) << 8) | buf[1]);
}
void adc_init(void)

View file

@ -37,13 +37,11 @@ void ascodec_close(void);
void ascodec_lock(void);
void ascodec_unlock(void);
int ascodec_write(unsigned int index, unsigned int value);
void ascodec_write(unsigned int index, unsigned int value);
int ascodec_read(unsigned int index);
int ascodec_readbytes(unsigned int index, unsigned int len, unsigned char *data);
void ascodec_wait_adc_finished(void);
void ascodec_readbytes(unsigned int index, unsigned int len, unsigned char *data);
#if CONFIG_CHARGING
bool ascodec_endofch(void);

View file

@ -50,6 +50,7 @@
#include "system.h"
#include "as3525.h"
#include "i2c.h"
#include "logf.h"
#define I2C2_DATA *((volatile unsigned int *)(I2C_AUDIO_BASE + 0x00))
#define I2C2_SLAD0 *((volatile unsigned int *)(I2C_AUDIO_BASE + 0x04))
@ -77,202 +78,28 @@
#define I2C2_IRQ_RXOVER 0x10
#define I2C2_IRQ_ACKTIMEO 0x80
#define REQ_UNFINISHED 0
#define REQ_FINISHED 1
#define REQ_RETRY 2
#ifdef DEBUG
#define IFDEBUG(x) x
#else
#define IFDEBUG(x)
#endif
#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 3 at the moment (when reading interrupts)
* 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 semaphore complete;
ascodec_cb_fn *callback;
struct ascodec_request *next;
};
static struct mutex as_mtx;
static unsigned long ascodec_enrd0_shadow = 0;
#if CONFIG_CHARGING
static bool chg_status = false;
static bool endofch = false;
#endif
static unsigned char *req_data_ptr = NULL;
static struct ascodec_request *req_head = NULL;
static struct ascodec_request *req_tail = NULL;
static struct semaphore adc_done_sem;
static struct ascodec_request as_audio_req;
#ifdef DEBUG
static int int_audio_ctr = 0;
static int int_chg_finished = 0;
static int int_chg_insert = 0;
static int int_chg_remove = 0;
static int int_usb_insert = 0;
static int int_usb_remove = 0;
static int int_rtc = 0;
static int int_adc = 0;
#endif /* DEBUG */
/* returns != 0 when busy */
static inline int i2c_busy(void)
/* returns true when busy */
static inline bool i2c_busy(void)
{
return (I2C2_SR & 1);
}
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());
req->status = 1;
if (req->callback) {
req->callback(req->data, req_data_ptr - req->data);
}
semaphore_release(&req->complete);
}
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_start_req(struct ascodec_request *req)
{
int unmask = 0;
/* enable clock */
bitset32(&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 */
}
void INT_I2C_AUDIO(void)
{
struct ascodec_request *req = req_head;
int irq_status = I2C2_MIS;
int status = ascodec_continue_req(req, 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);
int oldlevel = disable_irq_save(); /* IRQs are stacked */
/*
* If status == REQ_RETRY, this will restart
* the request because we didn't remove it from
* the request list
*/
if (status == REQ_FINISHED) {
req_head = req->next;
req->next = NULL;
}
if (req_head == NULL) {
req_tail = NULL;
/* disable clock */
bitclr32(&CGU_PERI, CGU_I2C_AUDIO_MASTER_CLOCK_ENABLE);
} else {
ascodec_start_req(req_head);
}
restore_irq(oldlevel);
}
}
void i2c_init(void)
{
}
/* initialises the internal i2c bus and prepares for transfers to the codec */
void ascodec_init(void)
static void i2c2_init(void)
{
int prescaler;
mutex_init(&as_mtx);
semaphore_init(&adc_done_sem, 1, 0);
/* enable clock */
bitset32(&CGU_PERI, CGU_I2C_AUDIO_MASTER_CLOCK_ENABLE);
/* prescaler for i2c clock */
prescaler = AS3525_I2C_PRESCALER;
I2C2_CPSR0 = prescaler & 0xFF; /* 8 lsb */
@ -283,17 +110,18 @@ void ascodec_init(void)
I2C2_CNTRL = I2C2_CNTRL_DEFAULT;
I2C2_IMR = 0x00; /* disable interrupts */
I2C2_INT_CLR = I2C2_RIS; /* clear interrupt status */
VIC_INT_ENABLE = INTERRUPT_I2C_AUDIO;
VIC_INT_ENABLE = INTERRUPT_AUDIO;
}
/* detect if USB was connected at startup since there is no transition */
ascodec_enrd0_shadow = ascodec_read(AS3514_IRQ_ENRD0);
if(ascodec_enrd0_shadow & USB_STATUS)
usb_insert_int();
else
usb_remove_int();
/* initialises the internal i2c bus and prepares for transfers to the codec */
void ascodec_init(void)
{
mutex_init(&as_mtx);
/* enable clock */
bitset32(&CGU_PERI, CGU_I2C_AUDIO_MASTER_CLOCK_ENABLE);
i2c2_init();
/* Generate irq for usb+charge status change */
ascodec_write(AS3514_IRQ_ENRD0,
@ -305,242 +133,172 @@ void ascodec_init(void)
#if CONFIG_CPU == AS3525v2
/* XIRQ = IRQ, active low reset signal, 6mA push-pull output */
ascodec_write_pmu(0x1a, 3, (1<<2)|3); /* 1A-3 = Out_Cntr3 register */
/* Generate irq on (rtc,) adc change */
ascodec_write(AS3514_IRQ_ENRD2, /*IRQ_RTC |*/ IRQ_ADC);
/* reset for compatible with old bootloader */
ascodec_write(AS3514_IRQ_ENRD2, 0x0);
#else
/* Generate irq for push-pull, active high, irq on rtc+adc change */
ascodec_write(AS3514_IRQ_ENRD2, IRQ_PUSHPULL | IRQ_HIGHACTIVE |
/*IRQ_RTC |*/ IRQ_ADC);
ascodec_write(AS3514_IRQ_ENRD2, IRQ_PUSHPULL | IRQ_HIGHACTIVE);
#endif
VIC_INT_ENABLE = INTERRUPT_AUDIO;
/* detect if USB was connected at startup since there is no transition */
int data = ascodec_read(AS3514_IRQ_ENRD0);
if(data & USB_STATUS)
usb_insert_int();
#if CONFIG_CHARGING
chg_status = data & CHG_STATUS;
#endif
}
static void ascodec_req_init(struct ascodec_request *req, int type,
unsigned int index, unsigned int cnt)
/* returns false if transfer incomplete */
static bool i2c2_transfer(void)
{
semaphore_init(&req->complete, 1, 0);
req->next = NULL;
req->callback = NULL;
req->type = type;
req->index = index;
req->cnt = cnt;
}
static int try = 0;
static void ascodec_submit(struct ascodec_request *req)
{
int oldlevel = disable_irq_save();
/* wait for transfer*/
int i = 10000;
while (I2C2_DACNT != 0 && i--);
req->status = 0;
if (!i) {
if (try == 5)
panicf("I2C2 reset failed");
if (req_head == NULL) {
req_tail = req_head = req;
ascodec_start_req(req);
} else {
req_tail->next = req;
req_tail = req;
logf("reset I2C2 %d", try);
i2c2_init();
try++;
return false;
}
restore_irq(oldlevel);
try = 0;
return true;
}
static void ascodec_wait(struct ascodec_request *req)
void ascodec_write(unsigned int index, unsigned int value)
{
/* NOTE: Only safe from thread context */
ascodec_lock();
if (irq_enabled()) {
semaphore_wait(&req->complete, 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.
*/
static void ascodec_async_write(unsigned int index, unsigned int value,
struct ascodec_request *req)
{
#ifndef HAVE_AS3543
if (index == AS3514_CVDD_DCDC3) /* prevent setting of the LREG_CP_not bit */
value &= ~(1 << 5);
#endif
ascodec_req_init(req, ASCODEC_REQ_WRITE, index, 1);
req->data[0] = value;
ascodec_submit(req);
do {
/* wait if still busy */
while (i2c_busy());
/* start transfer */
I2C2_SADDR = index;
I2C2_CNTRL &= ~(1 << 1);
I2C2_DATA = value;
I2C2_DACNT = 1;
} while (!i2c2_transfer());
ascodec_unlock();
}
/* 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.
*/
static 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)
{
struct ascodec_request req;
int data;
ascodec_async_read(index, 1, &req, NULL);
ascodec_wait(&req);
ascodec_lock();
return req.data[0];
do {
/* wait if still busy */
while (i2c_busy());
/* start transfer */
I2C2_SADDR = index;
I2C2_CNTRL |= (1 << 1);
I2C2_DACNT = 1;
} while (!i2c2_transfer());
data = I2C2_DATA;
ascodec_unlock();
return data;
}
int ascodec_readbytes(unsigned int index, unsigned int len, unsigned char *data)
void ascodec_readbytes(unsigned int index, unsigned int len, unsigned char *data)
{
int i, j;
struct ascodec_request req;
unsigned int i;
/* index and cnt will be filled in later, just use 0 */
ascodec_req_init(&req, ASCODEC_REQ_READ, 0, 0);
i = 0;
while (len > 0) {
int cnt = len > ASCODEC_REQ_MAXLEN ? ASCODEC_REQ_MAXLEN : len;
req.index = index;
req.cnt = cnt;
ascodec_submit(&req);
ascodec_wait(&req);
for (j=0; j<cnt; j++) data[i++] = req.data[j];
len -= cnt;
index += cnt;
}
return i;
for(i = 0; i < len; i++)
data[i] = ascodec_read(index+i);
}
#if CONFIG_CPU == AS3525v2
void ascodec_write_pmu(unsigned int index, unsigned int subreg,
unsigned int value)
{
struct ascodec_request reqs[2];
int oldstatus = disable_irq_save();
ascodec_lock();
/* we submit consecutive requests to make sure no operations happen on the
* i2c bus between selecting the sub register and writing to it */
ascodec_async_write(AS3543_PMU_ENABLE, 8 | subreg, &reqs[0]);
ascodec_async_write(index, value, &reqs[1]);
restore_irq(oldstatus);
ascodec_write(AS3543_PMU_ENABLE, 8 | subreg);
ascodec_write(index, value);
/* Wait for second request to finish */
ascodec_wait(&reqs[1]);
ascodec_unlock();
}
int ascodec_read_pmu(unsigned int index, unsigned int subreg)
{
struct ascodec_request reqs[2];
int oldstatus = disable_irq_save();
ascodec_lock();
/* we submit consecutive requests to make sure no operations happen on the
* i2c bus between selecting the sub register and reading it */
ascodec_async_write(AS3543_PMU_ENABLE, subreg, &reqs[0]);
ascodec_async_read(index, 1, &reqs[1], NULL);
restore_irq(oldstatus);
ascodec_write(AS3543_PMU_ENABLE, subreg);
int ret = ascodec_read(index);
/* Wait for second request to finish */
ascodec_wait(&reqs[1]);
ascodec_unlock();
return reqs[1].data[0];
return ret;
}
#endif /* CONFIG_CPU == AS3525v2 */
static void ascodec_read_cb(unsigned const char *data, unsigned int len)
void INT_AUDIO(void)
{
if (UNLIKELY(len != 3)) /* some error happened? */
panicf("INT_AUDIO callback got %d regs", len);
int oldstatus = disable_irq_save();
int data = ascodec_read(AS3514_IRQ_ENRD0);
if (data[0] & CHG_ENDOFCH) { /* chg finished */
ascodec_enrd0_shadow |= CHG_ENDOFCH;
IFDEBUG(int_chg_finished++);
#if CONFIG_CHARGING
if (data & CHG_ENDOFCH) { /* chg finished */
endofch = true;
}
if (data[0] & CHG_CHANGED) { /* chg status changed */
if (data[0] & CHG_STATUS) {
ascodec_enrd0_shadow |= CHG_STATUS;
IFDEBUG(int_chg_insert++);
} else {
ascodec_enrd0_shadow &= ~CHG_STATUS;
IFDEBUG(int_chg_remove++);
}
}
if (data[0] & USB_CHANGED) { /* usb status changed */
if (data[0] & USB_STATUS) {
IFDEBUG(int_usb_insert++);
chg_status = data & CHG_STATUS;
#endif
if (data & USB_CHANGED) { /* usb status changed */
if (data & USB_STATUS) {
usb_insert_int();
} else {
IFDEBUG(int_usb_remove++);
usb_remove_int();
}
}
if (data[2] & IRQ_RTC) { /* rtc irq */
/*
* Can be configured for once per second or once per minute,
* default is once per second
*/
IFDEBUG(int_rtc++);
}
if (data[2] & IRQ_ADC) { /* adc finished */
IFDEBUG(int_adc++);
semaphore_release(&adc_done_sem);
}
VIC_INT_ENABLE = INTERRUPT_AUDIO;
}
void INT_AUDIO(void)
{
VIC_INT_EN_CLEAR = INTERRUPT_AUDIO;
IFDEBUG(int_audio_ctr++);
ascodec_async_read(AS3514_IRQ_ENRD0, 3, &as_audio_req, ascodec_read_cb);
}
void ascodec_wait_adc_finished(void)
{
semaphore_wait(&adc_done_sem, TIMEOUT_BLOCK);
restore_irq(oldstatus);
}
#if CONFIG_CHARGING
bool ascodec_endofch(void)
{
bool ret = ascodec_enrd0_shadow & CHG_ENDOFCH;
bitclr32(&ascodec_enrd0_shadow, CHG_ENDOFCH); /* clear interrupt */
int oldstatus = disable_irq_save();
bool ret = endofch;
endofch = false;
restore_irq(oldstatus);
return ret;
}
bool ascodec_chg_status(void)
{
return ascodec_enrd0_shadow & CHG_STATUS;
return chg_status;
}
void ascodec_monitor_endofch(void)
@ -567,15 +325,6 @@ int ascodec_read_charger(void)
}
#endif /* CONFIG_CHARGING */
/*
* 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);

View file

@ -137,7 +137,6 @@ static const struct { int source; void (*isr) (void); } vec_int_srcs[] =
{ INT_SRC_USB, INT_USB_FUNC, },
{ INT_SRC_TIMER1, INT_TIMER1 },
{ INT_SRC_TIMER2, INT_TIMER2 },
{ INT_SRC_I2C_AUDIO, INT_I2C_AUDIO },
{ INT_SRC_AUDIO, INT_AUDIO },
/* Lowest priority at the end of the list */
};