[01/14] lm32: add Milkymist AC97 support

Submitted by Michael Walle on March 7, 2011, 10:32 p.m.

Details

Message ID 1299537165-16711-2-git-send-email-michael@walle.cc
State New
Headers show

Commit Message

Michael Walle March 7, 2011, 10:32 p.m.
This patch adds support for the Milkymist AC97 compatible sound output and
input core.

Signed-off-by: Michael Walle <michael@walle.cc>
---
 Makefile.target     |    1 +
 configure           |    3 +
 hw/milkymist-ac97.c |  335 +++++++++++++++++++++++++++++++++++++++++++++++++++
 trace-events        |   12 ++
 4 files changed, 351 insertions(+), 0 deletions(-)
 create mode 100644 hw/milkymist-ac97.c

Comments

Alexander Graf March 16, 2011, 4:50 p.m.
On 03/07/2011 11:32 PM, Michael Walle wrote:
> This patch adds support for the Milkymist AC97 compatible sound output and
> input core.

Malc, could you please take a look at this? :)

> Signed-off-by: Michael Walle<michael@walle.cc>
> ---
>   Makefile.target     |    1 +
>   configure           |    3 +
>   hw/milkymist-ac97.c |  335 +++++++++++++++++++++++++++++++++++++++++++++++++++
>   trace-events        |   12 ++
>   4 files changed, 351 insertions(+), 0 deletions(-)
>   create mode 100644 hw/milkymist-ac97.c
>
> diff --git a/Makefile.target b/Makefile.target
> index f0df98e..3be7868 100644
> --- a/Makefile.target
> +++ b/Makefile.target
> @@ -256,6 +256,7 @@ obj-lm32-y += lm32_juart.o
>   obj-lm32-y += lm32_timer.o
>   obj-lm32-y += lm32_uart.o
>   obj-lm32-y += lm32_sys.o
> +obj-lm32-y += milkymist-ac97.o
>
>   obj-mips-y = mips_r4k.o mips_jazz.o mips_malta.o mips_mipssim.o
>   obj-mips-y += mips_addr.o mips_timer.o mips_int.o
> diff --git a/configure b/configure
> index 5513d3e..e75e1a2 100755
> --- a/configure
> +++ b/configure
> @@ -3281,6 +3281,9 @@ if test "$target_softmmu" = "yes" ; then
>     arm)
>       cflags="-DHAS_AUDIO $cflags"
>     ;;
> +  lm32)
> +    cflags="-DHAS_AUDIO $cflags"
> +  ;;
>     i386|mips|ppc)
>       cflags="-DHAS_AUDIO -DHAS_AUDIO_CHOICE $cflags"
>     ;;
> diff --git a/hw/milkymist-ac97.c b/hw/milkymist-ac97.c
> new file mode 100644
> index 0000000..6c9e318
> --- /dev/null
> +++ b/hw/milkymist-ac97.c
> @@ -0,0 +1,335 @@
> +/*
> + *  QEMU model of the Milkymist System Controller.
> + *
> + *  Copyright (c) 2010 Michael Walle<michael@walle.cc>
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2 of the License, or (at your option) any later version.
> + *
> + * This library is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, see<http://www.gnu.org/licenses/>.
> + *
> + *
> + * Specification available at:
> + *   http://www.milkymist.org/socdoc/ac97.pdf
> + */
> +
> +#include "hw.h"
> +#include "sysbus.h"
> +#include "trace.h"
> +#include "audio/audio.h"
> +#include "qemu-error.h"
> +
> +enum {
> +    R_AC97_CTRL = 0,
> +    R_AC97_ADDR,
> +    R_AC97_DATAOUT,
> +    R_AC97_DATAIN,
> +    R_D_CTRL,
> +    R_D_ADDR,
> +    R_D_REMAINING,
> +    R_RESERVED,
> +    R_U_CTRL,
> +    R_U_ADDR,
> +    R_U_REMAINING,
> +    R_MAX
> +};
> +
> +enum {
> +    AC97_CTRL_RQEN  = (1<<0),
> +    AC97_CTRL_WRITE = (1<<1),
> +};
> +
> +enum {
> +    CTRL_EN = (1<<0),
> +};
> +
> +struct MilkymistAC97State {
> +    SysBusDevice busdev;
> +
> +    QEMUSoundCard card;
> +    SWVoiceIn *voice_in;
> +    SWVoiceOut *voice_out;
> +
> +    uint32_t regs[R_MAX];
> +
> +    qemu_irq crrequest_irq;
> +    qemu_irq crreply_irq;
> +    qemu_irq dmar_irq;
> +    qemu_irq dmaw_irq;
> +};
> +typedef struct MilkymistAC97State MilkymistAC97State;
> +
> +static void update_voices(MilkymistAC97State *s)
> +{
> +    if (s->regs[R_D_CTRL]&  CTRL_EN) {
> +        AUD_set_active_out(s->voice_out, 1);
> +    } else {
> +        AUD_set_active_out(s->voice_out, 0);
> +    }
> +
> +    if (s->regs[R_U_CTRL]&  CTRL_EN) {
> +        AUD_set_active_in(s->voice_in, 1);
> +    } else {
> +        AUD_set_active_in(s->voice_in, 0);
> +    }
> +}
> +
> +static uint32_t ac97_read(void *opaque, target_phys_addr_t addr)
> +{
> +    MilkymistAC97State *s = opaque;
> +    uint32_t r = 0;
> +
> +    addr>>= 2;
> +    switch (addr) {
> +    case R_AC97_CTRL:
> +    case R_AC97_ADDR:
> +    case R_AC97_DATAOUT:
> +    case R_AC97_DATAIN:
> +    case R_D_CTRL:
> +    case R_D_ADDR:
> +    case R_D_REMAINING:
> +    case R_U_CTRL:
> +    case R_U_ADDR:
> +    case R_U_REMAINING:
> +        r = s->regs[addr];
> +        break;
> +
> +    default:
> +        error_report("milkymist_ac97: read access to unkown register 0x"
> +                TARGET_FMT_plx, addr<<  2);
> +        break;
> +    }
> +
> +    trace_milkymist_ac97_memory_read(addr<<  2, r);
> +
> +    return r;
> +}
> +
> +static void ac97_write(void *opaque, target_phys_addr_t addr, uint32_t value)
> +{
> +    MilkymistAC97State *s = opaque;
> +
> +    trace_milkymist_ac97_memory_write(addr, value);
> +
> +    addr>>= 2;
> +    switch (addr) {
> +    case R_AC97_CTRL:
> +        /* always raise an IRQ according to the direction */
> +        if (value&  AC97_CTRL_RQEN) {
> +            if (value&  AC97_CTRL_WRITE) {
> +                trace_milkymist_ac97_pulse_irq_crrequest();
> +                qemu_irq_pulse(s->crrequest_irq);
> +            } else {
> +                trace_milkymist_ac97_pulse_irq_crreply();
> +                qemu_irq_pulse(s->crreply_irq);
> +            }
> +        }
> +
> +        /* RQEN is self clearing */
> +        s->regs[addr] = value&  ~AC97_CTRL_RQEN;
> +        break;
> +    case R_D_CTRL:
> +    case R_U_CTRL:
> +        s->regs[addr] = value;
> +        update_voices(s);
> +        break;
> +    case R_AC97_ADDR:
> +    case R_AC97_DATAOUT:
> +    case R_AC97_DATAIN:
> +    case R_D_ADDR:
> +    case R_D_REMAINING:
> +    case R_U_ADDR:
> +    case R_U_REMAINING:
> +        s->regs[addr] = value;
> +        break;
> +
> +    default:
> +        error_report("milkymist_ac97: write access to unkown register 0x"
> +                TARGET_FMT_plx, addr);
> +        break;
> +    }
> +
> +}
> +
> +static CPUReadMemoryFunc * const ac97_read_fn[] = {
> +    NULL,
> +    NULL,
> +&ac97_read,
> +};
> +
> +static CPUWriteMemoryFunc * const ac97_write_fn[] = {
> +    NULL,
> +    NULL,
> +&ac97_write,
> +};
> +
> +static void ac97_in_cb(void *opaque, int avail_b)
> +{
> +    MilkymistAC97State *s = opaque;
> +    uint8_t buf[4096];
> +    uint32_t remaining = s->regs[R_U_REMAINING];
> +    int temp = audio_MIN(remaining, avail_b);
> +    uint32_t addr = s->regs[R_U_ADDR];
> +    int transferred = 0;
> +
> +    trace_milkymist_ac97_in_cb(avail_b, remaining);
> +
> +    /* prevent from raising an IRQ */
> +    if (temp == 0) {
> +        return;
> +    }
> +
> +    while (temp) {
> +        int acquired, to_copy;
> +
> +        to_copy = audio_MIN(temp, sizeof(buf));
> +        acquired = AUD_read(s->voice_in, buf, to_copy);
> +        if (!acquired) {
> +            break;
> +        }
> +
> +        cpu_physical_memory_write(addr, buf, acquired);
> +
> +        temp -= acquired;
> +        addr += acquired;
> +        transferred += acquired;
> +    }
> +
> +    trace_milkymist_ac97_in_cb_transferred(transferred);
> +
> +    s->regs[R_U_ADDR] = addr;
> +    s->regs[R_U_REMAINING] -= transferred;
> +
> +    if ((s->regs[R_U_CTRL]&  CTRL_EN)&&  (s->regs[R_U_REMAINING] == 0)) {
> +        trace_milkymist_ac97_pulse_irq_dmaw();
> +        qemu_irq_pulse(s->dmaw_irq);
> +    }
> +}
> +
> +static void ac97_out_cb(void *opaque, int free_b)
> +{
> +    MilkymistAC97State *s = opaque;
> +    uint8_t buf[4096];
> +    uint32_t remaining = s->regs[R_D_REMAINING];
> +    int temp = audio_MIN(remaining, free_b);
> +    uint32_t addr = s->regs[R_D_ADDR];
> +    int transferred = 0;
> +
> +    trace_milkymist_ac97_out_cb(free_b, remaining);
> +
> +    /* prevent from raising an IRQ */
> +    if (temp == 0) {
> +        return;
> +    }
> +
> +    while (temp) {
> +        int copied, to_copy;
> +
> +        to_copy = audio_MIN(temp, sizeof(buf));
> +        cpu_physical_memory_read(addr, buf, to_copy);
> +        copied = AUD_write(s->voice_out, buf, to_copy);
> +        if (!copied) {
> +            break;
> +        }
> +        temp -= copied;
> +        addr += copied;
> +        transferred += copied;
> +    }
> +
> +    trace_milkymist_ac97_out_cb_transferred(transferred);
> +
> +    s->regs[R_D_ADDR] = addr;
> +    s->regs[R_D_REMAINING] -= transferred;
> +
> +    if ((s->regs[R_D_CTRL]&  CTRL_EN)&&  (s->regs[R_D_REMAINING] == 0)) {
> +        trace_milkymist_ac97_pulse_irq_dmar();
> +        qemu_irq_pulse(s->dmar_irq);
> +    }
> +}
> +
> +static void milkymist_ac97_reset(DeviceState *d)
> +{
> +    MilkymistAC97State *s = container_of(d, MilkymistAC97State, busdev.qdev);
> +    int i;
> +
> +    for (i = 0; i<  R_MAX; i++) {
> +        s->regs[i] = 0;
> +    }
> +
> +    AUD_set_active_in(s->voice_in, 0);
> +    AUD_set_active_out(s->voice_out, 0);
> +}
> +
> +static int ac97_post_load(void *opaque, int version_id)
> +{
> +    MilkymistAC97State *s = opaque;
> +
> +    update_voices(s);
> +
> +    return 0;
> +}
> +
> +static int milkymist_ac97_init(SysBusDevice *dev)
> +{
> +    MilkymistAC97State *s = FROM_SYSBUS(typeof(*s), dev);
> +    int ac97_regs;
> +
> +    struct audsettings as;
> +    sysbus_init_irq(dev,&s->crrequest_irq);
> +    sysbus_init_irq(dev,&s->crreply_irq);
> +    sysbus_init_irq(dev,&s->dmar_irq);
> +    sysbus_init_irq(dev,&s->dmaw_irq);
> +
> +    AUD_register_card("Milkymist AC'97",&s->card);
> +
> +    as.freq = 48000;
> +    as.nchannels = 2;
> +    as.fmt = AUD_FMT_S16;
> +    as.endianness = 1;
> +
> +    s->voice_in = AUD_open_in(&s->card, s->voice_in,
> +            "mm_ac97.in", s, ac97_in_cb,&as);
> +    s->voice_out = AUD_open_out(&s->card, s->voice_out,
> +            "mm_ac97.out", s, ac97_out_cb,&as);
> +
> +    ac97_regs = cpu_register_io_memory(ac97_read_fn, ac97_write_fn, s,
> +            DEVICE_NATIVE_ENDIAN);
> +    sysbus_init_mmio(dev, R_MAX * 4, ac97_regs);
> +
> +    return 0;
> +}
> +
> +static const VMStateDescription vmstate_milkymist_ac97 = {
> +    .name = "milkymist-ac97",
> +    .version_id = 1,
> +    .minimum_version_id = 1,
> +    .minimum_version_id_old = 1,
> +    .post_load = ac97_post_load,
> +    .fields      = (VMStateField[]) {
> +        VMSTATE_UINT32_ARRAY(regs, MilkymistAC97State, R_MAX),
> +        VMSTATE_END_OF_LIST()
> +    }
> +};
> +
> +static SysBusDeviceInfo milkymist_ac97_info = {
> +    .init = milkymist_ac97_init,
> +    .qdev.name  = "milkymist-ac97",
> +    .qdev.size  = sizeof(MilkymistAC97State),
> +    .qdev.vmsd  =&vmstate_milkymist_ac97,
> +    .qdev.reset = milkymist_ac97_reset,
> +};
> +
> +static void milkymist_ac97_register(void)
> +{
> +    sysbus_register_withprop(&milkymist_ac97_info);
> +}
> +
> +device_init(milkymist_ac97_register)
> diff --git a/trace-events b/trace-events
> index c791719..9241ac9 100644
> --- a/trace-events
> +++ b/trace-events
> @@ -283,3 +283,15 @@ disable lm32_uart_irq_state(int level) "irq state %d"
>
>   # hw/lm32_sys.c
>   disable lm32_sys_memory_write(uint32_t addr, uint32_t value) "addr 0x%08x value 0x%08x"
> +
> +# hw/milkymist-ac97.c
> +disable milkymist_ac97_memory_read(uint32_t addr, uint32_t value) "addr %08x value %08x"
> +disable milkymist_ac97_memory_write(uint32_t addr, uint32_t value) "addr %08x value %08x"
> +disable milkymist_ac97_pulse_irq_crrequest(void) "Pulse IRQ CR request"
> +disable milkymist_ac97_pulse_irq_crreply(void) "Pulse IRQ CR reply"
> +disable milkymist_ac97_pulse_irq_dmaw(void) "Pulse IRQ DMA write"
> +disable milkymist_ac97_pulse_irq_dmar(void) "Pulse IRQ DMA read"
> +disable milkymist_ac97_in_cb(int avail, uint32_t remaining) "avail %d remaining %u"
> +disable milkymist_ac97_in_cb_transferred(int transferred) "transferred %d"
> +disable milkymist_ac97_out_cb(int free, uint32_t remaining) "free %d remaining %u"
> +disable milkymist_ac97_out_cb_transferred(int transferred) "transferred %d"
malc March 16, 2011, 6:12 p.m.
On Wed, 16 Mar 2011, Alexander Graf wrote:

