aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--hw/omap2.c110
1 files changed, 79 insertions, 31 deletions
diff --git a/hw/omap2.c b/hw/omap2.c
index 1e4fb1133c..e3ea4f7f35 100644
--- a/hw/omap2.c
+++ b/hw/omap2.c
@@ -1447,6 +1447,7 @@ struct omap_eac_s {
#define EAC_BUF_LEN 1024
uint32_t rxbuf[EAC_BUF_LEN];
+ int rxoff;
int rxlen;
int rxavail;
uint32_t txbuf[EAC_BUF_LEN];
@@ -1478,7 +1479,7 @@ static inline void omap_eac_interrupt_update(struct omap_eac_s *s)
static inline void omap_eac_in_dmarequest_update(struct omap_eac_s *s)
{
- qemu_set_irq(s->codec.rxdrq, s->codec.rxavail + s->codec.rxlen &&
+ qemu_set_irq(s->codec.rxdrq, (s->codec.rxavail || s->codec.rxlen) &&
((s->codec.config[1] >> 12) & 1)); /* DMAREN */
}
@@ -1490,26 +1491,61 @@ static inline void omap_eac_out_dmarequest_update(struct omap_eac_s *s)
static inline void omap_eac_in_refill(struct omap_eac_s *s)
{
- int left, start = 0;
-
- s->codec.rxlen = MIN(s->codec.rxavail, EAC_BUF_LEN);
- s->codec.rxavail -= s->codec.rxlen;
-
- for (left = s->codec.rxlen << 2; left; start = (EAC_BUF_LEN << 2) - left)
- left -= AUD_read(s->codec.in_voice,
- (uint8_t *) s->codec.rxbuf + start, left);
+ int left = MIN(EAC_BUF_LEN - s->codec.rxlen, s->codec.rxavail) << 2;
+ int start = ((s->codec.rxoff + s->codec.rxlen) & (EAC_BUF_LEN - 1)) << 2;
+ int leftwrap = MIN(left, (EAC_BUF_LEN << 2) - start);
+ int recv = 1;
+ uint8_t *buf = (uint8_t *) s->codec.rxbuf + start;
+
+ left -= leftwrap;
+ start = 0;
+ while (leftwrap && (recv = AUD_read(s->codec.in_voice, buf + start,
+ leftwrap)) > 0) { /* Be defensive */
+ start += recv;
+ leftwrap -= recv;
+ }
+ if (recv <= 0)
+ s->codec.rxavail = 0;
+ else
+ s->codec.rxavail -= start >> 2;
+ s->codec.rxlen += start >> 2;
+
+ if (recv > 0 && left > 0) {
+ start = 0;
+ while (left && (recv = AUD_read(s->codec.in_voice,
+ (uint8_t *) s->codec.rxbuf + start,
+ left)) > 0) { /* Be defensive */
+ start += recv;
+ left -= recv;
+ }
+ if (recv <= 0)
+ s->codec.rxavail = 0;
+ else
+ s->codec.rxavail -= start >> 2;
+ s->codec.rxlen += start >> 2;
+ }
}
static inline void omap_eac_out_empty(struct omap_eac_s *s)
{
- int left, start = 0;
+ int left = s->codec.txlen << 2;
+ int start = 0;
+ int sent = 1;
+
+ while (left && (sent = AUD_write(s->codec.out_voice,
+ (uint8_t *) s->codec.txbuf + start,
+ left)) > 0) { /* Be defensive */
+ start += sent;
+ left -= sent;
+ }
- for (left = s->codec.txlen << 2; left; start = (s->codec.txlen << 2) - left)
- left -= AUD_write(s->codec.out_voice,
- (uint8_t *) s->codec.txbuf + start, left);
+ if (!sent) {
+ s->codec.txavail = 0;
+ omap_eac_out_dmarequest_update(s);
+ }
- s->codec.txavail -= s->codec.txlen;
- s->codec.txlen = 0;
+ if (start)
+ s->codec.txlen = 0;
}
static void omap_eac_in_cb(void *opaque, int avail_b)
@@ -1517,8 +1553,9 @@ static void omap_eac_in_cb(void *opaque, int avail_b)
struct omap_eac_s *s = (struct omap_eac_s *) opaque;
s->codec.rxavail = avail_b >> 2;
- omap_eac_in_dmarequest_update(s);
+ omap_eac_in_refill(s);
/* TODO: possibly discard current buffer if overrun */
+ omap_eac_in_dmarequest_update(s);
}
static void omap_eac_out_cb(void *opaque, int free_b)
@@ -1526,10 +1563,10 @@ static void omap_eac_out_cb(void *opaque, int free_b)
struct omap_eac_s *s = (struct omap_eac_s *) opaque;
s->codec.txavail = free_b >> 2;
- if (s->codec.txlen > s->codec.txavail)
- s->codec.txlen = s->codec.txavail;
- omap_eac_out_empty(s);
- omap_eac_out_dmarequest_update(s);
+ if (s->codec.txlen)
+ omap_eac_out_empty(s);
+ else
+ omap_eac_out_dmarequest_update(s);
}
static void omap_eac_enable_update(struct omap_eac_s *s)
@@ -1591,7 +1628,9 @@ static void omap_eac_format_update(struct omap_eac_s *s)
{
audsettings_t fmt;
- omap_eac_out_empty(s);
+ /* The hardware buffers at most one sample */
+ if (s->codec.rxlen)
+ s->codec.rxlen = 1;
if (s->codec.in_voice) {
AUD_set_active_in(s->codec.in_voice, 0);
@@ -1599,10 +1638,14 @@ static void omap_eac_format_update(struct omap_eac_s *s)
s->codec.in_voice = 0;
}
if (s->codec.out_voice) {
+ omap_eac_out_empty(s);
AUD_set_active_out(s->codec.out_voice, 0);
AUD_close_out(&s->codec.card, s->codec.out_voice);
s->codec.out_voice = 0;
+ s->codec.txavail = 0;
}
+ /* Discard what couldn't be written */
+ s->codec.txlen = 0;
omap_eac_enable_update(s);
if (!s->codec.enable)
@@ -1663,6 +1706,7 @@ static void omap_eac_reset(struct omap_eac_s *s)
s->codec.config[1] = 0x0000;
s->codec.config[2] = 0x0007;
s->codec.config[3] = 0x1ffc;
+ s->codec.rxoff = 0;
s->codec.rxlen = 0;
s->codec.txlen = 0;
s->codec.rxavail = 0;
@@ -1676,6 +1720,7 @@ static uint32_t omap_eac_read(void *opaque, target_phys_addr_t addr)
{
struct omap_eac_s *s = (struct omap_eac_s *) opaque;
int offset = addr - s->base;
+ uint32_t ret;
switch (offset) {
case 0x000: /* CPCFR1 */
@@ -1739,16 +1784,19 @@ static uint32_t omap_eac_read(void *opaque, target_phys_addr_t addr)
/* This should be write-only? Docs list it as read-only. */
return 0x0000;
case 0x0b8: /* ADRDR */
- if (likely(s->codec.rxlen > 1))
- return s->codec.rxbuf[EAC_BUF_LEN - s->codec.rxlen --];
- else if (s->codec.rxlen) {
+ if (likely(s->codec.rxlen > 1)) {
+ ret = s->codec.rxbuf[s->codec.rxoff ++];
+ s->codec.rxlen --;
+ s->codec.rxoff &= EAC_BUF_LEN - 1;
+ return ret;
+ } else if (s->codec.rxlen) {
+ ret = s->codec.rxbuf[s->codec.rxoff ++];
+ s->codec.rxlen --;
+ s->codec.rxoff &= EAC_BUF_LEN - 1;
if (s->codec.rxavail)
omap_eac_in_refill(s);
- else {
- s->codec.rxlen = 0;
- omap_eac_in_dmarequest_update(s);
- }
- return s->codec.rxbuf[EAC_BUF_LEN - 1];
+ omap_eac_in_dmarequest_update(s);
+ return ret;
}
return 0x0000;
case 0x0bc: /* AGCFR */
@@ -1881,8 +1929,8 @@ static void omap_eac_write(void *opaque, target_phys_addr_t addr,
s->codec.txlen == s->codec.txavail)) {
if (s->codec.txavail)
omap_eac_out_empty(s);
- else
- s->codec.txlen = 0;
+ /* Discard what couldn't be written */
+ s->codec.txlen = 0;
}
break;