qemu/hw/milkymist-ac97.c
<<
>>
Prefs
   1/*
   2 *  QEMU model of the Milkymist System Controller.
   3 *
   4 *  Copyright (c) 2010 Michael Walle <michael@walle.cc>
   5 *
   6 * This library is free software; you can redistribute it and/or
   7 * modify it under the terms of the GNU Lesser General Public
   8 * License as published by the Free Software Foundation; either
   9 * version 2 of the License, or (at your option) any later version.
  10 *
  11 * This library is distributed in the hope that it will be useful,
  12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14 * Lesser General Public License for more details.
  15 *
  16 * You should have received a copy of the GNU Lesser General Public
  17 * License along with this library; if not, see <http://www.gnu.org/licenses/>.
  18 *
  19 *
  20 * Specification available at:
  21 *   http://www.milkymist.org/socdoc/ac97.pdf
  22 */
  23
  24#include "hw.h"
  25#include "sysbus.h"
  26#include "trace.h"
  27#include "audio/audio.h"
  28#include "qemu-error.h"
  29
  30enum {
  31    R_AC97_CTRL = 0,
  32    R_AC97_ADDR,
  33    R_AC97_DATAOUT,
  34    R_AC97_DATAIN,
  35    R_D_CTRL,
  36    R_D_ADDR,
  37    R_D_REMAINING,
  38    R_RESERVED,
  39    R_U_CTRL,
  40    R_U_ADDR,
  41    R_U_REMAINING,
  42    R_MAX
  43};
  44
  45enum {
  46    AC97_CTRL_RQEN  = (1<<0),
  47    AC97_CTRL_WRITE = (1<<1),
  48};
  49
  50enum {
  51    CTRL_EN = (1<<0),
  52};
  53
  54struct MilkymistAC97State {
  55    SysBusDevice busdev;
  56    MemoryRegion regs_region;
  57
  58    QEMUSoundCard card;
  59    SWVoiceIn *voice_in;
  60    SWVoiceOut *voice_out;
  61
  62    uint32_t regs[R_MAX];
  63
  64    qemu_irq crrequest_irq;
  65    qemu_irq crreply_irq;
  66    qemu_irq dmar_irq;
  67    qemu_irq dmaw_irq;
  68};
  69typedef struct MilkymistAC97State MilkymistAC97State;
  70
  71static void update_voices(MilkymistAC97State *s)
  72{
  73    if (s->regs[R_D_CTRL] & CTRL_EN) {
  74        AUD_set_active_out(s->voice_out, 1);
  75    } else {
  76        AUD_set_active_out(s->voice_out, 0);
  77    }
  78
  79    if (s->regs[R_U_CTRL] & CTRL_EN) {
  80        AUD_set_active_in(s->voice_in, 1);
  81    } else {
  82        AUD_set_active_in(s->voice_in, 0);
  83    }
  84}
  85
  86static uint64_t ac97_read(void *opaque, hwaddr addr,
  87                          unsigned size)
  88{
  89    MilkymistAC97State *s = opaque;
  90    uint32_t r = 0;
  91
  92    addr >>= 2;
  93    switch (addr) {
  94    case R_AC97_CTRL:
  95    case R_AC97_ADDR:
  96    case R_AC97_DATAOUT:
  97    case R_AC97_DATAIN:
  98    case R_D_CTRL:
  99    case R_D_ADDR:
 100    case R_D_REMAINING:
 101    case R_U_CTRL:
 102    case R_U_ADDR:
 103    case R_U_REMAINING:
 104        r = s->regs[addr];
 105        break;
 106
 107    default:
 108        error_report("milkymist_ac97: read access to unknown register 0x"
 109                TARGET_FMT_plx, addr << 2);
 110        break;
 111    }
 112
 113    trace_milkymist_ac97_memory_read(addr << 2, r);
 114
 115    return r;
 116}
 117
 118static void ac97_write(void *opaque, hwaddr addr, uint64_t value,
 119                       unsigned size)
 120{
 121    MilkymistAC97State *s = opaque;
 122
 123    trace_milkymist_ac97_memory_write(addr, value);
 124
 125    addr >>= 2;
 126    switch (addr) {
 127    case R_AC97_CTRL:
 128        /* always raise an IRQ according to the direction */
 129        if (value & AC97_CTRL_RQEN) {
 130            if (value & AC97_CTRL_WRITE) {
 131                trace_milkymist_ac97_pulse_irq_crrequest();
 132                qemu_irq_pulse(s->crrequest_irq);
 133            } else {
 134                trace_milkymist_ac97_pulse_irq_crreply();
 135                qemu_irq_pulse(s->crreply_irq);
 136            }
 137        }
 138
 139        /* RQEN is self clearing */
 140        s->regs[addr] = value & ~AC97_CTRL_RQEN;
 141        break;
 142    case R_D_CTRL:
 143    case R_U_CTRL:
 144        s->regs[addr] = value;
 145        update_voices(s);
 146        break;
 147    case R_AC97_ADDR:
 148    case R_AC97_DATAOUT:
 149    case R_AC97_DATAIN:
 150    case R_D_ADDR:
 151    case R_D_REMAINING:
 152    case R_U_ADDR:
 153    case R_U_REMAINING:
 154        s->regs[addr] = value;
 155        break;
 156
 157    default:
 158        error_report("milkymist_ac97: write access to unknown register 0x"
 159                TARGET_FMT_plx, addr);
 160        break;
 161    }
 162
 163}
 164
 165static const MemoryRegionOps ac97_mmio_ops = {
 166    .read = ac97_read,
 167    .write = ac97_write,
 168    .valid = {
 169        .min_access_size = 4,
 170        .max_access_size = 4,
 171    },
 172    .endianness = DEVICE_NATIVE_ENDIAN,
 173};
 174
 175static void ac97_in_cb(void *opaque, int avail_b)
 176{
 177    MilkymistAC97State *s = opaque;
 178    uint8_t buf[4096];
 179    uint32_t remaining = s->regs[R_U_REMAINING];
 180    int temp = audio_MIN(remaining, avail_b);
 181    uint32_t addr = s->regs[R_U_ADDR];
 182    int transferred = 0;
 183
 184    trace_milkymist_ac97_in_cb(avail_b, remaining);
 185
 186    /* prevent from raising an IRQ */
 187    if (temp == 0) {
 188        return;
 189    }
 190
 191    while (temp) {
 192        int acquired, to_copy;
 193
 194        to_copy = audio_MIN(temp, sizeof(buf));
 195        acquired = AUD_read(s->voice_in, buf, to_copy);
 196        if (!acquired) {
 197            break;
 198        }
 199
 200        cpu_physical_memory_write(addr, buf, acquired);
 201
 202        temp -= acquired;
 203        addr += acquired;
 204        transferred += acquired;
 205    }
 206
 207    trace_milkymist_ac97_in_cb_transferred(transferred);
 208
 209    s->regs[R_U_ADDR] = addr;
 210    s->regs[R_U_REMAINING] -= transferred;
 211
 212    if ((s->regs[R_U_CTRL] & CTRL_EN) && (s->regs[R_U_REMAINING] == 0)) {
 213        trace_milkymist_ac97_pulse_irq_dmaw();
 214        qemu_irq_pulse(s->dmaw_irq);
 215    }
 216}
 217
 218static void ac97_out_cb(void *opaque, int free_b)
 219{
 220    MilkymistAC97State *s = opaque;
 221    uint8_t buf[4096];
 222    uint32_t remaining = s->regs[R_D_REMAINING];
 223    int temp = audio_MIN(remaining, free_b);
 224    uint32_t addr = s->regs[R_D_ADDR];
 225    int transferred = 0;
 226
 227    trace_milkymist_ac97_out_cb(free_b, remaining);
 228
 229    /* prevent from raising an IRQ */
 230    if (temp == 0) {
 231        return;
 232    }
 233
 234    while (temp) {
 235        int copied, to_copy;
 236
 237        to_copy = audio_MIN(temp, sizeof(buf));
 238        cpu_physical_memory_read(addr, buf, to_copy);
 239        copied = AUD_write(s->voice_out, buf, to_copy);
 240        if (!copied) {
 241            break;
 242        }
 243        temp -= copied;
 244        addr += copied;
 245        transferred += copied;
 246    }
 247
 248    trace_milkymist_ac97_out_cb_transferred(transferred);
 249
 250    s->regs[R_D_ADDR] = addr;
 251    s->regs[R_D_REMAINING] -= transferred;
 252
 253    if ((s->regs[R_D_CTRL] & CTRL_EN) && (s->regs[R_D_REMAINING] == 0)) {
 254        trace_milkymist_ac97_pulse_irq_dmar();
 255        qemu_irq_pulse(s->dmar_irq);
 256    }
 257}
 258
 259static void milkymist_ac97_reset(DeviceState *d)
 260{
 261    MilkymistAC97State *s = container_of(d, MilkymistAC97State, busdev.qdev);
 262    int i;
 263
 264    for (i = 0; i < R_MAX; i++) {
 265        s->regs[i] = 0;
 266    }
 267
 268    AUD_set_active_in(s->voice_in, 0);
 269    AUD_set_active_out(s->voice_out, 0);
 270}
 271
 272static int ac97_post_load(void *opaque, int version_id)
 273{
 274    MilkymistAC97State *s = opaque;
 275
 276    update_voices(s);
 277
 278    return 0;
 279}
 280
 281static int milkymist_ac97_init(SysBusDevice *dev)
 282{
 283    MilkymistAC97State *s = FROM_SYSBUS(typeof(*s), dev);
 284
 285    struct audsettings as;
 286    sysbus_init_irq(dev, &s->crrequest_irq);
 287    sysbus_init_irq(dev, &s->crreply_irq);
 288    sysbus_init_irq(dev, &s->dmar_irq);
 289    sysbus_init_irq(dev, &s->dmaw_irq);
 290
 291    AUD_register_card("Milkymist AC'97", &s->card);
 292
 293    as.freq = 48000;
 294    as.nchannels = 2;
 295    as.fmt = AUD_FMT_S16;
 296    as.endianness = 1;
 297
 298    s->voice_in = AUD_open_in(&s->card, s->voice_in,
 299            "mm_ac97.in", s, ac97_in_cb, &as);
 300    s->voice_out = AUD_open_out(&s->card, s->voice_out,
 301            "mm_ac97.out", s, ac97_out_cb, &as);
 302
 303    memory_region_init_io(&s->regs_region, &ac97_mmio_ops, s,
 304            "milkymist-ac97", R_MAX * 4);
 305    sysbus_init_mmio(dev, &s->regs_region);
 306
 307    return 0;
 308}
 309
 310static const VMStateDescription vmstate_milkymist_ac97 = {
 311    .name = "milkymist-ac97",
 312    .version_id = 1,
 313    .minimum_version_id = 1,
 314    .minimum_version_id_old = 1,
 315    .post_load = ac97_post_load,
 316    .fields      = (VMStateField[]) {
 317        VMSTATE_UINT32_ARRAY(regs, MilkymistAC97State, R_MAX),
 318        VMSTATE_END_OF_LIST()
 319    }
 320};
 321
 322static void milkymist_ac97_class_init(ObjectClass *klass, void *data)
 323{
 324    DeviceClass *dc = DEVICE_CLASS(klass);
 325    SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
 326
 327    k->init = milkymist_ac97_init;
 328    dc->reset = milkymist_ac97_reset;
 329    dc->vmsd = &vmstate_milkymist_ac97;
 330}
 331
 332static TypeInfo milkymist_ac97_info = {
 333    .name          = "milkymist-ac97",
 334    .parent        = TYPE_SYS_BUS_DEVICE,
 335    .instance_size = sizeof(MilkymistAC97State),
 336    .class_init    = milkymist_ac97_class_init,
 337};
 338
 339static void milkymist_ac97_register_types(void)
 340{
 341    type_register_static(&milkymist_ac97_info);
 342}
 343
 344type_init(milkymist_ac97_register_types)
 345