> On 03/07/2011 11:32 PM, Michael Walle wrote:
> > This patch adds support for the Milkymist AC97 compatible sound output and
> > input core.
> 
> Malc, could you please take a look at this? :)

Okay...

> 
> > Signed-off-by: Michael Walle<michael@walle.cc>
> > ---
> >   Makefile.target     |    1 +
> >   configure           |    3 +
> >   hw/milkymist-ac97.c |  335
> > +++++++++++++++++++++++++++++++++++++++++++++++++++
> >   trace-events        |   12 ++
> >   4 files changed, 351 insertions(+), 0 deletions(-)
> >   create mode 100644 hw/milkymist-ac97.c
> > 
> > diff --git a/Makefile.target b/Makefile.target
> > index f0df98e..3be7868 100644
> > --- a/Makefile.target
> > +++ b/Makefile.target
> > @@ -256,6 +256,7 @@ obj-lm32-y += lm32_juart.o
> >   obj-lm32-y += lm32_timer.o
> >   obj-lm32-y += lm32_uart.o
> >   obj-lm32-y += lm32_sys.o
> > +obj-lm32-y += milkymist-ac97.o
> > 
> >   obj-mips-y = mips_r4k.o mips_jazz.o mips_malta.o mips_mipssim.o
> >   obj-mips-y += mips_addr.o mips_timer.o mips_int.o
> > diff --git a/configure b/configure
> > index 5513d3e..e75e1a2 100755
> > --- a/configure
> > +++ b/configure
> > @@ -3281,6 +3281,9 @@ if test "$target_softmmu" = "yes" ; then
> >     arm)
> >       cflags="-DHAS_AUDIO $cflags"
> >     ;;
> > +  lm32)
> > +    cflags="-DHAS_AUDIO $cflags"
> > +  ;;
> >     i386|mips|ppc)
> >       cflags="-DHAS_AUDIO -DHAS_AUDIO_CHOICE $cflags"
> >     ;;
> > diff --git a/hw/milkymist-ac97.c b/hw/milkymist-ac97.c
> > new file mode 100644
> > index 0000000..6c9e318
> > --- /dev/null
> > +++ b/hw/milkymist-ac97.c
> > @@ -0,0 +1,335 @@
> > +/*
> > + *  QEMU model of the Milkymist System Controller.
> > + *
> > + *  Copyright (c) 2010 Michael Walle<michael@walle.cc>
> > + *
> > + * This library is free software; you can redistribute it and/or
> > + * modify it under the terms of the GNU Lesser General Public
> > + * License as published by the Free Software Foundation; either
> > + * version 2 of the License, or (at your option) any later version.
> > + *
> > + * This library is distributed in the hope that it will be useful,
> > + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> > + * Lesser General Public License for more details.
> > + *
> > + * You should have received a copy of the GNU Lesser General Public
> > + * License along with this library; if not,
> > see<http://www.gnu.org/licenses/>.
> > + *
> > + *
> > + * Specification available at:
> > + *   http://www.milkymist.org/socdoc/ac97.pdf
> > + */
> > +
> > +#include "hw.h"
> > +#include "sysbus.h"
> > +#include "trace.h"
> > +#include "audio/audio.h"
> > +#include "qemu-error.h"
> > +
> > +enum {
> > +    R_AC97_CTRL = 0,

Unneeded..

> > +    R_AC97_ADDR,
> > +    R_AC97_DATAOUT,
> > +    R_AC97_DATAIN,
> > +    R_D_CTRL,
> > +    R_D_ADDR,
> > +    R_D_REMAINING,
> > +    R_RESERVED,
> > +    R_U_CTRL,
> > +    R_U_ADDR,
> > +    R_U_REMAINING,
> > +    R_MAX
> > +};
> > +
> > +enum {
> > +    AC97_CTRL_RQEN  = (1<<0),

Oookay..

> > +    AC97_CTRL_WRITE = (1<<1),

Incinsistent(with previous enum formatting) comma (and C99 only at that)

> > +};
> > +
> > +enum {
> > +    CTRL_EN = (1<<0),

Ditto x2

> > +};
> > +
> > +struct MilkymistAC97State {
> > +    SysBusDevice busdev;
> > +
> > +    QEMUSoundCard card;
> > +    SWVoiceIn *voice_in;
> > +    SWVoiceOut *voice_out;
> > +
> > +    uint32_t regs[R_MAX];
> > +
> > +    qemu_irq crrequest_irq;
> > +    qemu_irq crreply_irq;
> > +    qemu_irq dmar_irq;
> > +    qemu_irq dmaw_irq;
> > +};
> > +typedef struct MilkymistAC97State MilkymistAC97State;
> > +
> > +static void update_voices(MilkymistAC97State *s)
> > +{
> > +    if (s->regs[R_D_CTRL]&  CTRL_EN) {

Space before ampersand would be nice.

> > +        AUD_set_active_out(s->voice_out, 1);
> > +    } else {
> > +        AUD_set_active_out(s->voice_out, 0);
> > +    }
> > +
> > +    if (s->regs[R_U_CTRL]&  CTRL_EN) {
> > +        AUD_set_active_in(s->voice_in, 1);
> > +    } else {
> > +        AUD_set_active_in(s->voice_in, 0);
> > +    }
> > +}
> > +
> > +static uint32_t ac97_read(void *opaque, target_phys_addr_t addr)
> > +{
> > +    MilkymistAC97State *s = opaque;
> > +    uint32_t r = 0;
> > +
> > +    addr>>= 2;

Again space here and elsewhere.

> > +    switch (addr) {
> > +    case R_AC97_CTRL:
> > +    case R_AC97_ADDR:
> > +    case R_AC97_DATAOUT:
> > +    case R_AC97_DATAIN:
> > +    case R_D_CTRL:
> > +    case R_D_ADDR:
> > +    case R_D_REMAINING:
> > +    case R_U_CTRL:
> > +    case R_U_ADDR:
> > +    case R_U_REMAINING:
> > +        r = s->regs[addr];
> > +        break;
> > +
> > +    default:
> > +        error_report("milkymist_ac97: read access to unkown register 0x"
> > +                TARGET_FMT_plx, addr<<  2);
> > +        break;
> > +    }
> > +
> > +    trace_milkymist_ac97_memory_read(addr<<  2, r);
> > +
> > +    return r;
> > +}
> > +
> > +static void ac97_write(void *opaque, target_phys_addr_t addr, uint32_t
> > value)
> > +{
> > +    MilkymistAC97State *s = opaque;
> > +
> > +    trace_milkymist_ac97_memory_write(addr, value);
> > +
> > +    addr>>= 2;
> > +    switch (addr) {
> > +    case R_AC97_CTRL:
> > +        /* always raise an IRQ according to the direction */
> > +        if (value&  AC97_CTRL_RQEN) {
> > +            if (value&  AC97_CTRL_WRITE) {
> > +                trace_milkymist_ac97_pulse_irq_crrequest();
> > +                qemu_irq_pulse(s->crrequest_irq);
> > +            } else {
> > +                trace_milkymist_ac97_pulse_irq_crreply();
> > +                qemu_irq_pulse(s->crreply_irq);
> > +            }
> > +        }
> > +
> > +        /* RQEN is self clearing */
> > +        s->regs[addr] = value&  ~AC97_CTRL_RQEN;
> > +        break;
> > +    case R_D_CTRL:
> > +    case R_U_CTRL:
> > +        s->regs[addr] = value;
> > +        update_voices(s);
> > +        break;
> > +    case R_AC97_ADDR:
> > +    case R_AC97_DATAOUT:
> > +    case R_AC97_DATAIN:
> > +    case R_D_ADDR:
> > +    case R_D_REMAINING:
> > +    case R_U_ADDR:
> > +    case R_U_REMAINING:
> > +        s->regs[addr] = value;
> > +        break;
> > +
> > +    default:
> > +        error_report("milkymist_ac97: write access to unkown register 0x"
> > +                TARGET_FMT_plx, addr);
> > +        break;
> > +    }
> > +
> > +}
> > +
> > +static CPUReadMemoryFunc * const ac97_read_fn[] = {
> > +    NULL,
> > +    NULL,
> > +&ac97_read,
> > +};
> > +
> > +static CPUWriteMemoryFunc * const ac97_write_fn[] = {
> > +    NULL,
> > +    NULL,
> > +&ac97_write,
> > +};
> > +
> > +static void ac97_in_cb(void *opaque, int avail_b)
> > +{
> > +    MilkymistAC97State *s = opaque;
> > +    uint8_t buf[4096];
> > +    uint32_t remaining = s->regs[R_U_REMAINING];
> > +    int temp = audio_MIN(remaining, avail_b);
> > +    uint32_t addr = s->regs[R_U_ADDR];
> > +    int transferred = 0;
> > +
> > +    trace_milkymist_ac97_in_cb(avail_b, remaining);
> > +
> > +    /* prevent from raising an IRQ */
> > +    if (temp == 0) {
> > +        return;
> > +    }
> > +
> > +    while (temp) {
> > +        int acquired, to_copy;
> > +
> > +        to_copy = audio_MIN(temp, sizeof(buf));
> > +        acquired = AUD_read(s->voice_in, buf, to_copy);
> > +        if (!acquired) {
> > +            break;
> > +        }
> > +
> > +        cpu_physical_memory_write(addr, buf, acquired);
> > +
> > +        temp -= acquired;
> > +        addr += acquired;
> > +        transferred += acquired;
> > +    }
> > +
> > +    trace_milkymist_ac97_in_cb_transferred(transferred);
> > +
> > +    s->regs[R_U_ADDR] = addr;
> > +    s->regs[R_U_REMAINING] -= transferred;
> > +
> > +    if ((s->regs[R_U_CTRL]&  CTRL_EN)&&  (s->regs[R_U_REMAINING] == 0)) {
> > +        trace_milkymist_ac97_pulse_irq_dmaw();
> > +        qemu_irq_pulse(s->dmaw_irq);
> > +    }
> > +}
> > +
> > +static void ac97_out_cb(void *opaque, int free_b)
> > +{
> > +    MilkymistAC97State *s = opaque;
> > +    uint8_t buf[4096];
> > +    uint32_t remaining = s->regs[R_D_REMAINING];
> > +    int temp = audio_MIN(remaining, free_b);
> > +    uint32_t addr = s->regs[R_D_ADDR];
> > +    int transferred = 0;
> > +
> > +    trace_milkymist_ac97_out_cb(free_b, remaining);
> > +
> > +    /* prevent from raising an IRQ */
> > +    if (temp == 0) {
> > +        return;
> > +    }
> > +
> > +    while (temp) {
> > +        int copied, to_copy;
> > +
> > +        to_copy = audio_MIN(temp, sizeof(buf));
> > +        cpu_physical_memory_read(addr, buf, to_copy);
> > +        copied = AUD_write(s->voice_out, buf, to_copy);
> > +        if (!copied) {
> > +            break;
> > +        }
> > +        temp -= copied;
> > +        addr += copied;
> > +        transferred += copied;
> > +    }
> > +
> > +    trace_milkymist_ac97_out_cb_transferred(transferred);
> > +
> > +    s->regs[R_D_ADDR] = addr;
> > +    s->regs[R_D_REMAINING] -= transferred;
> > +
> > +    if ((s->regs[R_D_CTRL]&  CTRL_EN)&&  (s->regs[R_D_REMAINING] == 0)) {
> > +        trace_milkymist_ac97_pulse_irq_dmar();
> > +        qemu_irq_pulse(s->dmar_irq);
> > +    }
> > +}
> > +
> > +static void milkymist_ac97_reset(DeviceState *d)
> > +{
> > +    MilkymistAC97State *s = container_of(d, MilkymistAC97State,
> > busdev.qdev);
> > +    int i;
> > +
> > +    for (i = 0; i<  R_MAX; i++) {
> > +        s->regs[i] = 0;
> > +    }
> > +
> > +    AUD_set_active_in(s->voice_in, 0);
> > +    AUD_set_active_out(s->voice_out, 0);
> > +}
> > +
> > +static int ac97_post_load(void *opaque, int version_id)
> > +{
> > +    MilkymistAC97State *s = opaque;
> > +
> > +    update_voices(s);
> > +
> > +    return 0;
> > +}
> > +
> > +static int milkymist_ac97_init(SysBusDevice *dev)
> > +{
> > +    MilkymistAC97State *s = FROM_SYSBUS(typeof(*s), dev);
> > +    int ac97_regs;
> > +
> > +    struct audsettings as;
> > +    sysbus_init_irq(dev,&s->crrequest_irq);
> > +    sysbus_init_irq(dev,&s->crreply_irq);
> > +    sysbus_init_irq(dev,&s->dmar_irq);
> > +    sysbus_init_irq(dev,&s->dmaw_irq);
> > +
> > +    AUD_register_card("Milkymist AC'97",&s->card);
> > +
> > +    as.freq = 48000;
> > +    as.nchannels = 2;
> > +    as.fmt = AUD_FMT_S16;
> > +    as.endianness = 1;
> > +
> > +    s->voice_in = AUD_open_in(&s->card, s->voice_in,
> > +            "mm_ac97.in", s, ac97_in_cb,&as);
> > +    s->voice_out = AUD_open_out(&s->card, s->voice_out,
> > +            "mm_ac97.out", s, ac97_out_cb,&as);
> > +
> > +    ac97_regs = cpu_register_io_memory(ac97_read_fn, ac97_write_fn, s,
> > +            DEVICE_NATIVE_ENDIAN);
> > +    sysbus_init_mmio(dev, R_MAX * 4, ac97_regs);
> > +
> > +    return 0;
> > +}
> > +
> > +static const VMStateDescription vmstate_milkymist_ac97 = {
> > +    .name = "milkymist-ac97",
> > +    .version_id = 1,
> > +    .minimum_version_id = 1,
> > +    .minimum_version_id_old = 1,
> > +    .post_load = ac97_post_load,
> > +    .fields      = (VMStateField[]) {
> > +        VMSTATE_UINT32_ARRAY(regs, MilkymistAC97State, R_MAX),
> > +        VMSTATE_END_OF_LIST()
> > +    }
> > +};
> > +
> > +static SysBusDeviceInfo milkymist_ac97_info = {
> > +    .init = milkymist_ac97_init,
> > +    .qdev.name  = "milkymist-ac97",
> > +    .qdev.size  = sizeof(MilkymistAC97State),
> > +    .qdev.vmsd  =&vmstate_milkymist_ac97,
> > +    .qdev.reset = milkymist_ac97_reset,
> > +};
> > +
> > +static void milkymist_ac97_register(void)
> > +{
> > +    sysbus_register_withprop(&milkymist_ac97_info);
> > +}
> > +
> > +device_init(milkymist_ac97_register)
> > diff --git a/trace-events b/trace-events
> > index c791719..9241ac9 100644
> > --- a/trace-events
> > +++ b/trace-events
> > @@ -283,3 +283,15 @@ disable lm32_uart_irq_state(int level) "irq state %d"
> > 
> >   # hw/lm32_sys.c
> >   disable lm32_sys_memory_write(uint32_t addr, uint32_t value) "addr 0x%08x
> > value 0x%08x"
> > +
> > +# hw/milkymist-ac97.c
> > +disable milkymist_ac97_memory_read(uint32_t addr, uint32_t value) "addr
> > %08x value %08x"
> > +disable milkymist_ac97_memory_write(uint32_t addr, uint32_t value) "addr
> > %08x value %08x"
> > +disable milkymist_ac97_pulse_irq_crrequest(void) "Pulse IRQ CR request"
> > +disable milkymist_ac97_pulse_irq_crreply(void) "Pulse IRQ CR reply"
> > +disable milkymist_ac97_pulse_irq_dmaw(void) "Pulse IRQ DMA write"
> > +disable milkymist_ac97_pulse_irq_dmar(void) "Pulse IRQ DMA read"
> > +disable milkymist_ac97_in_cb(int avail, uint32_t remaining) "avail %d
> > remaining %u"
> > +disable milkymist_ac97_in_cb_transferred(int transferred) "transferred %d"
> > +disable milkymist_ac97_out_cb(int free, uint32_t remaining) "free %d
> > remaining %u"
> > +disable milkymist_ac97_out_cb_transferred(int transferred) "transferred %d"
> 

