qemu

FORK: QEMU emulator
git clone https://git.neptards.moe/neptards/qemu.git
Log | Files | Refs | Submodules | LICENSE

marvell_88w8618.c (8761B)


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