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