diff mbox series

[5/5] hw/audio/via-ac97: Basic implementation of audio playback

Message ID f84b42b8e811c6a0ddb23139fdfd654c8cc4f09c.1677004415.git.balaton@eik.bme.hu
State New
Headers show
Series Pegasos2 fixes and audio output support | expand

Commit Message

BALATON Zoltan Feb. 21, 2023, 6:44 p.m. UTC
This adds basic implementation of the AC'97 sound part used in VIA
south bridge chips. Not all features of the device is emulated, only
one playback channel is supported but this is enough to get sound
output from some guests running on machines using this device such as
pegasos2.

Signed-off-by: BALATON Zoltan <balaton@eik.bme.hu>
---
 hw/audio/trace-events     |   6 +
 hw/audio/via-ac97.c       | 436 +++++++++++++++++++++++++++++++++++++-
 hw/isa/vt82c686.c         |   3 +-
 include/hw/isa/vt82c686.h |  26 +++
 4 files changed, 466 insertions(+), 5 deletions(-)

Comments

Volker Rümelin Feb. 23, 2023, 7:40 p.m. UTC | #1
Am 21.02.23 um 19:44 schrieb BALATON Zoltan:
> This adds basic implementation of the AC'97 sound part used in VIA
> south bridge chips. Not all features of the device is emulated, only
> one playback channel is supported but this is enough to get sound
> output from some guests running on machines using this device such as
> pegasos2.
>
> Signed-off-by: BALATON Zoltan <balaton@eik.bme.hu>
> ---
>   hw/audio/trace-events     |   6 +
>   hw/audio/via-ac97.c       | 436 +++++++++++++++++++++++++++++++++++++-
>   hw/isa/vt82c686.c         |   3 +-
>   include/hw/isa/vt82c686.h |  26 +++
>   4 files changed, 466 insertions(+), 5 deletions(-)
>
> diff --git a/hw/audio/trace-events b/hw/audio/trace-events
> index e0e71cd9b1..6eccdaa4b5 100644
> --- a/hw/audio/trace-events
> +++ b/hw/audio/trace-events
> @@ -11,3 +11,9 @@ hda_audio_running(const char *stream, int nr, bool running) "st %s, nr %d, run %
>   hda_audio_format(const char *stream, int chan, const char *fmt, int freq) "st %s, %d x %s @ %d Hz"
>   hda_audio_adjust(const char *stream, int pos) "st %s, pos %d"
>   hda_audio_overrun(const char *stream) "st %s"
> +
> +#via-ac97.c
> +via_ac97_codec_write(uint8_t addr, uint16_t val) "0x%x <- 0x%x"
> +via_ac97_sgd_fetch(uint32_t addr, char stop, char eol, char flag, uint32_t len) "addr=0x%x %c%c%c len=%d"
> +via_ac97_sgd_read(uint64_t addr, unsigned size, uint64_t val) "0x%"PRIx64" %d -> 0x%"PRIx64
> +via_ac97_sgd_write(uint64_t addr, unsigned size, uint64_t val) "0x%"PRIx64" %d <- 0x%"PRIx64
> diff --git a/hw/audio/via-ac97.c b/hw/audio/via-ac97.c
> index d1a856f63d..cdac5bc14b 100644
> --- a/hw/audio/via-ac97.c
> +++ b/hw/audio/via-ac97.c
> @@ -1,39 +1,467 @@
>   /*
>    * VIA south bridges sound support
>    *
> + * Copyright (c) 2022-2023 BALATON Zoltan
> + *
>    * This work is licensed under the GNU GPL license version 2 or later.
>    */
>   
>   /*
> - * TODO: This is entirely boiler plate just registering empty PCI devices
> - * with the right ID guests expect, functionality should be added here.
> + * TODO: This is only a basic implementation of one audio playback channel
> + *       more functionality should be added here.
>    */
>   
>   #include "qemu/osdep.h"
> +#include "qemu/log.h"
>   #include "hw/isa/vt82c686.h"
> -#include "hw/pci/pci_device.h"
> +#include "ac97.h"
> +#include "trace.h"
> +
> +#define CLEN_IS_EOL(x)  ((x)->clen & BIT(31))
> +#define CLEN_IS_FLAG(x) ((x)->clen & BIT(30))
> +#define CLEN_IS_STOP(x) ((x)->clen & BIT(29))
> +#define CLEN_LEN(x)     ((x)->clen & 0xfff)

Hi,

the CLEN_LEN mask is 0xffffff instead of 0xfff. The SGD Table Base Count 
has 24 bits.

With best regards,
Volker

