qemu/hw/audio/marvell_88w8618.c
<<
>>
Prefs
   1/*
   2 * Marvell 88w8618 audio emulation extracted from
   3 * Marvell MV88w8618 / Freecom MusicPal emulation.
   4 *
   5 * Copyright (c) 2008 Jan Kiszka
   6 *
   7 * This code is licensed under the GNU GPL v2.
   8 *
   9 * Contributions after 2012-01-13 are licensed under the terms of the
  10 * GNU GPL, version 2 or (at your option) any later version.
  11 */
  12
  13#include "qemu/osdep.h"
  14#include "hw/sysbus.h"
  15#include "hw/hw.h"
  16#include "hw/audio/wm8750.h"
  17#include "audio/audio.h"
  18#include "qapi/error.h"
  19#include "qemu/module.h"
  20
  21#define MP_AUDIO_SIZE           0x00001000
  22
  23/* Audio register offsets */
  24#define MP_AUDIO_PLAYBACK_MODE  0x00
  25#define MP_AUDIO_CLOCK_DIV      0x18
  26#define MP_AUDIO_IRQ_STATUS     0x20
  27#define MP_AUDIO_IRQ_ENABLE     0x24
  28#define MP_AUDIO_TX_START_LO    0x28
  29#define MP_AUDIO_TX_THRESHOLD   0x2C
  30#define MP_AUDIO_TX_STATUS      0x38
  31#define MP_AUDIO_TX_START_HI    0x40
  32
  33/* Status register and IRQ enable bits */
  34#define MP_AUDIO_TX_HALF        (1 << 6)
  35#define MP_AUDIO_TX_FULL        (1 << 7)
  36
  37/* Playback mode bits */
  38#define MP_AUDIO_16BIT_SAMPLE   (1 << 0)
  39#define MP_AUDIO_PLAYBACK_EN    (1 << 7)
  40#define MP_AUDIO_CLOCK_24MHZ    (1 << 9)
  41#define MP_AUDIO_MONO           (1 << 14)
  42
  43#define MV88W8618_AUDIO(obj) \
  44    OBJECT_CHECK(mv88w8618_audio_state, (obj), TYPE_MV88W8618_AUDIO)
  45
  46typedef struct mv88w8618_audio_state {
  47    SysBusDevice parent_obj;
  48
  49    MemoryRegion iomem;
  50    qemu_irq irq;
  51    uint32_t playback_mode;
  52    uint32_t status;
  53    uint32_t irq_enable;
  54    uint32_t phys_buf;
  55    uint32_t target_buffer;
  56    uint32_t threshold;
  57    uint32_t play_pos;
  58    uint32_t last_free;
  59    uint32_t clock_div;
  60    void *wm;
  61} mv88w8618_audio_state;
  62
  63static void mv88w8618_audio_callback(void *opaque, int free_out, int free_in)
  64{
  65    mv88w8618_audio_state *s = opaque;
  66    int16_t *codec_buffer;
  67    int8_t buf[4096];
  68    int8_t *mem_buffer;
  69    int pos, block_size;
  70
  71    if (!(s->playback_mode & MP_AUDIO_PLAYBACK_EN)) {
  72        return;
  73    }
  74    if (s->playback_mode & MP_AUDIO_16BIT_SAMPLE) {
  75        free_out <<= 1;
  76    }
  77    if (!(s->playback_mode & MP_AUDIO_MONO)) {
  78        free_out <<= 1;
  79    }
  80    block_size = s->threshold / 2;
  81    if (free_out - s->last_free < block_size) {
  82        return;
  83    }
  84    if (block_size > 4096) {
  85        return;
  86    }
  87    cpu_physical_memory_read(s->target_buffer + s->play_pos, buf, block_size);
  88    mem_buffer = buf;
  89    if (s->playback_mode & MP_AUDIO_16BIT_SAMPLE) {
  90        if (s->playback_mode & MP_AUDIO_MONO) {
  91            codec_buffer = wm8750_dac_buffer(s->wm, block_size >> 1);
  92            for (pos = 0; pos < block_size; pos += 2) {
  93                *codec_buffer++ = *(int16_t *)mem_buffer;
  94                *codec_buffer++ = *(int16_t *)mem_buffer;
  95                mem_buffer += 2;
  96            }
  97        } else {
  98            memcpy(wm8750_dac_buffer(s->wm, block_size >> 2),
  99                   (uint32_t *)mem_buffer, block_size);
 100        }
 101    } else {
 102        if (s->playback_mode & MP_AUDIO_MONO) {
 103            codec_buffer = wm8750_dac_buffer(s->wm, block_size);
 104            for (pos = 0; pos < block_size; pos++) {
 105                *codec_buffer++ = cpu_to_le16(256 * *mem_buffer);
 106                *codec_buffer++ = cpu_to_le16(256 * *mem_buffer++);
 107            }
 108        } else {
 109            codec_buffer = wm8750_dac_buffer(s->wm, block_size >> 1);
 110            for (pos = 0; pos < block_size; pos += 2) {
 111                *codec_buffer++ = cpu_to_le16(256 * *mem_buffer++);
 112                *codec_buffer++ = cpu_to_le16(256 * *mem_buffer++);
 113            }
 114        }
 115    }
 116    wm8750_dac_commit(s->wm);
 117
 118    s->last_free = free_out - block_size;
 119
 120    if (s->play_pos == 0) {
 121        s->status |= MP_AUDIO_TX_HALF;
 122        s->play_pos = block_size;
 123    } else {
 124        s->status |= MP_AUDIO_TX_FULL;
 125        s->play_pos = 0;
 126    }
 127
 128    if (s->status & s->irq_enable) {
 129        qemu_irq_raise(s->irq);
 130    }
 131}
 132
 133static void mv88w8618_audio_clock_update(mv88w8618_audio_state *s)
 134{
 135    int rate;
 136
 137    if (s->playback_mode & MP_AUDIO_CLOCK_24MHZ) {
 138        rate = 24576000 / 64; /* 24.576MHz */
 139    } else {
 140        rate = 11289600 / 64; /* 11.2896MHz */
 141    }
 142    rate /= ((s->clock_div >> 8) & 0xff) + 1;
 143
 144    wm8750_set_bclk_in(s->wm, rate);
 145}
 146
 147static uint64_t mv88w8618_audio_read(void *opaque, hwaddr offset,
 148                                    unsigned size)
 149{
 150    mv88w8618_audio_state *s = opaque;
 151
 152    switch (offset) {
 153    case MP_AUDIO_PLAYBACK_MODE:
 154        return s->playback_mode;
 155
 156    case MP_AUDIO_CLOCK_DIV:
 157        return s->clock_div;
 158
 159    case MP_AUDIO_IRQ_STATUS:
 160        return s->status;
 161
 162    case MP_AUDIO_IRQ_ENABLE:
 163        return s->irq_enable;
 164
 165    case MP_AUDIO_TX_STATUS:
 166        return s->play_pos >> 2;
 167
 168    default:
 169        return 0;
 170    }
 171}
 172
 173static void mv88w8618_audio_write(void *opaque, hwaddr offset,
 174                                  uint64_t value, unsigned size)
 175{
 176    mv88w8618_audio_state *s = opaque;
 177
 178    switch (offset) {
 179    case MP_AUDIO_PLAYBACK_MODE:
 180        if (value & MP_AUDIO_PLAYBACK_EN &&
 181            !(s->playback_mode & MP_AUDIO_PLAYBACK_EN)) {
 182            s->status = 0;
 183            s->last_free = 0;
 184            s->play_pos = 0;
 185        }
 186        s->playback_mode = value;
 187        mv88w8618_audio_clock_update(s);
 188        break;
 189
 190    case MP_AUDIO_CLOCK_DIV:
 191        s->clock_div = value;
 192        s->last_free = 0;
 193        s->play_pos = 0;
 194        mv88w8618_audio_clock_update(s);
 195        break;
 196
 197    case MP_AUDIO_IRQ_STATUS:
 198        s->status &= ~value;
 199        break;
 200
 201    case MP_AUDIO_IRQ_ENABLE:
 202        s->irq_enable = value;
 203        if (s->status & s->irq_enable) {
 204            qemu_irq_raise(s->irq);
 205        }
 206        break;
 207
 208    case MP_AUDIO_TX_START_LO:
 209        s->phys_buf = (s->phys_buf & 0xFFFF0000) | (value & 0xFFFF);
 210        s->target_buffer = s->phys_buf;
 211        s->play_pos = 0;
 212        s->last_free = 0;
 213        break;
 214
 215    case MP_AUDIO_TX_THRESHOLD:
 216        s->threshold = (value + 1) * 4;
 217        break;
 218
 219    case MP_AUDIO_TX_START_HI:
 220        s->phys_buf = (s->phys_buf & 0xFFFF) | (value << 16);
 221        s->target_buffer = s->phys_buf;
 222        s->play_pos = 0;
 223        s->last_free = 0;
 224        break;
 225    }
 226}
 227
 228static void mv88w8618_audio_reset(DeviceState *d)
 229{
 230    mv88w8618_audio_state *s = MV88W8618_AUDIO(d);
 231
 232    s->playback_mode = 0;
 233    s->status = 0;
 234    s->irq_enable = 0;
 235    s->clock_div = 0;
 236    s->threshold = 0;
 237    s->phys_buf = 0;
 238}
 239
 240static const MemoryRegionOps mv88w8618_audio_ops = {
 241    .read = mv88w8618_audio_read,
 242    .write = mv88w8618_audio_write,
 243    .endianness = DEVICE_NATIVE_ENDIAN,
 244};
 245
 246static void mv88w8618_audio_init(Object *obj)
 247{
 248    SysBusDevice *dev = SYS_BUS_DEVICE(obj);
 249    mv88w8618_audio_state *s = MV88W8618_AUDIO(dev);
 250
 251    sysbus_init_irq(dev, &s->irq);
 252
 253    memory_region_init_io(&s->iomem, obj, &mv88w8618_audio_ops, s,
 254                          "audio", MP_AUDIO_SIZE);
 255    sysbus_init_mmio(dev, &s->iomem);
 256
 257    object_property_add_link(OBJECT(dev), "wm8750", TYPE_WM8750,
 258                             (Object **) &s->wm,
 259                             qdev_prop_allow_set_link_before_realize,
 260                             0, &error_abort);
 261}
 262
 263static void mv88w8618_audio_realize(DeviceState *dev, Error **errp)
 264{
 265    mv88w8618_audio_state *s = MV88W8618_AUDIO(dev);
 266
 267    wm8750_data_req_set(s->wm, mv88w8618_audio_callback, s);
 268}
 269
 270static const VMStateDescription mv88w8618_audio_vmsd = {
 271    .name = "mv88w8618_audio",
 272    .version_id = 1,
 273    .minimum_version_id = 1,
 274    .fields = (VMStateField[]) {
 275        VMSTATE_UINT32(playback_mode, mv88w8618_audio_state),
 276        VMSTATE_UINT32(status, mv88w8618_audio_state),
 277        VMSTATE_UINT32(irq_enable, mv88w8618_audio_state),
 278        VMSTATE_UINT32(phys_buf, mv88w8618_audio_state),
 279        VMSTATE_UINT32(target_buffer, mv88w8618_audio_state),
 280        VMSTATE_UINT32(threshold, mv88w8618_audio_state),
 281        VMSTATE_UINT32(play_pos, mv88w8618_audio_state),
 282        VMSTATE_UINT32(last_free, mv88w8618_audio_state),
 283        VMSTATE_UINT32(clock_div, mv88w8618_audio_state),
 284        VMSTATE_END_OF_LIST()
 285    }
 286};
 287
 288static void mv88w8618_audio_class_init(ObjectClass *klass, void *data)
 289{
 290    DeviceClass *dc = DEVICE_CLASS(klass);
 291
 292    dc->realize = mv88w8618_audio_realize;
 293    dc->reset = mv88w8618_audio_reset;
 294    dc->vmsd = &mv88w8618_audio_vmsd;
 295    dc->user_creatable = false;
 296}
 297
 298static const TypeInfo mv88w8618_audio_info = {
 299    .name          = TYPE_MV88W8618_AUDIO,
 300    .parent        = TYPE_SYS_BUS_DEVICE,
 301    .instance_size = sizeof(mv88w8618_audio_state),
 302    .instance_init = mv88w8618_audio_init,
 303    .class_init    = mv88w8618_audio_class_init,
 304};
 305
 306static void mv88w8618_register_types(void)
 307{
 308    type_register_static(&mv88w8618_audio_info);
 309}
 310
 311type_init(mv88w8618_register_types)
 312