diff options
-rw-r--r-- | Makefile.target | 2 | ||||
-rw-r--r-- | hw/marvell_88w8618_audio.c | 274 |
2 files changed, 275 insertions, 1 deletions
diff --git a/Makefile.target b/Makefile.target index 1fee0d9afd..595c88bdd7 100644 --- a/Makefile.target +++ b/Makefile.target @@ -272,7 +272,7 @@ obj-arm-y += omap2.o omap_dss.o soc_dma.o obj-arm-y += omap_sx1.o palm.o tsc210x.o obj-arm-y += nseries.o blizzard.o onenand.o vga.o cbus.o tusb6010.o usb-musb.o obj-arm-y += mst_fpga.o mainstone.o -obj-arm-y += musicpal.o pflash_cfi02.o bitbang_i2c.o +obj-arm-y += musicpal.o pflash_cfi02.o bitbang_i2c.o marvell_88w8618_audio.o obj-arm-y += framebuffer.o obj-arm-y += syborg.o syborg_fb.o syborg_interrupt.o syborg_keyboard.o obj-arm-y += syborg_serial.o syborg_timer.o syborg_pointer.o syborg_rtc.o diff --git a/hw/marvell_88w8618_audio.c b/hw/marvell_88w8618_audio.c new file mode 100644 index 0000000000..5c6d3a595d --- /dev/null +++ b/hw/marvell_88w8618_audio.c @@ -0,0 +1,274 @@ +/* + * Marvell 88w8618 audio emulation extracted from + * Marvell MV88w8618 / Freecom MusicPal emulation. + * + * Copyright (c) 2008 Jan Kiszka + * + * This code is licenced under the GNU GPL v2. + */ +#include "sysbus.h" +#include "hw.h" +#include "i2c.h" +#include "sysbus.h" +#include "audio/audio.h" + +#define MP_AUDIO_SIZE 0x00001000 + +/* Audio register offsets */ +#define MP_AUDIO_PLAYBACK_MODE 0x00 +#define MP_AUDIO_CLOCK_DIV 0x18 +#define MP_AUDIO_IRQ_STATUS 0x20 +#define MP_AUDIO_IRQ_ENABLE 0x24 +#define MP_AUDIO_TX_START_LO 0x28 +#define MP_AUDIO_TX_THRESHOLD 0x2C +#define MP_AUDIO_TX_STATUS 0x38 +#define MP_AUDIO_TX_START_HI 0x40 + +/* Status register and IRQ enable bits */ +#define MP_AUDIO_TX_HALF (1 << 6) +#define MP_AUDIO_TX_FULL (1 << 7) + +/* Playback mode bits */ +#define MP_AUDIO_16BIT_SAMPLE (1 << 0) +#define MP_AUDIO_PLAYBACK_EN (1 << 7) +#define MP_AUDIO_CLOCK_24MHZ (1 << 9) +#define MP_AUDIO_MONO (1 << 14) + +#ifdef HAS_AUDIO +typedef struct mv88w8618_audio_state { + SysBusDevice busdev; + qemu_irq irq; + uint32_t playback_mode; + uint32_t status; + uint32_t irq_enable; + unsigned long phys_buf; + uint32_t target_buffer; + unsigned int threshold; + unsigned int play_pos; + unsigned int last_free; + uint32_t clock_div; + DeviceState *wm; +} mv88w8618_audio_state; + +static void mv88w8618_audio_callback(void *opaque, int free_out, int free_in) +{ + mv88w8618_audio_state *s = opaque; + int16_t *codec_buffer; + int8_t buf[4096]; + int8_t *mem_buffer; + int pos, block_size; + + if (!(s->playback_mode & MP_AUDIO_PLAYBACK_EN)) + return; + + if (s->playback_mode & MP_AUDIO_16BIT_SAMPLE) + free_out <<= 1; + + if (!(s->playback_mode & MP_AUDIO_MONO)) + free_out <<= 1; + + block_size = s->threshold / 2; + if (free_out - s->last_free < block_size) + return; + + if (block_size > 4096) + return; + + cpu_physical_memory_read(s->target_buffer + s->play_pos, (void *)buf, + block_size); + mem_buffer = buf; + if (s->playback_mode & MP_AUDIO_16BIT_SAMPLE) { + if (s->playback_mode & MP_AUDIO_MONO) { + codec_buffer = wm8750_dac_buffer(s->wm, block_size >> 1); + for (pos = 0; pos < block_size; pos += 2) { + *codec_buffer++ = *(int16_t *)mem_buffer; + *codec_buffer++ = *(int16_t *)mem_buffer; + mem_buffer += 2; + } + } else + memcpy(wm8750_dac_buffer(s->wm, block_size >> 2), + (uint32_t *)mem_buffer, block_size); + } else { + if (s->playback_mode & MP_AUDIO_MONO) { + codec_buffer = wm8750_dac_buffer(s->wm, block_size); + for (pos = 0; pos < block_size; pos++) { + *codec_buffer++ = cpu_to_le16(256 * *mem_buffer); + *codec_buffer++ = cpu_to_le16(256 * *mem_buffer++); + } + } else { + codec_buffer = wm8750_dac_buffer(s->wm, block_size >> 1); + for (pos = 0; pos < block_size; pos += 2) { + *codec_buffer++ = cpu_to_le16(256 * *mem_buffer++); + *codec_buffer++ = cpu_to_le16(256 * *mem_buffer++); + } + } + } + wm8750_dac_commit(s->wm); + + s->last_free = free_out - block_size; + + if (s->play_pos == 0) { + s->status |= MP_AUDIO_TX_HALF; + s->play_pos = block_size; + } else { + s->status |= MP_AUDIO_TX_FULL; + s->play_pos = 0; + } + + if (s->status & s->irq_enable) + qemu_irq_raise(s->irq); +} + +static void mv88w8618_audio_clock_update(mv88w8618_audio_state *s) +{ + int rate; + + if (s->playback_mode & MP_AUDIO_CLOCK_24MHZ) + rate = 24576000 / 64; /* 24.576MHz */ + else + rate = 11289600 / 64; /* 11.2896MHz */ + + rate /= ((s->clock_div >> 8) & 0xff) + 1; + + wm8750_set_bclk_in(s->wm, rate); +} + +static uint32_t mv88w8618_audio_read(void *opaque, target_phys_addr_t offset) +{ + mv88w8618_audio_state *s = opaque; + + switch (offset) { + case MP_AUDIO_PLAYBACK_MODE: + return s->playback_mode; + + case MP_AUDIO_CLOCK_DIV: + return s->clock_div; + + case MP_AUDIO_IRQ_STATUS: + return s->status; + + case MP_AUDIO_IRQ_ENABLE: + return s->irq_enable; + + case MP_AUDIO_TX_STATUS: + return s->play_pos >> 2; + + default: + return 0; + } +} + +static void mv88w8618_audio_write(void *opaque, target_phys_addr_t offset, + uint32_t value) +{ + mv88w8618_audio_state *s = opaque; + + switch (offset) { + case MP_AUDIO_PLAYBACK_MODE: + if (value & MP_AUDIO_PLAYBACK_EN && + !(s->playback_mode & MP_AUDIO_PLAYBACK_EN)) { + s->status = 0; + s->last_free = 0; + s->play_pos = 0; + } + s->playback_mode = value; + mv88w8618_audio_clock_update(s); + break; + + case MP_AUDIO_CLOCK_DIV: + s->clock_div = value; + s->last_free = 0; + s->play_pos = 0; + mv88w8618_audio_clock_update(s); + break; + + case MP_AUDIO_IRQ_STATUS: + s->status &= ~value; + break; + + case MP_AUDIO_IRQ_ENABLE: + s->irq_enable = value; + if (s->status & s->irq_enable) + qemu_irq_raise(s->irq); + break; + + case MP_AUDIO_TX_START_LO: + s->phys_buf = (s->phys_buf & 0xFFFF0000) | (value & 0xFFFF); + s->target_buffer = s->phys_buf; + s->play_pos = 0; + s->last_free = 0; + break; + + case MP_AUDIO_TX_THRESHOLD: + s->threshold = (value + 1) * 4; + break; + + case MP_AUDIO_TX_START_HI: + s->phys_buf = (s->phys_buf & 0xFFFF) | (value << 16); + s->target_buffer = s->phys_buf; + s->play_pos = 0; + s->last_free = 0; + break; + } +} + +static void mv88w8618_audio_reset(void *opaque) +{ + mv88w8618_audio_state *s = opaque; + + s->playback_mode = 0; + s->status = 0; + s->irq_enable = 0; +} + +static CPUReadMemoryFunc *mv88w8618_audio_readfn[] = { + mv88w8618_audio_read, + mv88w8618_audio_read, + mv88w8618_audio_read +}; + +static CPUWriteMemoryFunc *mv88w8618_audio_writefn[] = { + mv88w8618_audio_write, + mv88w8618_audio_write, + mv88w8618_audio_write +}; + +static void mv88w8618_audio_init(SysBusDevice *dev) +{ + mv88w8618_audio_state *s = FROM_SYSBUS(mv88w8618_audio_state, dev); + int iomemtype; + + sysbus_init_irq(dev, &s->irq); + + wm8750_data_req_set(s->wm, mv88w8618_audio_callback, s); + + iomemtype = cpu_register_io_memory(mv88w8618_audio_readfn, + mv88w8618_audio_writefn, s); + sysbus_init_mmio(dev, MP_AUDIO_SIZE, iomemtype); + + qemu_register_reset(mv88w8618_audio_reset, s); +} + +static SysBusDeviceInfo mv88w8618_audio_info = { + .init = mv88w8618_audio_init, + .qdev.name = "mv88w8618_audio", + .qdev.size = sizeof(mv88w8618_audio_state), + .qdev.props = (Property[]) { + { + .name = "wm8750", + .info = &qdev_prop_ptr, + .offset = offsetof(mv88w8618_audio_state, wm), + }, + {/* end of list */} + } +}; +#endif + +static void mv88w8618_register_devices(void) +{ +#ifdef HAS_AUDIO + sysbus_register_withprop(&mv88w8618_audio_info); +#endif +} + +device_init(mv88w8618_register_devices) |