IOW looks good to me.
Michael Walle March 16, 2011, 11:02 p.m.
Am Mittwoch 16 März 2011, 19:12:44 schrieb malc:
> > > diff --git a/hw/milkymist-ac97.c b/hw/milkymist-ac97.c
> > > new file mode 100644
> > > index 0000000..6c9e318
> > > --- /dev/null
> > > +++ b/hw/milkymist-ac97.c
> > > @@ -0,0 +1,335 @@
> > > +/*
> > > + *  QEMU model of the Milkymist System Controller.
> > > + *
> > > + *  Copyright (c) 2010 Michael Walle<michael@walle.cc>
> > > + *
> > > + * This library is free software; you can redistribute it and/or
> > > + * modify it under the terms of the GNU Lesser General Public
> > > + * License as published by the Free Software Foundation; either
> > > + * version 2 of the License, or (at your option) any later version.
> > > + *
> > > + * This library is distributed in the hope that it will be useful,
> > > + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> > > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> > > + * Lesser General Public License for more details.
> > > + *
> > > + * You should have received a copy of the GNU Lesser General Public
> > > + * License along with this library; if not,
> > > see<http://www.gnu.org/licenses/>.
> > > + *
> > > + *
> > > + * Specification available at:
> > > + *   http://www.milkymist.org/socdoc/ac97.pdf
> > > + */
> > > +
> > > +#include "hw.h"
> > > +#include "sysbus.h"
> > > +#include "trace.h"
> > > +#include "audio/audio.h"
> > > +#include "qemu-error.h"
> > > +
> > > +enum {
> > > +    R_AC97_CTRL = 0,
> 
> Unneeded..
I wanted to point out, that the registers begin at offset 0. If i change this, 
i'll need to change all other models, too. Including the ones that have been 
committed.

> > > +    R_AC97_ADDR,
> > > +    R_AC97_DATAOUT,
> > > +    R_AC97_DATAIN,
> > > +    R_D_CTRL,
> > > +    R_D_ADDR,
> > > +    R_D_REMAINING,
> > > +    R_RESERVED,
> > > +    R_U_CTRL,
> > > +    R_U_ADDR,
> > > +    R_U_REMAINING,
> > > +    R_MAX
> > > +};
> > > +
> > > +enum {
> > > +    AC97_CTRL_RQEN  = (1<<0),
> 
> Oookay..
> 
> > > +    AC97_CTRL_WRITE = (1<<1),
> 
> Incinsistent(with previous enum formatting) comma (and C99 only at that)
> 
> > > +};
> > > +
> > > +enum {
> > > +    CTRL_EN = (1<<0),
> 
> Ditto x2
Ok, but it has a hidden agenda. R_MAX must always be the last entry, therefore 
i omitted the comma. Whereas the bit flags could still be extended. Again if 
this is a concern for you, i'll change that and all other models too.

> > > +};
> > > +
> > > +struct MilkymistAC97State {
> > > +    SysBusDevice busdev;
> > > +
> > > +    QEMUSoundCard card;
> > > +    SWVoiceIn *voice_in;
> > > +    SWVoiceOut *voice_out;
> > > +
> > > +    uint32_t regs[R_MAX];
> > > +
> > > +    qemu_irq crrequest_irq;
> > > +    qemu_irq crreply_irq;
> > > +    qemu_irq dmar_irq;
> > > +    qemu_irq dmaw_irq;
> > > +};
> > > +typedef struct MilkymistAC97State MilkymistAC97State;
> > > +
> > > +static void update_voices(MilkymistAC97State *s)
> > > +{
> > > +    if (s->regs[R_D_CTRL]&  CTRL_EN) {
> 
> Space before ampersand would be nice.
mh i think Alex email client messed this up. At least for me, there is one 
space before and one space after the ampersand in my original email.
Alexander Graf March 16, 2011, 11:49 p.m.
On 17.03.2011, at 00:02, Michael Walle <michael@walle.cc> wrote:

> Am Mittwoch 16 März 2011, 19:12:44 schrieb malc:
>>>> diff --git a/hw/milkymist-ac97.c b/hw/milkymist-ac97.c
>>>> new file mode 100644
>>>> index 0000000..6c9e318
>>>> --- /dev/null
>>>> +++ b/hw/milkymist-ac97.c
>>>> @@ -0,0 +1,335 @@
>>>> +/*
>>>> + *  QEMU model of the Milkymist System Controller.
>>>> + *
>>>> + *  Copyright (c) 2010 Michael Walle<michael@walle.cc>
>>>> + *
>>>> + * This library is free software; you can redistribute it and/or
>>>> + * modify it under the terms of the GNU Lesser General Public
>>>> + * License as published by the Free Software Foundation; either
>>>> + * version 2 of the License, or (at your option) any later version.
>>>> + *
>>>> + * This library is distributed in the hope that it will be useful,
>>>> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
>>>> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
>>>> + * Lesser General Public License for more details.
>>>> + *
>>>> + * You should have received a copy of the GNU Lesser General Public
>>>> + * License along with this library; if not,
>>>> see<http://www.gnu.org/licenses/>.
>>>> + *
>>>> + *
>>>> + * Specification available at:
>>>> + *   http://www.milkymist.org/socdoc/ac97.pdf
>>>> + */
>>>> +
>>>> +#include "hw.h"
>>>> +#include "sysbus.h"
>>>> +#include "trace.h"
>>>> +#include "audio/audio.h"
>>>> +#include "qemu-error.h"
>>>> +
>>>> +enum {
>>>> +    R_AC97_CTRL = 0,
>> 
>> Unneeded..
> I wanted to point out, that the registers begin at offset 0. If i change this, 
> i'll need to change all other models, too. Including the ones that have been 
> committed.
> 
>>>> +    R_AC97_ADDR,
>>>> +    R_AC97_DATAOUT,
>>>> +    R_AC97_DATAIN,
>>>> +    R_D_CTRL,
>>>> +    R_D_ADDR,
>>>> +    R_D_REMAINING,
>>>> +    R_RESERVED,
>>>> +    R_U_CTRL,
>>>> +    R_U_ADDR,
>>>> +    R_U_REMAINING,
>>>> +    R_MAX
>>>> +};
>>>> +
>>>> +enum {
>>>> +    AC97_CTRL_RQEN  = (1<<0),
>> 
>> Oookay..
>> 
>>>> +    AC97_CTRL_WRITE = (1<<1),
>> 
>> Incinsistent(with previous enum formatting) comma (and C99 only at that)
>> 
>>>> +};
>>>> +
>>>> +enum {
>>>> +    CTRL_EN = (1<<0),
>> 
>> Ditto x2
> Ok, but it has a hidden agenda. R_MAX must always be the last entry, therefore 
> i omitted the comma. Whereas the bit flags could still be extended. Again if 
> this is a concern for you, i'll change that and all other models too.
> 
>>>> +};
>>>> +
>>>> +struct MilkymistAC97State {
>>>> +    SysBusDevice busdev;
>>>> +
>>>> +    QEMUSoundCard card;
>>>> +    SWVoiceIn *voice_in;
>>>> +    SWVoiceOut *voice_out;
>>>> +
>>>> +    uint32_t regs[R_MAX];
>>>> +
>>>> +    qemu_irq crrequest_irq;
>>>> +    qemu_irq crreply_irq;
>>>> +    qemu_irq dmar_irq;
>>>> +    qemu_irq dmaw_irq;
>>>> +};
>>>> +typedef struct MilkymistAC97State MilkymistAC97State;
>>>> +
>>>> +static void update_voices(MilkymistAC97State *s)
>>>> +{
>>>> +    if (s->regs[R_D_CTRL]&  CTRL_EN) {
>> 
>> Space before ampersand would be nice.
> mh i think Alex email client messed this up. At least for me, there is one 
> space before and one space after the ampersand in my original email.

Yes, thunderbird breaks those. Sorry :).

Alex

> 
> -- 
> Michael
malc March 17, 2011, 12:10 a.m.
On Thu, 17 Mar 2011, Michael Walle wrote:

> Am Mittwoch 16 M?rz 2011, 19:12:44 schrieb malc:
> > > > diff --git a/hw/milkymist-ac97.c b/hw/milkymist-ac97.c
> > > > new file mode 100644
> > > > index 0000000..6c9e318
> > > > --- /dev/null
> > > > +++ b/hw/milkymist-ac97.c
> > > > @@ -0,0 +1,335 @@
> > > > +/*
> > > > + *  QEMU model of the Milkymist System Controller.
> > > > + *
> > > > + *  Copyright (c) 2010 Michael Walle<michael@walle.cc>
> > > > + *
> > > > + * This library is free software; you can redistribute it and/or
> > > > + * modify it under the terms of the GNU Lesser General Public
> > > > + * License as published by the Free Software Foundation; either
> > > > + * version 2 of the License, or (at your option) any later version.
> > > > + *
> > > > + * This library is distributed in the hope that it will be useful,
> > > > + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> > > > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> > > > + * Lesser General Public License for more details.
> > > > + *
> > > > + * You should have received a copy of the GNU Lesser General Public
> > > > + * License along with this library; if not,
> > > > see<http://www.gnu.org/licenses/>.
> > > > + *
> > > > + *
> > > > + * Specification available at:
> > > > + *   http://www.milkymist.org/socdoc/ac97.pdf
> > > > + */
> > > > +
> > > > +#include "hw.h"
> > > > +#include "sysbus.h"
> > > > +#include "trace.h"
> > > > +#include "audio/audio.h"
> > > > +#include "qemu-error.h"
> > > > +
> > > > +enum {
> > > > +    R_AC97_CTRL = 0,
> > 
> > Unneeded..
> I wanted to point out, that the registers begin at offset 0. If i change 
> this, i'll need to change all other models, too. Including the ones that 
> have been committed.

And i just wanted to nitpick that it would be zero even without '= 0' part
(6.7.2.2#3 of ISO/IEC 9899:201x)

> 
> > > > +    R_AC97_ADDR,
> > > > +    R_AC97_DATAOUT,
> > > > +    R_AC97_DATAIN,
> > > > +    R_D_CTRL,
> > > > +    R_D_ADDR,
> > > > +    R_D_REMAINING,
> > > > +    R_RESERVED,
> > > > +    R_U_CTRL,
> > > > +    R_U_ADDR,
> > > > +    R_U_REMAINING,
> > > > +    R_MAX
> > > > +};
> > > > +
> > > > +enum {
> > > > +    AC97_CTRL_RQEN  = (1<<0),
> > 
> > Oookay..
> > 
> > > > +    AC97_CTRL_WRITE = (1<<1),
> > 
> > Incinsistent(with previous enum formatting) comma (and C99 only at that)
> > 
> > > > +};
> > > > +
> > > > +enum {
> > > > +    CTRL_EN = (1<<0),
> > 
> > Ditto x2
> Ok, but it has a hidden agenda. R_MAX must always be the last entry, therefore 
> i omitted the comma. Whereas the bit flags could still be extended. Again if 
> this is a concern for you, i'll change that and all other models too.
> 
> > > > +};
> > > > +
> > > > +struct MilkymistAC97State {
> > > > +    SysBusDevice busdev;
> > > > +
> > > > +    QEMUSoundCard card;
> > > > +    SWVoiceIn *voice_in;
> > > > +    SWVoiceOut *voice_out;
> > > > +
> > > > +    uint32_t regs[R_MAX];
> > > > +
> > > > +    qemu_irq crrequest_irq;
> > > > +    qemu_irq crreply_irq;
> > > > +    qemu_irq dmar_irq;
> > > > +    qemu_irq dmaw_irq;
> > > > +};
> > > > +typedef struct MilkymistAC97State MilkymistAC97State;
> > > > +
> > > > +static void update_voices(MilkymistAC97State *s)
> > > > +{
> > > > +    if (s->regs[R_D_CTRL]&  CTRL_EN) {
> > 
> > Space before ampersand would be nice.
> mh i think Alex email client messed this up. At least for me, there is one 
> space before and one space after the ampersand in my original email.
> 

Sorry for the noise then. (to Alexander: real men don't use crap MUA
you use)

Patch hide | download patch | download mbox

diff --git a/Makefile.target b/Makefile.target
index f0df98e..3be7868 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -256,6 +256,7 @@  obj-lm32-y += lm32_juart.o
 obj-lm32-y += lm32_timer.o
 obj-lm32-y += lm32_uart.o
 obj-lm32-y += lm32_sys.o
+obj-lm32-y += milkymist-ac97.o
 
 obj-mips-y = mips_r4k.o mips_jazz.o mips_malta.o mips_mipssim.o
 obj-mips-y += mips_addr.o mips_timer.o mips_int.o
diff --git a/configure b/configure
index 5513d3e..e75e1a2 100755
--- a/configure
+++ b/configure
@@ -3281,6 +3281,9 @@  if test "$target_softmmu" = "yes" ; then
   arm)
     cflags="-DHAS_AUDIO $cflags"
   ;;
+  lm32)
+    cflags="-DHAS_AUDIO $cflags"
+  ;;
   i386|mips|ppc)
     cflags="-DHAS_AUDIO -DHAS_AUDIO_CHOICE $cflags"
   ;;
diff --git a/hw/milkymist-ac97.c b/hw/milkymist-ac97.c
new file mode 100644
index 0000000..6c9e318
--- /dev/null
+++ b/hw/milkymist-ac97.c
@@ -0,0 +1,335 @@ 
+/*
+ *  QEMU model of the Milkymist System Controller.
+ *
+ *  Copyright (c) 2010 Michael Walle <michael@walle.cc>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Specification available at:
+ *   http://www.milkymist.org/socdoc/ac97.pdf
+ */
+
+#include "hw.h"
+#include "sysbus.h"
+#include "trace.h"
+#include "audio/audio.h"
+#include "qemu-error.h"
+
+enum {
+    R_AC97_CTRL = 0,
+    R_AC97_ADDR,
+    R_AC97_DATAOUT,
+    R_AC97_DATAIN,
+    R_D_CTRL,
+    R_D_ADDR,
+    R_D_REMAINING,
+    R_RESERVED,
+    R_U_CTRL,
+    R_U_ADDR,
+    R_U_REMAINING,
+    R_MAX
+};
+
+enum {
+    AC97_CTRL_RQEN  = (1<<0),
+    AC97_CTRL_WRITE = (1<<1),
+};
+
+enum {
+    CTRL_EN = (1<<0),
+};
+
+struct MilkymistAC97State {
+    SysBusDevice busdev;
+
+    QEMUSoundCard card;
+    SWVoiceIn *voice_in;
+    SWVoiceOut *voice_out;
+
+    uint32_t regs[R_MAX];
+
+    qemu_irq crrequest_irq;
+    qemu_irq crreply_irq;
+    qemu_irq dmar_irq;
+    qemu_irq dmaw_irq;
+};
+typedef struct MilkymistAC97State MilkymistAC97State;
+
+static void update_voices(MilkymistAC97State *s)
+{
+    if (s->regs[R_D_CTRL] & CTRL_EN) {
+        AUD_set_active_out(s->voice_out, 1);
+    } else {
+        AUD_set_active_out(s->voice_out, 0);
+    }
+
+    if (s->regs[R_U_CTRL] & CTRL_EN) {
+        AUD_set_active_in(s->voice_in, 1);
+    } else {
+        AUD_set_active_in(s->voice_in, 0);
+    }
+}
+
+static uint32_t ac97_read(void *opaque, target_phys_addr_t addr)
+{
+    MilkymistAC97State *s = opaque;
+    uint32_t r = 0;
+
+    addr >>= 2;
+    switch (addr) {
+    case R_AC97_CTRL:
+    case R_AC97_ADDR:
+    case R_AC97_DATAOUT:
+    case R_AC97_DATAIN:
+    case R_D_CTRL:
+    case R_D_ADDR:
+    case R_D_REMAINING:
+    case R_U_CTRL:
+    case R_U_ADDR:
+    case R_U_REMAINING:
+        r = s->regs[addr];
+        break;
+
+    default:
+        error_report("milkymist_ac97: read access to unkown register 0x"
+                TARGET_FMT_plx, addr << 2);
+        break;
+    }
+
+    trace_milkymist_ac97_memory_read(addr << 2, r);
+
+    return r;
+}
+
+static void ac97_write(void *opaque, target_phys_addr_t addr, uint32_t value)
+{
+    MilkymistAC97State *s = opaque;
+
+    trace_milkymist_ac97_memory_write(addr, value);
+
+    addr >>= 2;
+    switch (addr) {
+    case R_AC97_CTRL:
+        /* always raise an IRQ according to the direction */
+        if (value & AC97_CTRL_RQEN) {
+            if (value & AC97_CTRL_WRITE) {
+                trace_milkymist_ac97_pulse_irq_crrequest();
+                qemu_irq_pulse(s->crrequest_irq);
+            } else {
+                trace_milkymist_ac97_pulse_irq_crreply();
+                qemu_irq_pulse(s->crreply_irq);
+            }
+        }
+
+        /* RQEN is self clearing */
+        s->regs[addr] = value & ~AC97_CTRL_RQEN;
+        break;
+    case R_D_CTRL:
+    case R_U_CTRL:
+        s->regs[addr] = value;
+        update_voices(s);
+        break;
+    case R_AC97_ADDR:
+    case R_AC97_DATAOUT:
+    case R_AC97_DATAIN:
+    case R_D_ADDR:
+    case R_D_REMAINING:
+    case R_U_ADDR:
+    case R_U_REMAINING:
+        s->regs[addr] = value;
+        break;
+
+    default:
+        error_report("milkymist_ac97: write access to unkown register 0x"
+                TARGET_FMT_plx, addr);
+        break;
+    }
+
+}
+
+static CPUReadMemoryFunc * const ac97_read_fn[] = {
+    NULL,
+    NULL,
+    &ac97_read,
+};
+
+static CPUWriteMemoryFunc * const ac97_write_fn[] = {
+    NULL,
+    NULL,
+    &ac97_write,
+};
+
+static void ac97_in_cb(void *opaque, int avail_b)
+{
+    MilkymistAC97State *s = opaque;
+    uint8_t buf[4096];
+    uint32_t remaining = s->regs[R_U_REMAINING];
+    int temp = audio_MIN(remaining, avail_b);
+    uint32_t addr = s->regs[R_U_ADDR];
+    int transferred = 0;
+
+    trace_milkymist_ac97_in_cb(avail_b, remaining);
+
+    /* prevent from raising an IRQ */
+    if (temp == 0) {
+        return;
+    }
+
+    while (temp) {
+        int acquired, to_copy;
+
+        to_copy = audio_MIN(temp, sizeof(buf));
+        acquired = AUD_read(s->voice_in, buf, to_copy);
+        if (!acquired) {
+            break;
+        }
+
+        cpu_physical_memory_write(addr, buf, acquired);
+
+        temp -= acquired;
+        addr += acquired;
+        transferred += acquired;
+    }
+
+    trace_milkymist_ac97_in_cb_transferred(transferred);
+
+    s->regs[R_U_ADDR] = addr;
+    s->regs[R_U_REMAINING] -= transferred;
+
+    if ((s->regs[R_U_CTRL] & CTRL_EN) && (s->regs[R_U_REMAINING] == 0)) {
+        trace_milkymist_ac97_pulse_irq_dmaw();
+        qemu_irq_pulse(s->dmaw_irq);
+    }
+}
+
+static void ac97_out_cb(void *opaque, int free_b)
+{
+    MilkymistAC97State *s = opaque;
+    uint8_t buf[4096];
+    uint32_t remaining = s->regs[R_D_REMAINING];
+    int temp = audio_MIN(remaining, free_b);
+    uint32_t addr = s->regs[R_D_ADDR];
+    int transferred = 0;
+
+    trace_milkymist_ac97_out_cb(free_b, remaining);
+
+    /* prevent from raising an IRQ */
+    if (temp == 0) {
+        return;
+    }
+
+    while (temp) {
+        int copied, to_copy;
+
+        to_copy = audio_MIN(temp, sizeof(buf));
+        cpu_physical_memory_read(addr, buf, to_copy);
+        copied = AUD_write(s->voice_out, buf, to_copy);
+        if (!copied) {
+            break;
+        }
+        temp -= copied;
+        addr += copied;
+        transferred += copied;
+    }
+
+    trace_milkymist_ac97_out_cb_transferred(transferred);
+
+    s->regs[R_D_ADDR] = addr;
+    s->regs[R_D_REMAINING] -= transferred;
+
+    if ((s->regs[R_D_CTRL] & CTRL_EN) && (s->regs[R_D_REMAINING] == 0)) {
+        trace_milkymist_ac97_pulse_irq_dmar();
+        qemu_irq_pulse(s->dmar_irq);
+    }
+}
+
+static void milkymist_ac97_reset(DeviceState *d)
+{
+    MilkymistAC97State *s = container_of(d, MilkymistAC97State, busdev.qdev);
+    int i;
+
+    for (i = 0; i < R_MAX; i++) {
+        s->regs[i] = 0;
+    }
+
+    AUD_set_active_in(s->voice_in, 0);
+    AUD_set_active_out(s->voice_out, 0);
+}
+
+static int ac97_post_load(void *opaque, int version_id)
+{
+    MilkymistAC97State *s = opaque;
+
+    update_voices(s);
+
+    return 0;
+}
+
+static int milkymist_ac97_init(SysBusDevice *dev)
+{
+    MilkymistAC97State *s = FROM_SYSBUS(typeof(*s), dev);
+    int ac97_regs;
+
+    struct audsettings as;
+    sysbus_init_irq(dev, &s->crrequest_irq);
+    sysbus_init_irq(dev, &s->crreply_irq);
+    sysbus_init_irq(dev, &s->dmar_irq);
+    sysbus_init_irq(dev, &s->dmaw_irq);
+
+    AUD_register_card("Milkymist AC'97", &s->card);
+
+    as.freq = 48000;
+    as.nchannels = 2;
+    as.fmt = AUD_FMT_S16;
+    as.endianness = 1;
+
+    s->voice_in = AUD_open_in(&s->card, s->voice_in,
+            "mm_ac97.in", s, ac97_in_cb, &as);
+    s->voice_out = AUD_open_out(&s->card, s->voice_out,
+            "mm_ac97.out", s, ac97_out_cb, &as);
+
+    ac97_regs = cpu_register_io_memory(ac97_read_fn, ac97_write_fn, s,
+            DEVICE_NATIVE_ENDIAN);
+    sysbus_init_mmio(dev, R_MAX * 4, ac97_regs);
+
+    return 0;
+}
+
+static const VMStateDescription vmstate_milkymist_ac97 = {
+    .name = "milkymist-ac97",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .minimum_version_id_old = 1,
+    .post_load = ac97_post_load,
+    .fields      = (VMStateField[]) {
+        VMSTATE_UINT32_ARRAY(regs, MilkymistAC97State, R_MAX),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static SysBusDeviceInfo milkymist_ac97_info = {
+    .init = milkymist_ac97_init,
+    .qdev.name  = "milkymist-ac97",
+    .qdev.size  = sizeof(MilkymistAC97State),
+    .qdev.vmsd  = &vmstate_milkymist_ac97,
+    .qdev.reset = milkymist_ac97_reset,
+};
+
+static void milkymist_ac97_register(void)
+{
+    sysbus_register_withprop(&milkymist_ac97_info);
+}
+
+device_init(milkymist_ac97_register)
diff --git a/trace-events b/trace-events
index c791719..9241ac9 100644
--- a/trace-events
+++ b/trace-events
@@ -283,3 +283,15 @@  disable lm32_uart_irq_state(int level) "irq state %d"
 
 # hw/lm32_sys.c
 disable lm32_sys_memory_write(uint32_t addr, uint32_t value) "addr 0x%08x value 0x%08x"
+
+# hw/milkymist-ac97.c
+disable milkymist_ac97_memory_read(uint32_t addr, uint32_t value) "addr %08x value %08x"
+disable milkymist_ac97_memory_write(uint32_t addr, uint32_t value) "addr %08x value %08x"
+disable milkymist_ac97_pulse_irq_crrequest(void) "Pulse IRQ CR request"
+disable milkymist_ac97_pulse_irq_crreply(void) "Pulse IRQ CR reply"
+disable milkymist_ac97_pulse_irq_dmaw(void) "Pulse IRQ DMA write"
+disable milkymist_ac97_pulse_irq_dmar(void) "Pulse IRQ DMA read"
+disable milkymist_ac97_in_cb(int avail, uint32_t remaining) "avail %d remaining %u"
+disable milkymist_ac97_in_cb_transferred(int transferred) "transferred %d"
+disable milkymist_ac97_out_cb(int free, uint32_t remaining) "free %d remaining %u"
+disable milkymist_ac97_out_cb_transferred(int transferred) "transferred %d"