> +
> +#define STAT_ACTIVE BIT(7)
> +#define STAT_PAUSED BIT(6)
> +#define STAT_TRIG   BIT(3)
> +#define STAT_STOP   BIT(2)
> +#define STAT_EOL    BIT(1)
> +#define STAT_FLAG   BIT(0)
> +
> +#define CNTL_START  BIT(7)
> +#define CNTL_TERM   BIT(6)
> +#define CNTL_PAUSE  BIT(3)
> +
> +static void open_voice_out(ViaAC97State *s);
> +
> +static uint16_t codec_rates[] = { 8000, 11025, 16000, 22050, 32000, 44100,
> +                                  48000 };
> +
> +#define CODEC_REG(s, o)  ((s)->codec_regs[(o) / 2])
> +#define CODEC_VOL(vol, mask)  ((255 * ((vol) & mask)) / mask)
> +
> +static void codec_volume_set_out(ViaAC97State *s)
> +{
> +    int lvol, rvol, mute;
> +
> +    lvol = 255 - CODEC_VOL(CODEC_REG(s, AC97_Master_Volume_Mute) >> 8, 0x1f);
> +    lvol *= 255 - CODEC_VOL(CODEC_REG(s, AC97_PCM_Out_Volume_Mute) >> 8, 0x1f);
> +    lvol /= 255;
> +    rvol = 255 - CODEC_VOL(CODEC_REG(s, AC97_Master_Volume_Mute), 0x1f);
> +    rvol *= 255 - CODEC_VOL(CODEC_REG(s, AC97_PCM_Out_Volume_Mute), 0x1f);
> +    rvol /= 255;
> +    mute = CODEC_REG(s, AC97_Master_Volume_Mute) >> MUTE_SHIFT;
> +    mute |= CODEC_REG(s, AC97_PCM_Out_Volume_Mute) >> MUTE_SHIFT;
> +    AUD_set_volume_out(s->vo, mute, lvol, rvol);
> +}
> +
> +static void codec_reset(ViaAC97State *s)
> +{
> +    memset(s->codec_regs, 0, sizeof(s->codec_regs));
> +    CODEC_REG(s, AC97_Reset) = 0x6a90;
> +    CODEC_REG(s, AC97_Master_Volume_Mute) = 0x8000;
> +    CODEC_REG(s, AC97_Headphone_Volume_Mute) = 0x8000;
> +    CODEC_REG(s, AC97_Master_Volume_Mono_Mute) = 0x8000;
> +    CODEC_REG(s, AC97_Phone_Volume_Mute) = 0x8008;
> +    CODEC_REG(s, AC97_Mic_Volume_Mute) = 0x8008;
> +    CODEC_REG(s, AC97_Line_In_Volume_Mute) = 0x8808;
> +    CODEC_REG(s, AC97_CD_Volume_Mute) = 0x8808;
> +    CODEC_REG(s, AC97_Video_Volume_Mute) = 0x8808;
> +    CODEC_REG(s, AC97_Aux_Volume_Mute) = 0x8808;
> +    CODEC_REG(s, AC97_PCM_Out_Volume_Mute) = 0x8808;
> +    CODEC_REG(s, AC97_Record_Gain_Mute) = 0x8000;
> +    CODEC_REG(s, AC97_Powerdown_Ctrl_Stat) = 0x000f;
> +    CODEC_REG(s, AC97_Extended_Audio_ID) = 0x0a05;
> +    CODEC_REG(s, AC97_Extended_Audio_Ctrl_Stat) = 0x0400;
> +    CODEC_REG(s, AC97_PCM_Front_DAC_Rate) = 48000;
> +    CODEC_REG(s, AC97_PCM_LR_ADC_Rate) = 48000;
> +    /* Sigmatel 9766 (STAC9766) */
> +    CODEC_REG(s, AC97_Vendor_ID1) = 0x8384;
> +    CODEC_REG(s, AC97_Vendor_ID2) = 0x7666;
> +}
> +
> +static uint16_t codec_read(ViaAC97State *s, uint8_t addr)
> +{
> +    return CODEC_REG(s, addr);
> +}
> +
> +static void codec_write(ViaAC97State *s, uint8_t addr, uint16_t val)
> +{
> +    trace_via_ac97_codec_write(addr, val);
> +    switch (addr) {
> +    case AC97_Reset:
> +        codec_reset(s);
> +        return;
> +    case AC97_Master_Volume_Mute:
> +    case AC97_PCM_Out_Volume_Mute:
> +        if (addr == AC97_Master_Volume_Mute) {
> +            if (val & BIT(13)) {
> +                val |= 0x1f00;
> +            }
> +            if (val & BIT(5)) {
> +                val |= 0x1f;
> +            }
> +        }
> +        CODEC_REG(s, addr) = val & 0x9f1f;
> +        codec_volume_set_out(s);
> +        return;
> +    case AC97_Extended_Audio_Ctrl_Stat:
> +        CODEC_REG(s, addr) &= ~EACS_VRA;
> +        CODEC_REG(s, addr) |= val & EACS_VRA;
> +        if (!(val & EACS_VRA)) {
> +            CODEC_REG(s, AC97_PCM_Front_DAC_Rate) = 48000;
> +            CODEC_REG(s, AC97_PCM_LR_ADC_Rate) = 48000;
> +            open_voice_out(s);
> +        }
> +        return;
> +    case AC97_PCM_Front_DAC_Rate:
> +    case AC97_PCM_LR_ADC_Rate:
> +        if (CODEC_REG(s, AC97_Extended_Audio_Ctrl_Stat) & EACS_VRA) {
> +            int i;
> +            uint16_t rate = val;
> +
> +            for (i = 0; i < ARRAY_SIZE(codec_rates) - 1; i++) {
> +                if (rate < codec_rates[i] +
> +                    (codec_rates[i + 1] - codec_rates[i]) / 2) {
> +                    rate = codec_rates[i];
> +                    break;
> +                }
> +            }
> +            if (rate > 48000) {
> +                rate = 48000;
> +            }
> +            CODEC_REG(s, addr) = rate;
> +            open_voice_out(s);
> +        }
> +        return;
> +    case AC97_Powerdown_Ctrl_Stat:
> +        CODEC_REG(s, addr) = (val & 0xff00) | (CODEC_REG(s, addr) & 0xff);
> +        return;
> +    case AC97_Extended_Audio_ID:
> +    case AC97_Vendor_ID1:
> +    case AC97_Vendor_ID2:
> +        /* Read only registers */
> +        return;
> +    default:
> +        qemu_log_mask(LOG_UNIMP,
> +                      "via-ac97: Unimplemented codec register 0x%x\n", addr);
> +        CODEC_REG(s, addr) = val;
> +    }
> +}
> +
> +static void fetch_sgd(ViaAC97SGDChannel *c, PCIDevice *d)
> +{
> +    uint32_t b[2];
> +
> +    if (c->curr < c->base) {
> +        c->curr = c->base;
> +    }
> +    pci_dma_read(d, c->curr, b, sizeof(b));
> +    c->addr = le32_to_cpu(b[0]);
> +    c->clen = le32_to_cpu(b[1]);
> +    trace_via_ac97_sgd_fetch(c->addr, CLEN_IS_STOP(c) ? 'S' : '-',
> +                             CLEN_IS_EOL(c) ? 'E' : '-',
> +                             CLEN_IS_FLAG(c) ? 'F' : '-', CLEN_LEN(c));
> +}
> +
> +static void out_cb(void *opaque, int avail)
> +{
> +    ViaAC97State *s = opaque;
> +    ViaAC97SGDChannel *c = &s->aur;
> +    int temp, to_copy, copied;
> +    bool stop = false;
> +    uint8_t tmpbuf[4096];
> +
> +    if (c->stat & STAT_PAUSED) {
> +        return;
> +    }
> +    c->stat |= STAT_ACTIVE;
> +    while (avail && !stop) {
> +        if (!c->clen) {
> +            fetch_sgd(c, &s->dev);
> +        }
> +        temp = MIN(CLEN_LEN(c), avail);
> +        while (temp) {
> +            to_copy = MIN(temp, sizeof(tmpbuf));
> +            pci_dma_read(&s->dev, c->addr, tmpbuf, to_copy);
> +            copied = AUD_write(s->vo, tmpbuf, to_copy);
> +            if (!copied) {
> +                stop = true;
> +                break;
> +            }
> +            temp -= copied;
> +            avail -= copied;
> +            c->addr += copied;
> +            c->clen -= copied;
> +        }
> +        if (CLEN_LEN(c) == 0) {
> +            c->curr += 8;
> +            if (CLEN_IS_EOL(c)) {
> +                c->stat |= STAT_EOL;
> +                if (c->type & CNTL_START) {
> +                    c->curr = c->base;
> +                    c->stat |= STAT_PAUSED;
> +                } else {
> +                    c->stat &= ~STAT_ACTIVE;
> +                    AUD_set_active_out(s->vo, 0);
> +                }
> +                if (c->type & STAT_EOL) {
> +                    via_isa_set_irq(&s->dev, VIA_IRQ_AC97, 1);
> +                }
> +            }
> +            if (CLEN_IS_FLAG(c)) {
> +                c->stat |= STAT_FLAG;
> +                c->stat |= STAT_PAUSED;
> +                if (c->type & STAT_FLAG) {
> +                    via_isa_set_irq(&s->dev, VIA_IRQ_AC97, 1);
> +                }
> +            }
> +            if (CLEN_IS_STOP(c)) {
> +                c->stat |= STAT_STOP;
> +                c->stat |= STAT_PAUSED;
> +            }
> +            c->clen = 0;
> +            stop = true;
> +        }
> +    }
> +}
> +
> +static void open_voice_out(ViaAC97State *s)
> +{
> +    struct audsettings as = {
> +        .freq = CODEC_REG(s, AC97_PCM_Front_DAC_Rate),
> +        .nchannels = s->aur.type & BIT(4) ? 2 : 1,
> +        .fmt = s->aur.type & BIT(5) ? AUDIO_FORMAT_S16 : AUDIO_FORMAT_S8,
> +        .endianness = 0,
> +    };
> +    s->vo = AUD_open_out(&s->card, s->vo, "via-ac97.out", s, out_cb, &as);
> +}
> +
> +static uint64_t sgd_read(void *opaque, hwaddr addr, unsigned size)
> +{
> +    ViaAC97State *s = opaque;
> +    uint64_t val = 0;
> +
> +    switch (addr) {
> +    case 0:
> +        val = s->aur.stat;
> +        if (s->aur.type & CNTL_START) {
> +            val |= STAT_TRIG;
> +        }
> +        break;
> +    case 1:
> +        val = s->aur.stat & STAT_PAUSED ? BIT(3) : 0;
> +        break;
> +    case 2:
> +        val = s->aur.type;
> +        break;
> +    case 4:
> +        val = s->aur.curr;
> +        break;
> +    case 0xc:
> +        val = CLEN_LEN(&s->aur);
> +        break;
> +    case 0x80:
> +        val = s->ac97_cmd;
> +        break;
> +    case 0x84:
> +        val = s->aur.stat & STAT_FLAG;
> +        if (s->aur.stat & STAT_EOL) {
> +            val |= BIT(4);
> +        }
> +        if (s->aur.stat & STAT_STOP) {
> +            val |= BIT(8);
> +        }
> +        if (s->aur.stat & STAT_ACTIVE) {
> +            val |= BIT(12);
> +        }
> +        break;
> +    default:
> +        qemu_log_mask(LOG_UNIMP, "via-ac97: Unimplemented register read %"
> +                      HWADDR_PRIx"\n", addr);
> +    }
> +    trace_via_ac97_sgd_read(addr, size, val);
> +    return val;
> +}
> +
> +static void sgd_write(void *opaque, hwaddr addr, uint64_t val, unsigned size)
> +{
> +    ViaAC97State *s = opaque;
> +
> +    trace_via_ac97_sgd_write(addr, size, val);
> +    switch (addr) {
> +    case 0:
> +        if (val & STAT_STOP) {
> +            s->aur.stat &= ~STAT_PAUSED;
> +        }
> +        if (val & STAT_EOL) {
> +            s->aur.stat &= ~(STAT_EOL | STAT_PAUSED);
> +            if (s->aur.type & STAT_EOL) {
> +                via_isa_set_irq(&s->dev, VIA_IRQ_AC97, 0);
> +            }
> +        }
> +        if (val & STAT_FLAG) {
> +            s->aur.stat &= ~(STAT_FLAG | STAT_PAUSED);
> +            if (s->aur.type & STAT_FLAG) {
> +                via_isa_set_irq(&s->dev, VIA_IRQ_AC97, 0);
> +            }
> +        }
> +        break;
> +    case 1:
> +        if (val & CNTL_START) {
> +            AUD_set_active_out(s->vo, 1);
> +            s->aur.stat = STAT_ACTIVE;
> +        }
> +        if (val & CNTL_TERM) {
> +            AUD_set_active_out(s->vo, 0);
> +            s->aur.stat &= ~(STAT_ACTIVE | STAT_PAUSED);
> +            s->aur.clen = 0;
> +        }
> +        if (val & CNTL_PAUSE) {
> +            AUD_set_active_out(s->vo, 0);
> +            s->aur.stat &= ~STAT_ACTIVE;
> +            s->aur.stat |= STAT_PAUSED;
> +        } else if (!(val & CNTL_PAUSE) && (s->aur.stat & STAT_PAUSED)) {
> +            AUD_set_active_out(s->vo, 1);
> +            s->aur.stat |= STAT_ACTIVE;
> +            s->aur.stat &= ~STAT_PAUSED;
> +        }
> +        break;
> +    case 2:
> +    {
> +        uint32_t oldval = s->aur.type;
> +        s->aur.type = val;
> +        if ((oldval & 0x30) != (val & 0x30)) {
> +            open_voice_out(s);
> +        }
> +        break;
> +    }
> +    case 4:
> +        s->aur.base = val & ~1ULL;
> +        break;
> +    case 0x80:
> +        if (val >> 30) {
> +            /* we only have primary codec */
> +            break;
> +        }
> +        if (val & BIT(23)) { /* read reg */
> +            s->ac97_cmd = val & 0xc0ff0000ULL;
> +            s->ac97_cmd |= codec_read(s, (val >> 16) & 0x7f);
> +            s->ac97_cmd |= BIT(25); /* data valid */
> +        } else {
> +            s->ac97_cmd = val & 0xc0ffffffULL;
> +            codec_write(s, (val >> 16) & 0x7f, val);
> +        }
> +        break;
> +    case 0xc:
> +    case 0x84:
> +        /* Read only */
> +        break;
> +    default:
> +        qemu_log_mask(LOG_UNIMP, "via-ac97: Unimplemented register write %"
> +                      HWADDR_PRIx"\n", addr);
> +    }
> +}
> +
> +static const MemoryRegionOps sgd_ops = {
> +    .read = sgd_read,
> +    .write = sgd_write,
> +    .endianness = DEVICE_LITTLE_ENDIAN,
> +};
> +
> +static uint64_t fm_read(void *opaque, hwaddr addr, unsigned size)
> +{
> +    qemu_log_mask(LOG_UNIMP, "%s: 0x%"HWADDR_PRIx" %d\n", __func__, addr, size);
> +    return 0;
> +}
> +
> +static void fm_write(void *opaque, hwaddr addr, uint64_t val, unsigned size)
> +{
> +    qemu_log_mask(LOG_UNIMP, "%s: 0x%"HWADDR_PRIx" %d <= 0x%"PRIX64"\n",
> +                  __func__, addr, size, val);
> +}
> +
> +static const MemoryRegionOps fm_ops = {
> +    .read = fm_read,
> +    .write = fm_write,
> +    .endianness = DEVICE_LITTLE_ENDIAN,
> +};
> +
> +static uint64_t midi_read(void *opaque, hwaddr addr, unsigned size)
> +{
> +    qemu_log_mask(LOG_UNIMP, "%s: 0x%"HWADDR_PRIx" %d\n", __func__, addr, size);
> +    return 0;
> +}
> +
> +static void midi_write(void *opaque, hwaddr addr, uint64_t val, unsigned size)
> +{
> +    qemu_log_mask(LOG_UNIMP, "%s: 0x%"HWADDR_PRIx" %d <= 0x%"PRIX64"\n",
> +                  __func__, addr, size, val);
> +}
> +
> +static const MemoryRegionOps midi_ops = {
> +    .read = midi_read,
> +    .write = midi_write,
> +    .endianness = DEVICE_LITTLE_ENDIAN,
> +};
> +
> +static void via_ac97_reset(DeviceState *dev)
> +{
> +    ViaAC97State *s = VIA_AC97(dev);
> +
> +    codec_reset(s);
> +}
>   
>   static void via_ac97_realize(PCIDevice *pci_dev, Error **errp)
>   {
> +    ViaAC97State *s = VIA_AC97(pci_dev);
> +    Object *o = OBJECT(s);
> +
>       pci_set_word(pci_dev->config + PCI_COMMAND,
>                    PCI_COMMAND_INVALIDATE | PCI_COMMAND_PARITY);
>       pci_set_word(pci_dev->config + PCI_STATUS,
>                    PCI_STATUS_CAP_LIST | PCI_STATUS_DEVSEL_MEDIUM);
>       pci_set_long(pci_dev->config + PCI_INTERRUPT_PIN, 0x03);
> +
> +    memory_region_init_io(&s->sgd, o, &sgd_ops, s, "via-ac97.sgd", 256);
> +    pci_register_bar(pci_dev, 0, PCI_BASE_ADDRESS_SPACE_IO, &s->sgd);
> +    memory_region_init_io(&s->fm, o, &fm_ops, s, "via-ac97.fm", 4);
> +    pci_register_bar(pci_dev, 1, PCI_BASE_ADDRESS_SPACE_IO, &s->fm);
> +    memory_region_init_io(&s->midi, o, &midi_ops, s, "via-ac97.midi", 4);
> +    pci_register_bar(pci_dev, 2, PCI_BASE_ADDRESS_SPACE_IO, &s->midi);
> +
> +    AUD_register_card ("via-ac97", &s->card);
>   }
>   
> +static void via_ac97_exit(PCIDevice *dev)
> +{
> +    ViaAC97State *s = VIA_AC97(dev);
> +
> +    AUD_close_out(&s->card, s->vo);
> +    AUD_remove_card(&s->card);
> +}
> +
> +static Property via_ac97_properties[] = {
> +    DEFINE_AUDIO_PROPERTIES(ViaAC97State, card),
> +    DEFINE_PROP_END_OF_LIST(),
> +};
> +
>   static void via_ac97_class_init(ObjectClass *klass, void *data)
>   {
>       DeviceClass *dc = DEVICE_CLASS(klass);
>       PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
>   
>       k->realize = via_ac97_realize;
> +    k->exit = via_ac97_exit;
>       k->vendor_id = PCI_VENDOR_ID_VIA;
>       k->device_id = PCI_DEVICE_ID_VIA_AC97;
>       k->revision = 0x50;
>       k->class_id = PCI_CLASS_MULTIMEDIA_AUDIO;
> +    device_class_set_props(dc, via_ac97_properties);
>       set_bit(DEVICE_CATEGORY_SOUND, dc->categories);
>       dc->desc = "VIA AC97";
> +    dc->reset = via_ac97_reset;
>       /* Reason: Part of a south bridge chip */
>       dc->user_creatable = false;
>   }
> @@ -41,7 +469,7 @@ static void via_ac97_class_init(ObjectClass *klass, void *data)
>   static const TypeInfo via_ac97_info = {
>       .name          = TYPE_VIA_AC97,
>       .parent        = TYPE_PCI_DEVICE,
> -    .instance_size = sizeof(PCIDevice),
> +    .instance_size = sizeof(ViaAC97State),
>       .class_init    = via_ac97_class_init,
>       .interfaces = (InterfaceInfo[]) {
>           { INTERFACE_CONVENTIONAL_PCI_DEVICE },
> diff --git a/hw/isa/vt82c686.c b/hw/isa/vt82c686.c
> index f31b2fa7ca..b16620daf8 100644
> --- a/hw/isa/vt82c686.c
> +++ b/hw/isa/vt82c686.c
> @@ -555,7 +555,7 @@ struct ViaISAState {
>       PCIIDEState ide;
>       UHCIState uhci[2];
>       ViaPMState pm;
> -    PCIDevice ac97;
> +    ViaAC97State ac97;
>       PCIDevice mc97;
>   };
>   
> @@ -610,6 +610,7 @@ void via_isa_set_irq(PCIDevice *d, ViaISAIRQSourceBit n, int level)
>           break;
>       case VIA_IRQ_USB0:
>       case VIA_IRQ_USB1:
> +    case VIA_IRQ_AC97:
>           max_irq = 14;
>           isa_irq = d->config[PCI_INTERRUPT_LINE];
>           break;
> diff --git a/include/hw/isa/vt82c686.h b/include/hw/isa/vt82c686.h
> index e982c5fe26..f7258d01df 100644
> --- a/include/hw/isa/vt82c686.h
> +++ b/include/hw/isa/vt82c686.h
> @@ -1,6 +1,8 @@
>   #ifndef HW_VT82C686_H
>   #define HW_VT82C686_H
>   
> +#include "hw/pci/pci_device.h"
> +#include "audio/audio.h"
>   
>   #define TYPE_VT82C686B_ISA "vt82c686b-isa"
>   #define TYPE_VT82C686B_USB_UHCI "vt82c686b-usb-uhci"
> @@ -9,6 +11,29 @@
>   #define TYPE_VIA_IDE "via-ide"
>   #define TYPE_VIA_MC97 "via-mc97"
>   
> +typedef struct {
> +    uint8_t stat;
> +    uint8_t type;
> +    uint32_t base;
> +    uint32_t curr;
> +    uint32_t addr;
> +    uint32_t clen;
> +} ViaAC97SGDChannel;
> +
> +OBJECT_DECLARE_SIMPLE_TYPE(ViaAC97State, VIA_AC97);
> +
> +struct ViaAC97State {
> +    PCIDevice dev;
> +    QEMUSoundCard card;
> +    MemoryRegion sgd;
> +    MemoryRegion fm;
> +    MemoryRegion midi;
> +    SWVoiceOut *vo;
> +    ViaAC97SGDChannel aur;
> +    uint16_t codec_regs[128];
> +    uint32_t ac97_cmd;
> +};
> +
>   typedef enum {
>       VIA_IRQ_IDE0 = 0,
>       VIA_IRQ_IDE1 = 1,
> @@ -18,6 +43,7 @@ typedef enum {
>       VIA_IRQ_PIRQB = 5,
>       VIA_IRQ_PIRQC = 6,
>       VIA_IRQ_PIRQD = 7,
> +    VIA_IRQ_AC97 = 8,
>   } ViaISAIRQSourceBit;
>   
>   void via_isa_set_irq(PCIDevice *d, ViaISAIRQSourceBit n, int level);
Bernhard Beschow Feb. 24, 2023, 9:53 a.m. UTC | #2
Am 23. Februar 2023 19:40:03 UTC schrieb "Volker Rümelin" <vr_qemu@t-online.de>:
>Am 21.02.23 um 19:44 schrieb BALATON Zoltan:
>> This adds basic implementation of the AC'97 sound part used in VIA
>> south bridge chips. Not all features of the device is emulated, only
>> one playback channel is supported but this is enough to get sound
>> output from some guests running on machines using this device such as
>> pegasos2.
>> 
>> Signed-off-by: BALATON Zoltan <balaton@eik.bme.hu>
>> ---
>>   hw/audio/trace-events     |   6 +
>>   hw/audio/via-ac97.c       | 436 +++++++++++++++++++++++++++++++++++++-
>>   hw/isa/vt82c686.c         |   3 +-
>>   include/hw/isa/vt82c686.h |  26 +++
>>   4 files changed, 466 insertions(+), 5 deletions(-)
>> 
>> diff --git a/hw/audio/trace-events b/hw/audio/trace-events
>> index e0e71cd9b1..6eccdaa4b5 100644
>> --- a/hw/audio/trace-events
>> +++ b/hw/audio/trace-events
>> @@ -11,3 +11,9 @@ hda_audio_running(const char *stream, int nr, bool running) "st %s, nr %d, run %
>>   hda_audio_format(const char *stream, int chan, const char *fmt, int freq) "st %s, %d x %s @ %d Hz"
>>   hda_audio_adjust(const char *stream, int pos) "st %s, pos %d"
>>   hda_audio_overrun(const char *stream) "st %s"
>> +
>> +#via-ac97.c
>> +via_ac97_codec_write(uint8_t addr, uint16_t val) "0x%x <- 0x%x"
>> +via_ac97_sgd_fetch(uint32_t addr, char stop, char eol, char flag, uint32_t len) "addr=0x%x %c%c%c len=%d"
>> +via_ac97_sgd_read(uint64_t addr, unsigned size, uint64_t val) "0x%"PRIx64" %d -> 0x%"PRIx64
>> +via_ac97_sgd_write(uint64_t addr, unsigned size, uint64_t val) "0x%"PRIx64" %d <- 0x%"PRIx64
>> diff --git a/hw/audio/via-ac97.c b/hw/audio/via-ac97.c
>> index d1a856f63d..cdac5bc14b 100644
>> --- a/hw/audio/via-ac97.c
>> +++ b/hw/audio/via-ac97.c
>> @@ -1,39 +1,467 @@
>>   /*
>>    * VIA south bridges sound support
>>    *
>> + * Copyright (c) 2022-2023 BALATON Zoltan
>> + *
>>    * This work is licensed under the GNU GPL license version 2 or later.
>>    */
>>     /*
>> - * TODO: This is entirely boiler plate just registering empty PCI devices
>> - * with the right ID guests expect, functionality should be added here.
>> + * TODO: This is only a basic implementation of one audio playback channel
>> + *       more functionality should be added here.
>>    */
>>     #include "qemu/osdep.h"
>> +#include "qemu/log.h"
>>   #include "hw/isa/vt82c686.h"
>> -#include "hw/pci/pci_device.h"
>> +#include "ac97.h"
>> +#include "trace.h"
>> +
>> +#define CLEN_IS_EOL(x)  ((x)->clen & BIT(31))
>> +#define CLEN_IS_FLAG(x) ((x)->clen & BIT(30))
>> +#define CLEN_IS_STOP(x) ((x)->clen & BIT(29))
>> +#define CLEN_LEN(x)     ((x)->clen & 0xfff)
>
>Hi,
>
>the CLEN_LEN mask is 0xffffff instead of 0xfff. The SGD Table Base Count has 24 bits.

This fixes sound for a x86 Linux guest on my pc-via branch.

Best regards,
Bernhard

>
>With best regards,
>Volker
>
>> +
>> +#define STAT_ACTIVE BIT(7)
>> +#define STAT_PAUSED BIT(6)
>> +#define STAT_TRIG   BIT(3)
>> +#define STAT_STOP   BIT(2)
>> +#define STAT_EOL    BIT(1)
>> +#define STAT_FLAG   BIT(0)
>> +
>> +#define CNTL_START  BIT(7)
>> +#define CNTL_TERM   BIT(6)
>> +#define CNTL_PAUSE  BIT(3)
>> +
>> +static void open_voice_out(ViaAC97State *s);
>> +
>> +static uint16_t codec_rates[] = { 8000, 11025, 16000, 22050, 32000, 44100,
>> +                                  48000 };
>> +
>> +#define CODEC_REG(s, o)  ((s)->codec_regs[(o) / 2])
>> +#define CODEC_VOL(vol, mask)  ((255 * ((vol) & mask)) / mask)
>> +
>> +static void codec_volume_set_out(ViaAC97State *s)
>> +{
>> +    int lvol, rvol, mute;
>> +
>> +    lvol = 255 - CODEC_VOL(CODEC_REG(s, AC97_Master_Volume_Mute) >> 8, 0x1f);
>> +    lvol *= 255 - CODEC_VOL(CODEC_REG(s, AC97_PCM_Out_Volume_Mute) >> 8, 0x1f);
>> +    lvol /= 255;
>> +    rvol = 255 - CODEC_VOL(CODEC_REG(s, AC97_Master_Volume_Mute), 0x1f);
>> +    rvol *= 255 - CODEC_VOL(CODEC_REG(s, AC97_PCM_Out_Volume_Mute), 0x1f);
>> +    rvol /= 255;
>> +    mute = CODEC_REG(s, AC97_Master_Volume_Mute) >> MUTE_SHIFT;
>> +    mute |= CODEC_REG(s, AC97_PCM_Out_Volume_Mute) >> MUTE_SHIFT;
>> +    AUD_set_volume_out(s->vo, mute, lvol, rvol);
>> +}
>> +
>> +static void codec_reset(ViaAC97State *s)
>> +{
>> +    memset(s->codec_regs, 0, sizeof(s->codec_regs));
>> +    CODEC_REG(s, AC97_Reset) = 0x6a90;
>> +    CODEC_REG(s, AC97_Master_Volume_Mute) = 0x8000;
>> +    CODEC_REG(s, AC97_Headphone_Volume_Mute) = 0x8000;
>> +    CODEC_REG(s, AC97_Master_Volume_Mono_Mute) = 0x8000;
>> +    CODEC_REG(s, AC97_Phone_Volume_Mute) = 0x8008;
>> +    CODEC_REG(s, AC97_Mic_Volume_Mute) = 0x8008;
>> +    CODEC_REG(s, AC97_Line_In_Volume_Mute) = 0x8808;
>> +    CODEC_REG(s, AC97_CD_Volume_Mute) = 0x8808;
>> +    CODEC_REG(s, AC97_Video_Volume_Mute) = 0x8808;
>> +    CODEC_REG(s, AC97_Aux_Volume_Mute) = 0x8808;
>> +    CODEC_REG(s, AC97_PCM_Out_Volume_Mute) = 0x8808;
>> +    CODEC_REG(s, AC97_Record_Gain_Mute) = 0x8000;
>> +    CODEC_REG(s, AC97_Powerdown_Ctrl_Stat) = 0x000f;
>> +    CODEC_REG(s, AC97_Extended_Audio_ID) = 0x0a05;
>> +    CODEC_REG(s, AC97_Extended_Audio_Ctrl_Stat) = 0x0400;
>> +    CODEC_REG(s, AC97_PCM_Front_DAC_Rate) = 48000;
>> +    CODEC_REG(s, AC97_PCM_LR_ADC_Rate) = 48000;
>> +    /* Sigmatel 9766 (STAC9766) */
>> +    CODEC_REG(s, AC97_Vendor_ID1) = 0x8384;
>> +    CODEC_REG(s, AC97_Vendor_ID2) = 0x7666;
>> +}
>> +
>> +static uint16_t codec_read(ViaAC97State *s, uint8_t addr)
>> +{
>> +    return CODEC_REG(s, addr);
>> +}
>> +
>> +static void codec_write(ViaAC97State *s, uint8_t addr, uint16_t val)
>> +{
>> +    trace_via_ac97_codec_write(addr, val);
>> +    switch (addr) {
>> +    case AC97_Reset:
>> +        codec_reset(s);
>> +        return;
>> +    case AC97_Master_Volume_Mute:
>> +    case AC97_PCM_Out_Volume_Mute:
>> +        if (addr == AC97_Master_Volume_Mute) {
>> +            if (val & BIT(13)) {
>> +                val |= 0x1f00;
>> +            }
>> +            if (val & BIT(5)) {
>> +                val |= 0x1f;
>> +            }
>> +        }
>> +        CODEC_REG(s, addr) = val & 0x9f1f;
>> +        codec_volume_set_out(s);
>> +        return;
>> +    case AC97_Extended_Audio_Ctrl_Stat:
>> +        CODEC_REG(s, addr) &= ~EACS_VRA;
>> +        CODEC_REG(s, addr) |= val & EACS_VRA;
>> +        if (!(val & EACS_VRA)) {
>> +            CODEC_REG(s, AC97_PCM_Front_DAC_Rate) = 48000;
>> +            CODEC_REG(s, AC97_PCM_LR_ADC_Rate) = 48000;
>> +            open_voice_out(s);
>> +        }
>> +        return;
>> +    case AC97_PCM_Front_DAC_Rate:
>> +    case AC97_PCM_LR_ADC_Rate:
>> +        if (CODEC_REG(s, AC97_Extended_Audio_Ctrl_Stat) & EACS_VRA) {
>> +            int i;
>> +            uint16_t rate = val;
>> +
>> +            for (i = 0; i < ARRAY_SIZE(codec_rates) - 1; i++) {
>> +                if (rate < codec_rates[i] +
>> +                    (codec_rates[i + 1] - codec_rates[i]) / 2) {
>> +                    rate = codec_rates[i];
>> +                    break;
>> +                }
>> +            }
>> +            if (rate > 48000) {
>> +                rate = 48000;
>> +            }
>> +            CODEC_REG(s, addr) = rate;
>> +            open_voice_out(s);
>> +        }
>> +        return;
>> +    case AC97_Powerdown_Ctrl_Stat:
>> +        CODEC_REG(s, addr) = (val & 0xff00) | (CODEC_REG(s, addr) & 0xff);
>> +        return;
>> +    case AC97_Extended_Audio_ID:
>> +    case AC97_Vendor_ID1:
>> +    case AC97_Vendor_ID2:
>> +        /* Read only registers */
>> +        return;
>> +    default:
>> +        qemu_log_mask(LOG_UNIMP,
>> +                      "via-ac97: Unimplemented codec register 0x%x\n", addr);
>> +        CODEC_REG(s, addr) = val;
>> +    }
>> +}
>> +
>> +static void fetch_sgd(ViaAC97SGDChannel *c, PCIDevice *d)
>> +{
>> +    uint32_t b[2];
>> +
>> +    if (c->curr < c->base) {
>> +        c->curr = c->base;
>> +    }
>> +    pci_dma_read(d, c->curr, b, sizeof(b));
>> +    c->addr = le32_to_cpu(b[0]);
>> +    c->clen = le32_to_cpu(b[1]);
>> +    trace_via_ac97_sgd_fetch(c->addr, CLEN_IS_STOP(c) ? 'S' : '-',
>> +                             CLEN_IS_EOL(c) ? 'E' : '-',
>> +                             CLEN_IS_FLAG(c) ? 'F' : '-', CLEN_LEN(c));
>> +}
>> +
>> +static void out_cb(void *opaque, int avail)
>> +{
>> +    ViaAC97State *s = opaque;
>> +    ViaAC97SGDChannel *c = &s->aur;
>> +    int temp, to_copy, copied;
>> +    bool stop = false;
>> +    uint8_t tmpbuf[4096];
>> +
>> +    if (c->stat & STAT_PAUSED) {
>> +        return;
>> +    }
>> +    c->stat |= STAT_ACTIVE;
>> +    while (avail && !stop) {
>> +        if (!c->clen) {
>> +            fetch_sgd(c, &s->dev);
>> +        }
>> +        temp = MIN(CLEN_LEN(c), avail);
>> +        while (temp) {
>> +            to_copy = MIN(temp, sizeof(tmpbuf));
>> +            pci_dma_read(&s->dev, c->addr, tmpbuf, to_copy);
>> +            copied = AUD_write(s->vo, tmpbuf, to_copy);
>> +            if (!copied) {
>> +                stop = true;
>> +                break;
>> +            }
>> +            temp -= copied;
>> +            avail -= copied;
>> +            c->addr += copied;
>> +            c->clen -= copied;
>> +        }
>> +        if (CLEN_LEN(c) == 0) {
>> +            c->curr += 8;
>> +            if (CLEN_IS_EOL(c)) {
>> +                c->stat |= STAT_EOL;
>> +                if (c->type & CNTL_START) {
>> +                    c->curr = c->base;
>> +                    c->stat |= STAT_PAUSED;
>> +                } else {
>> +                    c->stat &= ~STAT_ACTIVE;
>> +                    AUD_set_active_out(s->vo, 0);
>> +                }
>> +                if (c->type & STAT_EOL) {
>> +                    via_isa_set_irq(&s->dev, VIA_IRQ_AC97, 1);
>> +                }
>> +            }
>> +            if (CLEN_IS_FLAG(c)) {
>> +                c->stat |= STAT_FLAG;
>> +                c->stat |= STAT_PAUSED;
>> +                if (c->type & STAT_FLAG) {
>> +                    via_isa_set_irq(&s->dev, VIA_IRQ_AC97, 1);
>> +                }
>> +            }
>> +            if (CLEN_IS_STOP(c)) {
>> +                c->stat |= STAT_STOP;
>> +                c->stat |= STAT_PAUSED;
>> +            }
>> +            c->clen = 0;
>> +            stop = true;
>> +        }
>> +    }
>> +}
>> +
>> +static void open_voice_out(ViaAC97State *s)
>> +{
>> +    struct audsettings as = {
>> +        .freq = CODEC_REG(s, AC97_PCM_Front_DAC_Rate),
>> +        .nchannels = s->aur.type & BIT(4) ? 2 : 1,
>> +        .fmt = s->aur.type & BIT(5) ? AUDIO_FORMAT_S16 : AUDIO_FORMAT_S8,
>> +        .endianness = 0,
>> +    };
>> +    s->vo = AUD_open_out(&s->card, s->vo, "via-ac97.out", s, out_cb, &as);
>> +}
>> +
>> +static uint64_t sgd_read(void *opaque, hwaddr addr, unsigned size)
>> +{
>> +    ViaAC97State *s = opaque;
>> +    uint64_t val = 0;
>> +
>> +    switch (addr) {
>> +    case 0:
>> +        val = s->aur.stat;
>> +        if (s->aur.type & CNTL_START) {
>> +            val |= STAT_TRIG;
>> +        }
>> +        break;
>> +    case 1:
>> +        val = s->aur.stat & STAT_PAUSED ? BIT(3) : 0;
>> +        break;
>> +    case 2:
>> +        val = s->aur.type;
>> +        break;
>> +    case 4:
>> +        val = s->aur.curr;
>> +        break;
>> +    case 0xc:
>> +        val = CLEN_LEN(&s->aur);
>> +        break;
>> +    case 0x80:
>> +        val = s->ac97_cmd;
>> +        break;
>> +    case 0x84:
>> +        val = s->aur.stat & STAT_FLAG;
>> +        if (s->aur.stat & STAT_EOL) {
>> +            val |= BIT(4);
>> +        }
>> +        if (s->aur.stat & STAT_STOP) {
>> +            val |= BIT(8);
>> +        }
>> +        if (s->aur.stat & STAT_ACTIVE) {
>> +            val |= BIT(12);
>> +        }
>> +        break;
>> +    default:
>> +        qemu_log_mask(LOG_UNIMP, "via-ac97: Unimplemented register read %"
>> +                      HWADDR_PRIx"\n", addr);
>> +    }
>> +    trace_via_ac97_sgd_read(addr, size, val);
>> +    return val;
>> +}
>> +
>> +static void sgd_write(void *opaque, hwaddr addr, uint64_t val, unsigned size)
>> +{
>> +    ViaAC97State *s = opaque;
>> +
>> +    trace_via_ac97_sgd_write(addr, size, val);
>> +    switch (addr) {
>> +    case 0:
>> +        if (val & STAT_STOP) {
>> +            s->aur.stat &= ~STAT_PAUSED;
>> +        }
>> +        if (val & STAT_EOL) {
>> +            s->aur.stat &= ~(STAT_EOL | STAT_PAUSED);
>> +            if (s->aur.type & STAT_EOL) {
>> +                via_isa_set_irq(&s->dev, VIA_IRQ_AC97, 0);
>> +            }
>> +        }
>> +        if (val & STAT_FLAG) {
>> +            s->aur.stat &= ~(STAT_FLAG | STAT_PAUSED);
>> +            if (s->aur.type & STAT_FLAG) {
>> +                via_isa_set_irq(&s->dev, VIA_IRQ_AC97, 0);
>> +            }
>> +        }
>> +        break;
>> +    case 1:
>> +        if (val & CNTL_START) {
>> +            AUD_set_active_out(s->vo, 1);
>> +            s->aur.stat = STAT_ACTIVE;
>> +        }
>> +        if (val & CNTL_TERM) {
>> +            AUD_set_active_out(s->vo, 0);
>> +            s->aur.stat &= ~(STAT_ACTIVE | STAT_PAUSED);
>> +            s->aur.clen = 0;
>> +        }
>> +        if (val & CNTL_PAUSE) {
>> +            AUD_set_active_out(s->vo, 0);
>> +            s->aur.stat &= ~STAT_ACTIVE;
>> +            s->aur.stat |= STAT_PAUSED;
>> +        } else if (!(val & CNTL_PAUSE) && (s->aur.stat & STAT_PAUSED)) {
>> +            AUD_set_active_out(s->vo, 1);
>> +            s->aur.stat |= STAT_ACTIVE;
>> +            s->aur.stat &= ~STAT_PAUSED;
>> +        }
>> +        break;
>> +    case 2:
>> +    {
>> +        uint32_t oldval = s->aur.type;
>> +        s->aur.type = val;
>> +        if ((oldval & 0x30) != (val & 0x30)) {
>> +            open_voice_out(s);
>> +        }
>> +        break;
>> +    }
>> +    case 4:
>> +        s->aur.base = val & ~1ULL;
>> +        break;
>> +    case 0x80:
>> +        if (val >> 30) {
>> +            /* we only have primary codec */
>> +            break;
>> +        }
>> +        if (val & BIT(23)) { /* read reg */
>> +            s->ac97_cmd = val & 0xc0ff0000ULL;
>> +            s->ac97_cmd |= codec_read(s, (val >> 16) & 0x7f);
>> +            s->ac97_cmd |= BIT(25); /* data valid */
>> +        } else {
>> +            s->ac97_cmd = val & 0xc0ffffffULL;
>> +            codec_write(s, (val >> 16) & 0x7f, val);
>> +        }
>> +        break;
>> +    case 0xc:
>> +    case 0x84:
>> +        /* Read only */
>> +        break;
>> +    default:
>> +        qemu_log_mask(LOG_UNIMP, "via-ac97: Unimplemented register write %"
>> +                      HWADDR_PRIx"\n", addr);
>> +    }
>> +}
>> +
>> +static const MemoryRegionOps sgd_ops = {
>> +    .read = sgd_read,
>> +    .write = sgd_write,
>> +    .endianness = DEVICE_LITTLE_ENDIAN,
>> +};
>> +
>> +static uint64_t fm_read(void *opaque, hwaddr addr, unsigned size)
>> +{
>> +    qemu_log_mask(LOG_UNIMP, "%s: 0x%"HWADDR_PRIx" %d\n", __func__, addr, size);
>> +    return 0;
>> +}
>> +
>> +static void fm_write(void *opaque, hwaddr addr, uint64_t val, unsigned size)
>> +{
>> +    qemu_log_mask(LOG_UNIMP, "%s: 0x%"HWADDR_PRIx" %d <= 0x%"PRIX64"\n",
>> +                  __func__, addr, size, val);
>> +}
>> +
>> +static const MemoryRegionOps fm_ops = {
>> +    .read = fm_read,
>> +    .write = fm_write,
>> +    .endianness = DEVICE_LITTLE_ENDIAN,
>> +};
>> +
>> +static uint64_t midi_read(void *opaque, hwaddr addr, unsigned size)
>> +{
>> +    qemu_log_mask(LOG_UNIMP, "%s: 0x%"HWADDR_PRIx" %d\n", __func__, addr, size);
>> +    return 0;
>> +}
>> +
>> +static void midi_write(void *opaque, hwaddr addr, uint64_t val, unsigned size)
>> +{
>> +    qemu_log_mask(LOG_UNIMP, "%s: 0x%"HWADDR_PRIx" %d <= 0x%"PRIX64"\n",
>> +                  __func__, addr, size, val);
>> +}
>> +
>> +static const MemoryRegionOps midi_ops = {
>> +    .read = midi_read,
>> +    .write = midi_write,
>> +    .endianness = DEVICE_LITTLE_ENDIAN,
>> +};
>> +
>> +static void via_ac97_reset(DeviceState *dev)
>> +{
>> +    ViaAC97State *s = VIA_AC97(dev);
>> +
>> +    codec_reset(s);
>> +}
>>     static void via_ac97_realize(PCIDevice *pci_dev, Error **errp)
>>   {
>> +    ViaAC97State *s = VIA_AC97(pci_dev);
>> +    Object *o = OBJECT(s);
>> +
>>       pci_set_word(pci_dev->config + PCI_COMMAND,
>>                    PCI_COMMAND_INVALIDATE | PCI_COMMAND_PARITY);
>>       pci_set_word(pci_dev->config + PCI_STATUS,
>>                    PCI_STATUS_CAP_LIST | PCI_STATUS_DEVSEL_MEDIUM);
>>       pci_set_long(pci_dev->config + PCI_INTERRUPT_PIN, 0x03);
>> +
>> +    memory_region_init_io(&s->sgd, o, &sgd_ops, s, "via-ac97.sgd", 256);
>> +    pci_register_bar(pci_dev, 0, PCI_BASE_ADDRESS_SPACE_IO, &s->sgd);
>> +    memory_region_init_io(&s->fm, o, &fm_ops, s, "via-ac97.fm", 4);
>> +    pci_register_bar(pci_dev, 1, PCI_BASE_ADDRESS_SPACE_IO, &s->fm);
>> +    memory_region_init_io(&s->midi, o, &midi_ops, s, "via-ac97.midi", 4);
>> +    pci_register_bar(pci_dev, 2, PCI_BASE_ADDRESS_SPACE_IO, &s->midi);
>> +
>> +    AUD_register_card ("via-ac97", &s->card);
>>   }
>>   +static void via_ac97_exit(PCIDevice *dev)
>> +{
>> +    ViaAC97State *s = VIA_AC97(dev);
>> +
>> +    AUD_close_out(&s->card, s->vo);
>> +    AUD_remove_card(&s->card);
>> +}
>> +
>> +static Property via_ac97_properties[] = {
>> +    DEFINE_AUDIO_PROPERTIES(ViaAC97State, card),
>> +    DEFINE_PROP_END_OF_LIST(),
>> +};
>> +
>>   static void via_ac97_class_init(ObjectClass *klass, void *data)
>>   {
>>       DeviceClass *dc = DEVICE_CLASS(klass);
>>       PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
>>         k->realize = via_ac97_realize;
>> +    k->exit = via_ac97_exit;
>>       k->vendor_id = PCI_VENDOR_ID_VIA;
>>       k->device_id = PCI_DEVICE_ID_VIA_AC97;
>>       k->revision = 0x50;
>>       k->class_id = PCI_CLASS_MULTIMEDIA_AUDIO;
>> +    device_class_set_props(dc, via_ac97_properties);
>>       set_bit(DEVICE_CATEGORY_SOUND, dc->categories);
>>       dc->desc = "VIA AC97";
>> +    dc->reset = via_ac97_reset;
>>       /* Reason: Part of a south bridge chip */
>>       dc->user_creatable = false;
>>   }
>> @@ -41,7 +469,7 @@ static void via_ac97_class_init(ObjectClass *klass, void *data)
>>   static const TypeInfo via_ac97_info = {
>>       .name          = TYPE_VIA_AC97,
>>       .parent        = TYPE_PCI_DEVICE,
>> -    .instance_size = sizeof(PCIDevice),
>> +    .instance_size = sizeof(ViaAC97State),
>>       .class_init    = via_ac97_class_init,
>>       .interfaces = (InterfaceInfo[]) {
>>           { INTERFACE_CONVENTIONAL_PCI_DEVICE },
>> diff --git a/hw/isa/vt82c686.c b/hw/isa/vt82c686.c
>> index f31b2fa7ca..b16620daf8 100644
>> --- a/hw/isa/vt82c686.c
>> +++ b/hw/isa/vt82c686.c
>> @@ -555,7 +555,7 @@ struct ViaISAState {
>>       PCIIDEState ide;
>>       UHCIState uhci[2];
>>       ViaPMState pm;
>> -    PCIDevice ac97;
>> +    ViaAC97State ac97;
>>       PCIDevice mc97;
>>   };
>>   @@ -610,6 +610,7 @@ void via_isa_set_irq(PCIDevice *d, ViaISAIRQSourceBit n, int level)
>>           break;
>>       case VIA_IRQ_USB0:
>>       case VIA_IRQ_USB1:
>> +    case VIA_IRQ_AC97:
>>           max_irq = 14;
>>           isa_irq = d->config[PCI_INTERRUPT_LINE];
>>           break;
>> diff --git a/include/hw/isa/vt82c686.h b/include/hw/isa/vt82c686.h
>> index e982c5fe26..f7258d01df 100644
>> --- a/include/hw/isa/vt82c686.h
>> +++ b/include/hw/isa/vt82c686.h
>> @@ -1,6 +1,8 @@
>>   #ifndef HW_VT82C686_H
>>   #define HW_VT82C686_H
>>   +#include "hw/pci/pci_device.h"
>> +#include "audio/audio.h"
>>     #define TYPE_VT82C686B_ISA "vt82c686b-isa"
>>   #define TYPE_VT82C686B_USB_UHCI "vt82c686b-usb-uhci"
>> @@ -9,6 +11,29 @@
>>   #define TYPE_VIA_IDE "via-ide"
>>   #define TYPE_VIA_MC97 "via-mc97"
>>   +typedef struct {
>> +    uint8_t stat;
>> +    uint8_t type;
>> +    uint32_t base;
>> +    uint32_t curr;
>> +    uint32_t addr;
>> +    uint32_t clen;
>> +} ViaAC97SGDChannel;
>> +
>> +OBJECT_DECLARE_SIMPLE_TYPE(ViaAC97State, VIA_AC97);
>> +
>> +struct ViaAC97State {
>> +    PCIDevice dev;
>> +    QEMUSoundCard card;
>> +    MemoryRegion sgd;
>> +    MemoryRegion fm;
>> +    MemoryRegion midi;
>> +    SWVoiceOut *vo;
>> +    ViaAC97SGDChannel aur;
>> +    uint16_t codec_regs[128];
>> +    uint32_t ac97_cmd;
>> +};
>> +
>>   typedef enum {
>>       VIA_IRQ_IDE0 = 0,
>>       VIA_IRQ_IDE1 = 1,
>> @@ -18,6 +43,7 @@ typedef enum {
>>       VIA_IRQ_PIRQB = 5,
>>       VIA_IRQ_PIRQC = 6,
>>       VIA_IRQ_PIRQD = 7,
>> +    VIA_IRQ_AC97 = 8,
>>   } ViaISAIRQSourceBit;
>>     void via_isa_set_irq(PCIDevice *d, ViaISAIRQSourceBit n, int level);
>
BALATON Zoltan Feb. 24, 2023, 12:26 p.m. UTC | #3
On Fri, 24 Feb 2023, Bernhard Beschow wrote:
> Am 23. Februar 2023 19:40:03 UTC schrieb "Volker Rümelin" <vr_qemu@t-online.de>:
>> Am 21.02.23 um 19:44 schrieb BALATON Zoltan:
>>> This adds basic implementation of the AC'97 sound part used in VIA
>>> south bridge chips. Not all features of the device is emulated, only
>>> one playback channel is supported but this is enough to get sound
>>> output from some guests running on machines using this device such as
>>> pegasos2.
>>>
>>> Signed-off-by: BALATON Zoltan <balaton@eik.bme.hu>
>>> ---
>>>   hw/audio/trace-events     |   6 +
>>>   hw/audio/via-ac97.c       | 436 +++++++++++++++++++++++++++++++++++++-
>>>   hw/isa/vt82c686.c         |   3 +-
>>>   include/hw/isa/vt82c686.h |  26 +++
>>>   4 files changed, 466 insertions(+), 5 deletions(-)
>>>
>>> diff --git a/hw/audio/trace-events b/hw/audio/trace-events
>>> index e0e71cd9b1..6eccdaa4b5 100644
>>> --- a/hw/audio/trace-events
>>> +++ b/hw/audio/trace-events
>>> @@ -11,3 +11,9 @@ hda_audio_running(const char *stream, int nr, bool running) "st %s, nr %d, run %
>>>   hda_audio_format(const char *stream, int chan, const char *fmt, int freq) "st %s, %d x %s @ %d Hz"
>>>   hda_audio_adjust(const char *stream, int pos) "st %s, pos %d"
>>>   hda_audio_overrun(const char *stream) "st %s"
>>> +
>>> +#via-ac97.c
>>> +via_ac97_codec_write(uint8_t addr, uint16_t val) "0x%x <- 0x%x"
>>> +via_ac97_sgd_fetch(uint32_t addr, char stop, char eol, char flag, uint32_t len) "addr=0x%x %c%c%c len=%d"
>>> +via_ac97_sgd_read(uint64_t addr, unsigned size, uint64_t val) "0x%"PRIx64" %d -> 0x%"PRIx64
>>> +via_ac97_sgd_write(uint64_t addr, unsigned size, uint64_t val) "0x%"PRIx64" %d <- 0x%"PRIx64
>>> diff --git a/hw/audio/via-ac97.c b/hw/audio/via-ac97.c
>>> index d1a856f63d..cdac5bc14b 100644
>>> --- a/hw/audio/via-ac97.c
>>> +++ b/hw/audio/via-ac97.c
>>> @@ -1,39 +1,467 @@
>>>   /*
>>>    * VIA south bridges sound support
>>>    *
>>> + * Copyright (c) 2022-2023 BALATON Zoltan
>>> + *
>>>    * This work is licensed under the GNU GPL license version 2 or later.
>>>    */
>>>     /*
>>> - * TODO: This is entirely boiler plate just registering empty PCI devices
>>> - * with the right ID guests expect, functionality should be added here.
>>> + * TODO: This is only a basic implementation of one audio playback channel
>>> + *       more functionality should be added here.
>>>    */
>>>     #include "qemu/osdep.h"
>>> +#include "qemu/log.h"
>>>   #include "hw/isa/vt82c686.h"
>>> -#include "hw/pci/pci_device.h"
>>> +#include "ac97.h"
>>> +#include "trace.h"
>>> +
>>> +#define CLEN_IS_EOL(x)  ((x)->clen & BIT(31))
>>> +#define CLEN_IS_FLAG(x) ((x)->clen & BIT(30))
>>> +#define CLEN_IS_STOP(x) ((x)->clen & BIT(29))
>>> +#define CLEN_LEN(x)     ((x)->clen & 0xfff)
>>
>> Hi,
>>
>> the CLEN_LEN mask is 0xffffff instead of 0xfff. The SGD Table Base Count has 24 bits.
>
> This fixes sound for a x86 Linux guest on my pc-via branch.

Thanks, will be in next version. Not sure where have I lost that, maybe 
accidenatlly deleted part of the constant on a rebase and did not notice.
Bernhard does that mean you give a Tested-by for this patch?

Regards,
BALATON Zoltan
BALATON Zoltan Feb. 24, 2023, 1:05 p.m. UTC | #4
Just for some motivation, this is what we want to make possible for more 
people with QEMU 8.0: https://youtu.be/YY9RExl4VDI

This would need at least my SM502 patches and fixing the IRQ routing in 
the VT8231 (this test was with my series for that, I'll ask testing 
Bernhard's version the same way once I get it and rebase my patches on 
it). AmigaOS can use ES1370 so the via-ac97 patches are not that important 
now but the other patches would be needed. I hope users won't have to wait 
until September to try this.

While AmigaOS runs on sam460ex now, that versiion is much slower due to 
the limited PPC440 that doesn't have AltiVec and also due to some issues 
on the real platform it has to do more synchronisation which slows it 
down. The pegasos2 version runs much better and has a better chance to get 
it to work with KVM on PPC host so this would be a big improvement even if 
there would be more bugs to fix in upcoming releases but we'll never find 
those unless people can start using it and report them. More people are 
interested but less are able to compile and test from git repos and depend 
on binary distros instead.

Regards,
BALATON Zoltan
Daniel Henrique Barboza Feb. 24, 2023, 5:14 p.m. UTC | #5
On 2/24/23 10:05, BALATON Zoltan wrote:
> Just for some motivation, this is what we want to make possible for more people with QEMU 8.0: https://youtu.be/YY9RExl4VDI

That's neat!

> 
> This would need at least my SM502 patches and fixing the IRQ routing in the VT8231 (this test was with my series for that, I'll ask testing Bernhard's version the same way once I get it and rebase my patches on it). AmigaOS can use ES1370 so the via-ac97 patches are not that important now but the other patches would be needed. I hope users won't have to wait until September to try this.

If you're referring to "hw/display/sm501: Implement more 2D raster operations" then it's already
queued in ppc-next. In fact I believe you can re-send it with this series, with my r-b, and whoever
gets to send the PR can send everything at once.

If either Gerd of Phil wants to pick these up I have already acked the pegasos2 changes.

If you want me to get this via qemu-ppc bear in mind that I need to send the PR March 7th
at the latest.


Thanks,


Daniel

> 
> While AmigaOS runs on sam460ex now, that versiion is much slower due to the limited PPC440 that doesn't have AltiVec and also due to some issues on the real platform it has to do more synchronisation which slows it down. The pegasos2 version runs much better and has a better chance to get it to work with KVM on PPC host so this would be a big improvement even if there would be more bugs to fix in upcoming releases but we'll never find those unless people can start using it and report them. More people are interested but less are able to compile and test from git repos and depend on binary distros instead.
> 
> Regards,
> BALATON Zoltan
BALATON Zoltan Feb. 24, 2023, 9:08 p.m. UTC | #6
On Fri, 24 Feb 2023, Daniel Henrique Barboza wrote:
> On 2/24/23 10:05, BALATON Zoltan wrote:
>> Just for some motivation, this is what we want to make possible for more 
>> people with QEMU 8.0: https://youtu.be/YY9RExl4VDI
>
> That's neat!
>
>> 
>> This would need at least my SM502 patches and fixing the IRQ routing in the 
>> VT8231 (this test was with my series for that, I'll ask testing Bernhard's 
>> version the same way once I get it and rebase my patches on it). AmigaOS 
>> can use ES1370 so the via-ac97 patches are not that important now but the 
>> other patches would be needed. I hope users won't have to wait until 
>> September to try this.
>
> If you're referring to "hw/display/sm501: Implement more 2D raster 
> operations" then it's already
> queued in ppc-next.

That one and another I've sent yesterday that adds fallbacks for pixman 
routines.

> In fact I believe you can re-send it with this series, 
> with my r-b, and whoever
> gets to send the PR can send everything at once.

OK I'll collect all the patches in one series and submit it early next 
week. Hopefully we can test over the weekend and address any comments by 
the freeze.

> If either Gerd of Phil wants to pick these up I have already acked the 
> pegasos2 changes.
>
> If you want me to get this via qemu-ppc bear in mind that I need to send the 
> PR March 7th
> at the latest.

I'm aware of that, although if we get more CI outage which stops pulls 
maybe we should consider 1-2 week extension, but I'm planning accordingly 
and if there won't be unexpected delays (e.g. new late reviews asking for 
extensive changes) this should be ready a week before the freeze. I think 
we can do one or two more versions/

Regards,
BALATON Zoltan
Daniel Henrique Barboza Feb. 25, 2023, 8:58 a.m. UTC | #7
On 2/24/23 18:08, BALATON Zoltan wrote:
> On Fri, 24 Feb 2023, Daniel Henrique Barboza wrote:
>> On 2/24/23 10:05, BALATON Zoltan wrote:
>>> Just for some motivation, this is what we want to make possible for more people with QEMU 8.0: https://youtu.be/YY9RExl4VDI
>>
>> That's neat!
>>
>>>
>>> This would need at least my SM502 patches and fixing the IRQ routing in the VT8231 (this test was with my series for that, I'll ask testing Bernhard's version the same way once I get it and rebase my patches on it). AmigaOS can use ES1370 so the via-ac97 patches are not that important now but the other patches would be needed. I hope users won't have to wait until September to try this.
>>
>> If you're referring to "hw/display/sm501: Implement more 2D raster operations" then it's already
>> queued in ppc-next.
> 
> That one and another I've sent yesterday that adds fallbacks for pixman routines.
> 
>> In fact I believe you can re-send it with this series, with my r-b, and whoever
>> gets to send the PR can send everything at once.
> 
> OK I'll collect all the patches in one series and submit it early next week. Hopefully we can test over the weekend and address any comments by the freeze.

Yes please.

> 
>> If either Gerd of Phil wants to pick these up I have already acked the pegasos2 changes.
>>
>> If you want me to get this via qemu-ppc bear in mind that I need to send the PR March 7th
>> at the latest.
> 
> I'm aware of that, although if we get more CI outage which stops pulls maybe we should consider 1-2 week extension, but I'm planning accordingly and if there won't be unexpected delays (e.g. new late reviews asking for extensive changes) this should be ready a week before the freeze. I think we can do one or two more versions/

It's no possible to predict this kind of unexpected delay, yes. But in the end the
maintainer is here to mediate these situations. If the patches have acks and the
maintainer considered it worth pushing, then this late review must be so good that
it convinces the maintainer to drop the patches from the PR (and from the release,
in this case).

It can happen, sure, but it's not a given. Specially considering that we have a good
code freeze window to fix any bugs that we might have left behind during the development
cycle.


Thanks,


Daniel

> 
> Regards,
> BALATON Zoltan
diff mbox series

Patch

diff --git a/hw/audio/trace-events b/hw/audio/trace-events
index e0e71cd9b1..6eccdaa4b5 100644
--- a/hw/audio/trace-events
+++ b/hw/audio/trace-events
@@ -11,3 +11,9 @@  hda_audio_running(const char *stream, int nr, bool running) "st %s, nr %d, run %
 hda_audio_format(const char *stream, int chan, const char *fmt, int freq) "st %s, %d x %s @ %d Hz"
 hda_audio_adjust(const char *stream, int pos) "st %s, pos %d"
 hda_audio_overrun(const char *stream) "st %s"
+
+#via-ac97.c
+via_ac97_codec_write(uint8_t addr, uint16_t val) "0x%x <- 0x%x"
+via_ac97_sgd_fetch(uint32_t addr, char stop, char eol, char flag, uint32_t len) "addr=0x%x %c%c%c len=%d"
+via_ac97_sgd_read(uint64_t addr, unsigned size, uint64_t val) "0x%"PRIx64" %d -> 0x%"PRIx64
+via_ac97_sgd_write(uint64_t addr, unsigned size, uint64_t val) "0x%"PRIx64" %d <- 0x%"PRIx64
diff --git a/hw/audio/via-ac97.c b/hw/audio/via-ac97.c
index d1a856f63d..cdac5bc14b 100644
--- a/hw/audio/via-ac97.c
+++ b/hw/audio/via-ac97.c
@@ -1,39 +1,467 @@ 
 /*
  * VIA south bridges sound support
  *
+ * Copyright (c) 2022-2023 BALATON Zoltan
+ *
  * This work is licensed under the GNU GPL license version 2 or later.
  */
 
 /*
- * TODO: This is entirely boiler plate just registering empty PCI devices
- * with the right ID guests expect, functionality should be added here.
+ * TODO: This is only a basic implementation of one audio playback channel
+ *       more functionality should be added here.
  */
 
 #include "qemu/osdep.h"
+#include "qemu/log.h"
 #include "hw/isa/vt82c686.h"
-#include "hw/pci/pci_device.h"
+#include "ac97.h"
+#include "trace.h"
+
+#define CLEN_IS_EOL(x)  ((x)->clen & BIT(31))
+#define CLEN_IS_FLAG(x) ((x)->clen & BIT(30))
+#define CLEN_IS_STOP(x) ((x)->clen & BIT(29))
+#define CLEN_LEN(x)     ((x)->clen & 0xfff)
+
+#define STAT_ACTIVE BIT(7)
+#define STAT_PAUSED BIT(6)
+#define STAT_TRIG   BIT(3)
+#define STAT_STOP   BIT(2)
+#define STAT_EOL    BIT(1)
+#define STAT_FLAG   BIT(0)
+
+#define CNTL_START  BIT(7)
+#define CNTL_TERM   BIT(6)
+#define CNTL_PAUSE  BIT(3)
+
+static void open_voice_out(ViaAC97State *s);
+
+static uint16_t codec_rates[] = { 8000, 11025, 16000, 22050, 32000, 44100,
+                                  48000 };
+
+#define CODEC_REG(s, o)  ((s)->codec_regs[(o) / 2])
+#define CODEC_VOL(vol, mask)  ((255 * ((vol) & mask)) / mask)
+
+static void codec_volume_set_out(ViaAC97State *s)
+{
+    int lvol, rvol, mute;
+
+    lvol = 255 - CODEC_VOL(CODEC_REG(s, AC97_Master_Volume_Mute) >> 8, 0x1f);
+    lvol *= 255 - CODEC_VOL(CODEC_REG(s, AC97_PCM_Out_Volume_Mute) >> 8, 0x1f);
+    lvol /= 255;
+    rvol = 255 - CODEC_VOL(CODEC_REG(s, AC97_Master_Volume_Mute), 0x1f);
+    rvol *= 255 - CODEC_VOL(CODEC_REG(s, AC97_PCM_Out_Volume_Mute), 0x1f);
+    rvol /= 255;
+    mute = CODEC_REG(s, AC97_Master_Volume_Mute) >> MUTE_SHIFT;
+    mute |= CODEC_REG(s, AC97_PCM_Out_Volume_Mute) >> MUTE_SHIFT;
+    AUD_set_volume_out(s->vo, mute, lvol, rvol);
+}
+
+static void codec_reset(ViaAC97State *s)
+{
+    memset(s->codec_regs, 0, sizeof(s->codec_regs));
+    CODEC_REG(s, AC97_Reset) = 0x6a90;
+    CODEC_REG(s, AC97_Master_Volume_Mute) = 0x8000;
+    CODEC_REG(s, AC97_Headphone_Volume_Mute) = 0x8000;
+    CODEC_REG(s, AC97_Master_Volume_Mono_Mute) = 0x8000;
+    CODEC_REG(s, AC97_Phone_Volume_Mute) = 0x8008;
+    CODEC_REG(s, AC97_Mic_Volume_Mute) = 0x8008;
+    CODEC_REG(s, AC97_Line_In_Volume_Mute) = 0x8808;
+    CODEC_REG(s, AC97_CD_Volume_Mute) = 0x8808;
+    CODEC_REG(s, AC97_Video_Volume_Mute) = 0x8808;
+    CODEC_REG(s, AC97_Aux_Volume_Mute) = 0x8808;
+    CODEC_REG(s, AC97_PCM_Out_Volume_Mute) = 0x8808;
+    CODEC_REG(s, AC97_Record_Gain_Mute) = 0x8000;
+    CODEC_REG(s, AC97_Powerdown_Ctrl_Stat) = 0x000f;
+    CODEC_REG(s, AC97_Extended_Audio_ID) = 0x0a05;
+    CODEC_REG(s, AC97_Extended_Audio_Ctrl_Stat) = 0x0400;
+    CODEC_REG(s, AC97_PCM_Front_DAC_Rate) = 48000;
+    CODEC_REG(s, AC97_PCM_LR_ADC_Rate) = 48000;
+    /* Sigmatel 9766 (STAC9766) */
+    CODEC_REG(s, AC97_Vendor_ID1) = 0x8384;
+    CODEC_REG(s, AC97_Vendor_ID2) = 0x7666;
+}
+
+static uint16_t codec_read(ViaAC97State *s, uint8_t addr)
+{
+    return CODEC_REG(s, addr);
+}
+
+static void codec_write(ViaAC97State *s, uint8_t addr, uint16_t val)
+{
+    trace_via_ac97_codec_write(addr, val);
+    switch (addr) {
+    case AC97_Reset:
+        codec_reset(s);
+        return;
+    case AC97_Master_Volume_Mute:
+    case AC97_PCM_Out_Volume_Mute:
+        if (addr == AC97_Master_Volume_Mute) {
+            if (val & BIT(13)) {
+                val |= 0x1f00;
+            }
+            if (val & BIT(5)) {
+                val |= 0x1f;
+            }
+        }
+        CODEC_REG(s, addr) = val & 0x9f1f;
+        codec_volume_set_out(s);
+        return;
+    case AC97_Extended_Audio_Ctrl_Stat:
+        CODEC_REG(s, addr) &= ~EACS_VRA;
+        CODEC_REG(s, addr) |= val & EACS_VRA;
+        if (!(val & EACS_VRA)) {
+            CODEC_REG(s, AC97_PCM_Front_DAC_Rate) = 48000;
+            CODEC_REG(s, AC97_PCM_LR_ADC_Rate) = 48000;
+            open_voice_out(s);
+        }
+        return;
+    case AC97_PCM_Front_DAC_Rate:
+    case AC97_PCM_LR_ADC_Rate:
+        if (CODEC_REG(s, AC97_Extended_Audio_Ctrl_Stat) & EACS_VRA) {
+            int i;
+            uint16_t rate = val;
+
+            for (i = 0; i < ARRAY_SIZE(codec_rates) - 1; i++) {
+                if (rate < codec_rates[i] +
+                    (codec_rates[i + 1] - codec_rates[i]) / 2) {
+                    rate = codec_rates[i];
+                    break;
+                }
+            }
+            if (rate > 48000) {
+                rate = 48000;
+            }
+            CODEC_REG(s, addr) = rate;
+            open_voice_out(s);
+        }
+        return;
+    case AC97_Powerdown_Ctrl_Stat:
+        CODEC_REG(s, addr) = (val & 0xff00) | (CODEC_REG(s, addr) & 0xff);
+        return;
+    case AC97_Extended_Audio_ID:
+    case AC97_Vendor_ID1:
+    case AC97_Vendor_ID2:
+        /* Read only registers */
+        return;
+    default:
+        qemu_log_mask(LOG_UNIMP,
+                      "via-ac97: Unimplemented codec register 0x%x\n", addr);
+        CODEC_REG(s, addr) = val;
+    }
+}
+
+static void fetch_sgd(ViaAC97SGDChannel *c, PCIDevice *d)
+{
+    uint32_t b[2];
+
+    if (c->curr < c->base) {
+        c->curr = c->base;
+    }
+    pci_dma_read(d, c->curr, b, sizeof(b));
+    c->addr = le32_to_cpu(b[0]);
+    c->clen = le32_to_cpu(b[1]);
+    trace_via_ac97_sgd_fetch(c->addr, CLEN_IS_STOP(c) ? 'S' : '-',
+                             CLEN_IS_EOL(c) ? 'E' : '-',
+                             CLEN_IS_FLAG(c) ? 'F' : '-', CLEN_LEN(c));
+}
+
+static void out_cb(void *opaque, int avail)
+{
+    ViaAC97State *s = opaque;
+    ViaAC97SGDChannel *c = &s->aur;
+    int temp, to_copy, copied;
+    bool stop = false;
+    uint8_t tmpbuf[4096];
+
+    if (c->stat & STAT_PAUSED) {
+        return;
+    }
+    c->stat |= STAT_ACTIVE;
+    while (avail && !stop) {
+        if (!c->clen) {
+            fetch_sgd(c, &s->dev);
+        }
+        temp = MIN(CLEN_LEN(c), avail);
+        while (temp) {
+            to_copy = MIN(temp, sizeof(tmpbuf));
+            pci_dma_read(&s->dev, c->addr, tmpbuf, to_copy);
+            copied = AUD_write(s->vo, tmpbuf, to_copy);
+            if (!copied) {
+                stop = true;
+                break;
+            }
+            temp -= copied;
+            avail -= copied;
+            c->addr += copied;
+            c->clen -= copied;
+        }
+        if (CLEN_LEN(c) == 0) {
+            c->curr += 8;
+            if (CLEN_IS_EOL(c)) {
+                c->stat |= STAT_EOL;
+                if (c->type & CNTL_START) {
+                    c->curr = c->base;
+                    c->stat |= STAT_PAUSED;
+                } else {
+                    c->stat &= ~STAT_ACTIVE;
+                    AUD_set_active_out(s->vo, 0);
+                }
+                if (c->type & STAT_EOL) {
+                    via_isa_set_irq(&s->dev, VIA_IRQ_AC97, 1);
+                }
+            }
+            if (CLEN_IS_FLAG(c)) {
+                c->stat |= STAT_FLAG;
+                c->stat |= STAT_PAUSED;
+                if (c->type & STAT_FLAG) {
+                    via_isa_set_irq(&s->dev, VIA_IRQ_AC97, 1);
+                }
+            }
+            if (CLEN_IS_STOP(c)) {
+                c->stat |= STAT_STOP;
+                c->stat |= STAT_PAUSED;
+            }
+            c->clen = 0;
+            stop = true;
+        }
+    }
+}
+
+static void open_voice_out(ViaAC97State *s)
+{
+    struct audsettings as = {
+        .freq = CODEC_REG(s, AC97_PCM_Front_DAC_Rate),
+        .nchannels = s->aur.type & BIT(4) ? 2 : 1,
+        .fmt = s->aur.type & BIT(5) ? AUDIO_FORMAT_S16 : AUDIO_FORMAT_S8,
+        .endianness = 0,
+    };
+    s->vo = AUD_open_out(&s->card, s->vo, "via-ac97.out", s, out_cb, &as);
+}
+
+static uint64_t sgd_read(void *opaque, hwaddr addr, unsigned size)
+{
+    ViaAC97State *s = opaque;
+    uint64_t val = 0;
+
+    switch (addr) {
+    case 0:
+        val = s->aur.stat;
+        if (s->aur.type & CNTL_START) {
+            val |= STAT_TRIG;
+        }
+        break;
+    case 1:
+        val = s->aur.stat & STAT_PAUSED ? BIT(3) : 0;
+        break;
+    case 2:
+        val = s->aur.type;
+        break;
+    case 4:
+        val = s->aur.curr;
+        break;
+    case 0xc:
+        val = CLEN_LEN(&s->aur);
+        break;
+    case 0x80:
+        val = s->ac97_cmd;
+        break;
+    case 0x84:
+        val = s->aur.stat & STAT_FLAG;
+        if (s->aur.stat & STAT_EOL) {
+            val |= BIT(4);
+        }
+        if (s->aur.stat & STAT_STOP) {
+            val |= BIT(8);
+        }
+        if (s->aur.stat & STAT_ACTIVE) {
+            val |= BIT(12);
+        }
+        break;
+    default:
+        qemu_log_mask(LOG_UNIMP, "via-ac97: Unimplemented register read %"
+                      HWADDR_PRIx"\n", addr);
+    }
+    trace_via_ac97_sgd_read(addr, size, val);
+    return val;
+}
+
+static void sgd_write(void *opaque, hwaddr addr, uint64_t val, unsigned size)
+{
+    ViaAC97State *s = opaque;
+
+    trace_via_ac97_sgd_write(addr, size, val);
+    switch (addr) {
+    case 0:
+        if (val & STAT_STOP) {
+            s->aur.stat &= ~STAT_PAUSED;
+        }
+        if (val & STAT_EOL) {
+            s->aur.stat &= ~(STAT_EOL | STAT_PAUSED);
+            if (s->aur.type & STAT_EOL) {
+                via_isa_set_irq(&s->dev, VIA_IRQ_AC97, 0);
+            }
+        }
+        if (val & STAT_FLAG) {
+            s->aur.stat &= ~(STAT_FLAG | STAT_PAUSED);
+            if (s->aur.type & STAT_FLAG) {
+                via_isa_set_irq(&s->dev, VIA_IRQ_AC97, 0);
+            }
+        }
+        break;
+    case 1:
+        if (val & CNTL_START) {
+            AUD_set_active_out(s->vo, 1);
+            s->aur.stat = STAT_ACTIVE;
+        }
+        if (val & CNTL_TERM) {
+            AUD_set_active_out(s->vo, 0);
+            s->aur.stat &= ~(STAT_ACTIVE | STAT_PAUSED);
+            s->aur.clen = 0;
+        }
+        if (val & CNTL_PAUSE) {
+            AUD_set_active_out(s->vo, 0);
+            s->aur.stat &= ~STAT_ACTIVE;
+            s->aur.stat |= STAT_PAUSED;
+        } else if (!(val & CNTL_PAUSE) && (s->aur.stat & STAT_PAUSED)) {
+            AUD_set_active_out(s->vo, 1);
+            s->aur.stat |= STAT_ACTIVE;
+            s->aur.stat &= ~STAT_PAUSED;
+        }
+        break;
+    case 2:
+    {
+        uint32_t oldval = s->aur.type;
+        s->aur.type = val;
+        if ((oldval & 0x30) != (val & 0x30)) {
+            open_voice_out(s);
+        }
+        break;
+    }
+    case 4:
+        s->aur.base = val & ~1ULL;
+        break;
+    case 0x80:
+        if (val >> 30) {
+            /* we only have primary codec */
+            break;
+        }
+        if (val & BIT(23)) { /* read reg */
+            s->ac97_cmd = val & 0xc0ff0000ULL;
+            s->ac97_cmd |= codec_read(s, (val >> 16) & 0x7f);
+            s->ac97_cmd |= BIT(25); /* data valid */
+        } else {
+            s->ac97_cmd = val & 0xc0ffffffULL;
+            codec_write(s, (val >> 16) & 0x7f, val);
+        }
+        break;
+    case 0xc:
+    case 0x84:
+        /* Read only */
+        break;
+    default:
+        qemu_log_mask(LOG_UNIMP, "via-ac97: Unimplemented register write %"
+                      HWADDR_PRIx"\n", addr);
+    }
+}
+
+static const MemoryRegionOps sgd_ops = {
+    .read = sgd_read,
+    .write = sgd_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static uint64_t fm_read(void *opaque, hwaddr addr, unsigned size)
+{
+    qemu_log_mask(LOG_UNIMP, "%s: 0x%"HWADDR_PRIx" %d\n", __func__, addr, size);
+    return 0;
+}
+
+static void fm_write(void *opaque, hwaddr addr, uint64_t val, unsigned size)
+{
+    qemu_log_mask(LOG_UNIMP, "%s: 0x%"HWADDR_PRIx" %d <= 0x%"PRIX64"\n",
+                  __func__, addr, size, val);
+}
+
+static const MemoryRegionOps fm_ops = {
+    .read = fm_read,
+    .write = fm_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static uint64_t midi_read(void *opaque, hwaddr addr, unsigned size)
+{
+    qemu_log_mask(LOG_UNIMP, "%s: 0x%"HWADDR_PRIx" %d\n", __func__, addr, size);
+    return 0;
+}
+
+static void midi_write(void *opaque, hwaddr addr, uint64_t val, unsigned size)
+{
+    qemu_log_mask(LOG_UNIMP, "%s: 0x%"HWADDR_PRIx" %d <= 0x%"PRIX64"\n",
+                  __func__, addr, size, val);
+}
+
+static const MemoryRegionOps midi_ops = {
+    .read = midi_read,
+    .write = midi_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void via_ac97_reset(DeviceState *dev)
+{
+    ViaAC97State *s = VIA_AC97(dev);
+
+    codec_reset(s);
+}
 
 static void via_ac97_realize(PCIDevice *pci_dev, Error **errp)
 {
+    ViaAC97State *s = VIA_AC97(pci_dev);
+    Object *o = OBJECT(s);
+
     pci_set_word(pci_dev->config + PCI_COMMAND,
                  PCI_COMMAND_INVALIDATE | PCI_COMMAND_PARITY);
     pci_set_word(pci_dev->config + PCI_STATUS,
                  PCI_STATUS_CAP_LIST | PCI_STATUS_DEVSEL_MEDIUM);
     pci_set_long(pci_dev->config + PCI_INTERRUPT_PIN, 0x03);
+
+    memory_region_init_io(&s->sgd, o, &sgd_ops, s, "via-ac97.sgd", 256);
+    pci_register_bar(pci_dev, 0, PCI_BASE_ADDRESS_SPACE_IO, &s->sgd);
+    memory_region_init_io(&s->fm, o, &fm_ops, s, "via-ac97.fm", 4);
+    pci_register_bar(pci_dev, 1, PCI_BASE_ADDRESS_SPACE_IO, &s->fm);
+    memory_region_init_io(&s->midi, o, &midi_ops, s, "via-ac97.midi", 4);
+    pci_register_bar(pci_dev, 2, PCI_BASE_ADDRESS_SPACE_IO, &s->midi);
+
+    AUD_register_card ("via-ac97", &s->card);
 }
 
+static void via_ac97_exit(PCIDevice *dev)
+{
+    ViaAC97State *s = VIA_AC97(dev);
+
+    AUD_close_out(&s->card, s->vo);
+    AUD_remove_card(&s->card);
+}
+
+static Property via_ac97_properties[] = {
+    DEFINE_AUDIO_PROPERTIES(ViaAC97State, card),
+    DEFINE_PROP_END_OF_LIST(),
+};
+
 static void via_ac97_class_init(ObjectClass *klass, void *data)
 {
     DeviceClass *dc = DEVICE_CLASS(klass);
     PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
 
     k->realize = via_ac97_realize;
+    k->exit = via_ac97_exit;
     k->vendor_id = PCI_VENDOR_ID_VIA;
     k->device_id = PCI_DEVICE_ID_VIA_AC97;
     k->revision = 0x50;
     k->class_id = PCI_CLASS_MULTIMEDIA_AUDIO;
+    device_class_set_props(dc, via_ac97_properties);
     set_bit(DEVICE_CATEGORY_SOUND, dc->categories);
     dc->desc = "VIA AC97";
+    dc->reset = via_ac97_reset;
     /* Reason: Part of a south bridge chip */
     dc->user_creatable = false;
 }
@@ -41,7 +469,7 @@  static void via_ac97_class_init(ObjectClass *klass, void *data)
 static const TypeInfo via_ac97_info = {
     .name          = TYPE_VIA_AC97,
     .parent        = TYPE_PCI_DEVICE,
-    .instance_size = sizeof(PCIDevice),
+    .instance_size = sizeof(ViaAC97State),
     .class_init    = via_ac97_class_init,
     .interfaces = (InterfaceInfo[]) {
         { INTERFACE_CONVENTIONAL_PCI_DEVICE },
diff --git a/hw/isa/vt82c686.c b/hw/isa/vt82c686.c
index f31b2fa7ca..b16620daf8 100644
--- a/hw/isa/vt82c686.c
+++ b/hw/isa/vt82c686.c
@@ -555,7 +555,7 @@  struct ViaISAState {
     PCIIDEState ide;
     UHCIState uhci[2];
     ViaPMState pm;
-    PCIDevice ac97;
+    ViaAC97State ac97;
     PCIDevice mc97;
 };
 
@@ -610,6 +610,7 @@  void via_isa_set_irq(PCIDevice *d, ViaISAIRQSourceBit n, int level)
         break;
     case VIA_IRQ_USB0:
     case VIA_IRQ_USB1:
+    case VIA_IRQ_AC97:
         max_irq = 14;
         isa_irq = d->config[PCI_INTERRUPT_LINE];
         break;
diff --git a/include/hw/isa/vt82c686.h b/include/hw/isa/vt82c686.h
index e982c5fe26..f7258d01df 100644
--- a/include/hw/isa/vt82c686.h
+++ b/include/hw/isa/vt82c686.h
@@ -1,6 +1,8 @@ 
 #ifndef HW_VT82C686_H
 #define HW_VT82C686_H
 
+#include "hw/pci/pci_device.h"
+#include "audio/audio.h"
 
 #define TYPE_VT82C686B_ISA "vt82c686b-isa"
 #define TYPE_VT82C686B_USB_UHCI "vt82c686b-usb-uhci"
@@ -9,6 +11,29 @@ 
 #define TYPE_VIA_IDE "via-ide"
 #define TYPE_VIA_MC97 "via-mc97"
 
+typedef struct {
+    uint8_t stat;
+    uint8_t type;
+    uint32_t base;
+    uint32_t curr;
+    uint32_t addr;
+    uint32_t clen;
+} ViaAC97SGDChannel;
+
+OBJECT_DECLARE_SIMPLE_TYPE(ViaAC97State, VIA_AC97);
+
+struct ViaAC97State {
+    PCIDevice dev;
+    QEMUSoundCard card;
+    MemoryRegion sgd;
+    MemoryRegion fm;
+    MemoryRegion midi;
+    SWVoiceOut *vo;
+    ViaAC97SGDChannel aur;
+    uint16_t codec_regs[128];
+    uint32_t ac97_cmd;
+};
+
 typedef enum {
     VIA_IRQ_IDE0 = 0,
     VIA_IRQ_IDE1 = 1,
@@ -18,6 +43,7 @@  typedef enum {
     VIA_IRQ_PIRQB = 5,
     VIA_IRQ_PIRQC = 6,
     VIA_IRQ_PIRQD = 7,
+    VIA_IRQ_AC97 = 8,
 } ViaISAIRQSourceBit;
 
 void via_isa_set_irq(PCIDevice *d, ViaISAIRQSourceBit n, int level);