diff options
-rw-r--r-- | hw/omap2.c | 110 |
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; |