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