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