The PL041 driver provides an interface to an ACLink bus. The LM4549 driver emulates a DAC connected on the ACLink bus. Only audio playback is implemented.
Versatile/PB test build: linux-2.6.38.5 buildroot-2010.11 alsa-lib-1.0.22 alsa-utils-1.0.22 mpg123-0.66 Qemu host: Ubuntu 10.04 in Vmware/OS X Playback tested successfully with aplay and mpg123. Signed-off-by: Mathieu Sonet <cont...@elasticsheep.com> --- Makefile.target | 1 + hw/aclink.c | 121 +++++++++++++++ hw/aclink.h | 63 ++++++++ hw/lm4549.c | 368 +++++++++++++++++++++++++++++++++++++++++++++ hw/pl041.c | 436 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ hw/pl041.h | 126 ++++++++++++++++ hw/pl041.hx | 62 ++++++++ hw/versatilepb.c | 6 + 8 files changed, 1183 insertions(+), 0 deletions(-) create mode 100644 hw/aclink.c create mode 100644 hw/aclink.h create mode 100644 hw/lm4549.c create mode 100644 hw/pl041.c create mode 100644 hw/pl041.h create mode 100644 hw/pl041.hx diff --git a/Makefile.target b/Makefile.target index 21f864a..cdd7b40 100644 --- a/Makefile.target +++ b/Makefile.target @@ -354,6 +354,7 @@ obj-arm-y += syborg_virtio.o obj-arm-y += vexpress.o obj-arm-y += strongarm.o obj-arm-y += collie.o +obj-arm-y += pl041.o aclink.o lm4549.o obj-sh4-y = shix.o r2d.o sh7750.o sh7750_regnames.o tc58128.o obj-sh4-y += sh_timer.o sh_serial.o sh_intc.o sh_pci.o sm501.o diff --git a/hw/aclink.c b/hw/aclink.c new file mode 100644 index 0000000..c335f60 --- /dev/null +++ b/hw/aclink.c @@ -0,0 +1,121 @@ +/* + * ACLink Interface + * + * Copyright (c) 2011 + * Written by Mathieu Sonet - www.elasticsheep.com + * + * This code is licenced under the GPL. + * + * ***************************************************************** + * + * This file defines the ACLink bus interface to exchange data + * between an host and a codec. + * + */ + +#include "aclink.h" + +/*** Types ***/ + +struct ACLinkBus { + BusState qbus; + ACLinkControllerInfo *controller_info; + uint32_t bitclk; +}; + +struct BusInfo aclink_bus_info = { + .name = "aclink", + .size = sizeof(ACLinkBus), +}; + +/*** Functions ***/ + +ACLinkBus *aclink_create_bus(DeviceState *parent, const char *name) +{ + BusState *bus; + bus = qbus_create(&aclink_bus_info, parent, name); + return FROM_QBUS(ACLinkBus, bus); +} + +void aclink_set_controller_info(ACLinkBus *bus, ACLinkControllerInfo *info) +{ + bus->controller_info = info; +} + +static int aclink_device_init(DeviceState *dev, DeviceInfo *base_info) +{ + ACLinkDeviceInfo *info = container_of(base_info, ACLinkDeviceInfo, qdev); + ACLinkDevice *s = ACLINK_DEVICE_FROM_QDEV(dev); + ACLinkBus *bus; + + bus = FROM_QBUS(ACLinkBus, qdev_get_parent_bus(dev)); + if (QLIST_FIRST(&bus->qbus.children) != dev + || QLIST_NEXT(dev, sibling) != NULL) { + hw_error("Too many devices on the ACLINK bus"); + } + + s->info = info; + return info->init(s); +} + +void aclink_register_device(ACLinkDeviceInfo *info) +{ + assert(info->qdev.size >= sizeof(ACLinkDevice)); + info->qdev.init = aclink_device_init; + info->qdev.bus_info = &aclink_bus_info; + qdev_register(&info->qdev); +} + +DeviceState *aclink_create_device(ACLinkBus *bus, const char *name) +{ + DeviceState *dev; + dev = qdev_create(&bus->qbus, name); + qdev_init_nofail(dev); + return dev; +} + +static ACLinkDevice *aclink_get_device(ACLinkBus *bus) +{ + DeviceState *dev = QLIST_FIRST(&bus->qbus.children); + if (!dev) { + return NULL; + } + return ACLINK_DEVICE_FROM_QDEV(dev); +} + +void aclink_bitclk_enable(ACLinkDevice *dev, uint32_t on) +{ + ACLinkBus *bus = FROM_QBUS(ACLinkBus, qdev_get_parent_bus(&dev->qdev)); + uint32_t has_changed; + + on = (on > 0) ? 1 : 0; + has_changed = (bus->bitclk != on) ? 1 : 0; + + bus->bitclk = on; + if (has_changed) { + bus->controller_info->bitclk_state_changed(bus->qbus.parent); + } +} + +uint32_t aclink_bitclk_is_enabled(ACLinkBus *bus) +{ + return bus->bitclk; +} + +void aclink_sdataout_slot12(ACLinkBus *bus, uint32_t slot1, uint32_t slot2) +{ + ACLinkDevice *device = aclink_get_device(bus); + device->info->sdataout_slot12(device, slot1, slot2); +} + +void aclink_sdataout_slot34(ACLinkBus *bus, uint32_t slot3, uint32_t slot4) +{ + ACLinkDevice *device = aclink_get_device(bus); + device->info->sdataout_slot34(device, slot3, slot4); +} + +void aclink_sdatain_slot12(ACLinkDevice *dev, uint32_t slot1, uint32_t slot2) +{ + ACLinkBus *bus = FROM_QBUS(ACLinkBus, qdev_get_parent_bus(&dev->qdev)); + bus->controller_info->sdatain_slot12(bus->qbus.parent, slot1, slot2); +} diff --git a/hw/aclink.h b/hw/aclink.h new file mode 100644 index 0000000..d360d4b --- /dev/null +++ b/hw/aclink.h @@ -0,0 +1,63 @@ +/* + * ACLink Interface + * + * Copyright (c) 2011 + * Written by Mathieu Sonet - www.elasticsheep.com + * + * This code is licenced under the GPL. + * + * ***************************************************************** + * + * This file defines the ACLink bus interface to exchange data + * between an host and a codec. + * + */ + +#ifndef ACLINK_H +#define ACLINK_H + +#include "qdev.h" + +typedef struct ACLinkBus ACLinkBus; +typedef struct ACLinkDevice ACLinkDevice; + +/* Controller */ +typedef struct { + void (*sdatain_slot12)(DeviceState *dev, uint32_t slot1, uint32_t slot2); + void (*bitclk_state_changed)(DeviceState *dev); +} ACLinkControllerInfo; + +/* Device */ +typedef struct { + DeviceInfo qdev; + int (*init)(ACLinkDevice *dev); + void (*sdataout_slot12)(ACLinkDevice *dev, uint32_t slot1, uint32_t slot2); + void (*sdataout_slot34)(ACLinkDevice *dev, uint32_t slot3, uint32_t slot4); +} ACLinkDeviceInfo; + +struct ACLinkDevice { + DeviceState qdev; + ACLinkDeviceInfo *info; +}; + +#define ACLINK_DEVICE_FROM_QDEV(dev) DO_UPCAST(ACLinkDevice, qdev, dev) +#define FROM_ACLINK_DEVICE(type, dev) DO_UPCAST(type, aclinkdev, dev) + +ACLinkBus *aclink_create_bus(DeviceState *parent, const char *name); +void aclink_set_controller_info(ACLinkBus *bus, ACLinkControllerInfo *info); + +DeviceState *aclink_create_device(ACLinkBus *bus, const char *name); +void aclink_register_device(ACLinkDeviceInfo *info); + +/* Common interface */ +uint32_t aclink_bitclk_is_enabled(ACLinkBus *bus); + +/* Controller => device interface */ +void aclink_sdataout_slot12(ACLinkBus *bus, uint32_t slot1, uint32_t slot2); +void aclink_sdataout_slot34(ACLinkBus *bus, uint32_t slot3, uint32_t slot4); + +/* Device => controller interface */ +void aclink_sdatain_slot12(ACLinkDevice *bus, uint32_t slot1, uint32_t slot2); +void aclink_bitclk_enable(ACLinkDevice *dev, uint32_t on); + +#endif diff --git a/hw/lm4549.c b/hw/lm4549.c new file mode 100644 index 0000000..050a7a0 --- /dev/null +++ b/hw/lm4549.c @@ -0,0 +1,368 @@ +/* + * LM4549 Audio Codec Interface + * + * Copyright (c) 2011 + * Written by Mathieu Sonet - www.elasticsheep.com + * + * This code is licenced under the GPL. + * + * ***************************************************************** + * + * This driver emulates the LM4549 codec connected on an ACLINK bus. + * + */ + +#include "sysbus.h" +#include "aclink.h" + +#include "audio/audio.h" + +/* #define LM4549_DEBUG 1 */ +/* #define LM4549_DUMP_DAC_INPUT 1 */ + +#ifdef LM4549_DEBUG +#define DPRINTF(fmt, ...) \ +do { printf("lm4549: " fmt , ## __VA_ARGS__); } while (0) +#else +#define DPRINTF(fmt, ...) do {} while (0) +#endif + +#if defined(LM4549_DUMP_DAC_INPUT) +#include <stdio.h> +static FILE *fp_dac_input; +#endif + +/*** Local prototypes ***/ +void lm4549_sdataout_slot12(ACLinkDevice *dev, uint32_t slot1, uint32_t slot2); +void lm4549_sdataout_slot34(ACLinkDevice *dev, uint32_t slot3, uint32_t slot4); + +/*** LM4549 register list ***/ + +enum { + LM4549_Reset = 0x00, + LM4549_Master_Volume = 0x02, + LM4549_Line_Out_Volume = 0x04, + LM4549_Master_Volume_Mono = 0x06, + LM4549_PC_Beep_Volume = 0x0A, + LM4549_Phone_Volume = 0x0C, + LM4549_Mic_Volume = 0x0E, + LM4549_Line_In_Volume = 0x10, + LM4549_CD_Volume = 0x12, + LM4549_Video_Volume = 0x14, + LM4549_Aux_Volume = 0x16, + LM4549_PCM_Out_Volume = 0x18, + LM4549_Record_Select = 0x1A, + LM4549_Record_Gain = 0x1C, + LM4549_General_Purpose = 0x20, + LM4549_3D_Control = 0x22, + LM4549_Powerdown_Ctrl_Stat = 0x26, + LM4549_Extended_Audio_ID = 0x28, + LM4549_Extended_Audio_Stat_Ctrl = 0x2A, + LM4549_PCM_Front_DAC_Rate = 0x2C, + LM4549_PCM_ADC_Rate = 0x32, + LM4549_Vendor_ID1 = 0x7C, + LM4549_Vendor_ID2 = 0x7E +}; + +/*** LM4549 device state ***/ +typedef struct { + struct { + uint16_t value; + uint16_t default_value; + uint16_t read_only; + } data[128]; +} lm4549_registers; + +typedef struct { + ACLinkDevice aclinkdev; + lm4549_registers codec_regs; + QEMUSoundCard card; + SWVoiceOut *voice; + +#define BUFFER_SIZE (512) + uint32_t buffer[BUFFER_SIZE]; + uint32_t buffer_level; +} lm4549_state; + +/*** Functions ***/ + +static void lm4549_store_init(lm4549_state *s, uint32_t offset, uint32_t value, + uint32_t is_read_only) +{ + lm4549_registers *r = &s->codec_regs; + + if (offset > 128) { + DPRINTF("lm4549_store: Out of bound offset 0x%x\n", (int)offset); + } + + r->data[offset].value = value & 0xFFFF; + r->data[offset].default_value = value & 0xFFFF; + r->data[offset].read_only = (is_read_only > 0) ? 1 : 0; +} + +static void lm4549_store_reset(lm4549_state *s) +{ + lm4549_registers *r = &s->codec_regs; + int i; + + for (i = 0; i < 128; i++) { + r->data[i].value = r->data[i].default_value; + } +} + +static void lm4549_store(lm4549_state *s, uint32_t offset, uint16_t value) +{ + lm4549_registers *r = &s->codec_regs; + + if (offset > 128) { + DPRINTF("lm4549_store: Out of bound offset 0x%x\n", (int)offset); + } + + if (r->data[offset].read_only) { + DPRINTF("lm4549_store: Read-only offset 0x%x\n", (int)offset); + return; + } + + r->data[offset].value = value & 0xFFFF; +} + +static uint16_t lm4549_load(lm4549_state *s, uint32_t offset) +{ + lm4549_registers *r = &s->codec_regs; + + if (offset > 128) { + hw_error("lm4549_load: Out of bound offset 0x%x\n", (int)offset); + } + + return r->data[offset].value; +} + +static void lm4549_audio_transfer(lm4549_state *s) +{ + uint32_t written_bytes, written_samples; + uint32_t i; + + /* Activate the voice */ + AUD_set_active_out(s->voice, 1); + + /* Try to write the buffer content */ + written_bytes = AUD_write(s->voice, s->buffer, + s->buffer_level * sizeof(uint32_t)); + written_samples = written_bytes >> 2; + +#if defined(LM4549_DUMP_DAC_INPUT) + fwrite(s->buffer, sizeof(uint8_t), written_bytes, fp_dac_input); +#endif + + if (written_samples == s->buffer_level) { + s->buffer_level = 0; + } else { + s->buffer_level -= written_samples; + + if (s->buffer_level > 0) { + /* Move the data back to the start of the buffer */ + for (i = 0; i < s->buffer_level; i++) { + s->buffer[i] = s->buffer[i + written_samples]; + } + } + + /* Regulate the data throughput by disabling further transfer + from the ACLink controller */ + aclink_bitclk_enable(&s->aclinkdev, 0); + } +} + +static void lm4549_audio_out_callback(void *opaque, int free) +{ + lm4549_state *s = (lm4549_state *)opaque; + static uint32_t prev_buffer_level; + +#ifdef LM4549_DEBUG + int size = AUD_get_buffer_size_out(s->voice); + DPRINTF("lm4549_audio_out_callback size = %i free = %i\n", size, free); +#endif + + /* Detect that no more date are coming from the ACLink + => disable the voice */ + if (s->buffer_level == prev_buffer_level) { + AUD_set_active_out(s->voice, 0); + } + prev_buffer_level = s->buffer_level; + + /* Check if a buffer transfer is pending */ + if (s->buffer_level == BUFFER_SIZE) { + lm4549_audio_transfer(s); + } + + /* Enable the bitclk to get data again */ + aclink_bitclk_enable(&s->aclinkdev, 1); +} + +static uint32_t lm4549_read(ACLinkDevice *dev, target_phys_addr_t offset) +{ + lm4549_state *s = FROM_ACLINK_DEVICE(lm4549_state, dev); + uint32_t value = 0; + + /* Read the stored value */ + value = lm4549_load(s, offset); + DPRINTF("lm4549_read [0x%02x] = 0x%04x\n", offset, value); + + return value; +} + +static void lm4549_write(ACLinkDevice *dev, + target_phys_addr_t offset, uint32_t value) +{ + lm4549_state *s = FROM_ACLINK_DEVICE(lm4549_state, dev); + + DPRINTF("lm4549_write [0x%02x] = 0x%04x\n", offset, value); + + /* Store the new value */ + lm4549_store(s, offset, value); + + switch (offset) { + case LM4549_Reset: + lm4549_store_reset(s); + break; + case LM4549_PCM_Front_DAC_Rate: + DPRINTF("DAC rate change = %i\n", lm4549_load(s, offset)); + + /* Re-open a voice with the new sample rate */ + struct audsettings as; + as.freq = lm4549_load(s, offset); + as.nchannels = 2; + as.fmt = AUD_FMT_S16; + as.endianness = 0; + + s->voice = AUD_open_out( + &s->card, + s->voice, + "lm4549.out", + dev, + lm4549_audio_out_callback, + &as + ); + break; + } +} + +void lm4549_sdataout_slot12(ACLinkDevice *dev, uint32_t slot1, uint32_t slot2) +{ +#define SLOT1_RW (1 << 19) + uint16_t control = (slot1 >> 12) & 0x7F; + uint16_t data = (slot2 >> 4) & 0xFFFF; + uint32_t value = 0; + + if ((slot1 & SLOT1_RW) == 0) { + /* Write operation */ + lm4549_write(dev, control, data); + } else { + /* Read operation */ + value = lm4549_read(dev, control); + + /* Write the return value in SDATAIN */ + aclink_sdatain_slot12(dev, slot1 & ~SLOT1_RW, value << 4); + } +} + +void lm4549_sdataout_slot34(ACLinkDevice *dev, uint32_t slot3, uint32_t slot4) +{ + lm4549_state *s = FROM_ACLINK_DEVICE(lm4549_state, dev); + uint32_t sample = ((slot3 >> 4) & 0xFFFF) + (((slot4 >> 4) & 0xFFFF) << 16); + + if (s->buffer_level >= BUFFER_SIZE) { + hw_error("sdataout slot34: overrun\n"); + } + + /* Store the sample in the buffer */ + s->buffer[s->buffer_level++] = sample; + + if (s->buffer_level == BUFFER_SIZE) { + /* Trigger the transfer of the buffer to the audio host */ + lm4549_audio_transfer(s); + } +} + +static int lm4549_init(ACLinkDevice *dev) +{ + lm4549_state *s = FROM_ACLINK_DEVICE(lm4549_state, dev); + struct audsettings as; + + /* Init the register store */ + lm4549_store_init(s, LM4549_Reset, 0x0d50, 0); + lm4549_store_init(s, LM4549_Master_Volume, 0x8008, 0); + lm4549_store_init(s, LM4549_Line_Out_Volume, 0x8000, 0); + lm4549_store_init(s, LM4549_Master_Volume_Mono, 0x8000, 0); + lm4549_store_init(s, LM4549_PC_Beep_Volume, 0x0000, 0); + lm4549_store_init(s, LM4549_Phone_Volume, 0x8008, 0); + lm4549_store_init(s, LM4549_Mic_Volume, 0x8008, 0); + lm4549_store_init(s, LM4549_Line_In_Volume, 0x8808, 0); + lm4549_store_init(s, LM4549_CD_Volume, 0x8808, 0); + lm4549_store_init(s, LM4549_Video_Volume, 0x8808, 0); + lm4549_store_init(s, LM4549_Aux_Volume, 0x8808, 0); + lm4549_store_init(s, LM4549_PCM_Out_Volume, 0x8808, 0); + lm4549_store_init(s, LM4549_Record_Select, 0x0000, 0); + lm4549_store_init(s, LM4549_Record_Gain, 0x8000, 0); + lm4549_store_init(s, LM4549_General_Purpose, 0x0000, 0); + lm4549_store_init(s, LM4549_3D_Control, 0x0101, 0); + lm4549_store_init(s, LM4549_Powerdown_Ctrl_Stat, 0x0000, 0); + lm4549_store_init(s, LM4549_Extended_Audio_ID, 0x0001, 1); + lm4549_store_init(s, LM4549_Extended_Audio_Stat_Ctrl, 0x0000, 0); + lm4549_store_init(s, LM4549_PCM_Front_DAC_Rate, 0xBB80, 0); + lm4549_store_init(s, LM4549_PCM_ADC_Rate, 0xBB80, 0); + lm4549_store_init(s, LM4549_Vendor_ID1, 0x4e53, 1); + lm4549_store_init(s, LM4549_Vendor_ID2, 0x4331, 1); + + /* Enable the ACLink clock */ + aclink_bitclk_enable(dev, 1); + + /* Register an audio card */ + AUD_register_card("lm4549", &s->card); + + /* Open a default voice */ + as.freq = 48000; + as.nchannels = 2; + as.fmt = AUD_FMT_S16; + as.endianness = 0; + + s->voice = AUD_open_out( + &s->card, + s->voice, + "lm4549.out", + s, + lm4549_audio_out_callback, + &as + ); + + AUD_set_volume_out(s->voice, 0, 255, 255); + + /* Reset the input buffer */ + memset(s->buffer, 0x00, sizeof(s->buffer)); + s->buffer_level = 0; + +#if defined(LM4549_DUMP_DAC_INPUT) + fp_dac_input = fopen("lm4549_dac_input.pcm", "wb"); + if (!fp_dac_input) { + hw_error("Unable to open lm4549_dac_input.pcm for writing\n"); + } +#endif + + return 0; +} + +static ACLinkDeviceInfo lm4549_info = { + .qdev = { + .name = "lm4549", + .size = sizeof(lm4549_state) + }, + .init = lm4549_init, + .sdataout_slot12 = lm4549_sdataout_slot12, + .sdataout_slot34 = lm4549_sdataout_slot34, +}; + +static void lm4549_register_device(void) +{ + aclink_register_device(&lm4549_info); +} + +device_init(lm4549_register_device) diff --git a/hw/pl041.c b/hw/pl041.c new file mode 100644 index 0000000..0c84dd8 --- /dev/null +++ b/hw/pl041.c @@ -0,0 +1,436 @@ +/* + * Arm PrimeCell PL041 Advanced Audio Codec Interface + * + * Copyright (c) 2011 + * Written by Mathieu Sonet - www.elasticsheep.com + * + * This code is licenced under the GPL. + * + * ***************************************************************** + * + * This driver emulates the ARM AACI interface. + * It connects the system bus to an ACLink bus on which an audio + * codec can be connected. + * + */ + +#include "sysbus.h" + +#include "pl041.h" +#include "aclink.h" + +/*** Debug macros ***/ + +/* #define PL041_DEBUG_LEVEL 1 */ + +#if defined(PL041_DEBUG_LEVEL) && (PL041_DEBUG_LEVEL >= 1) +#define DBG_L1(fmt, ...) \ +do { printf("pl041: " fmt , ## __VA_ARGS__); } while (0) +#else +#define DBG_L1(fmt, ...) \ +do { } while (0) +#endif + +#if defined(PL041_DEBUG_LEVEL) && (PL041_DEBUG_LEVEL >= 2) +#define DBG_L2(fmt, ...) \ +do { printf("pl041: " fmt , ## __VA_ARGS__); } while (0) +#else +#define DBG_L2(fmt, ...) \ +do { } while (0) +#endif + +/*** Constants ***/ + +#define FIFO_DEPTH 16 + +/*** Types ***/ + +typedef struct { + uint32_t size; + uint32_t half; + uint32_t level; + uint32_t data[FIFO_DEPTH]; +} pl041_fifo; + +typedef struct { + SysBusDevice busdev; + qemu_irq irq; + pl041_regfile regs; + pl041_fifo fifo1; + ACLinkBus *aclink; +} pl041_state; + +/*** Globals ***/ + +static const unsigned char pl041_id[8] = { + 0x41, 0x10, 0x04, 0x00, 0x0d, 0xf0, 0x05, 0xb1 +}; + +#if defined(PL041_DEBUG_LEVEL) +#define REGISTER(name, offset) #name, +static const char *pl041_regs_name[] = { + #include "pl041.hx" +}; +#undef REGISTER +#endif + +/*** Local prototypes ***/ + +static void pl041_isr1_update(pl041_state *s); + +/*** Functions ***/ + +#if defined(PL041_DEBUG_LEVEL) +static const char *get_reg_name(target_phys_addr_t offset) +{ + if (offset <= PL041_dr4_3) { + return pl041_regs_name[offset >> 2]; + } + + return "unknown"; +} +#endif + +static void pl041_fifo1_push(pl041_state *s, uint32_t value) +{ + pl041_fifo *f = &s->fifo1; + + /* Check the FIFO level */ + if (f->level >= f->size) { + hw_error("fifo1 push: overrun\n"); + } + + /* Push the value in the FIFO */ + if (f->level < f->size) { + s->fifo1.data[f->level++] = value; + } + + /* Update the status register */ + if (f->level > 0) { + s->regs.sr1 &= ~(TXUNDERRUN | TXFE); + } + + if (f->level >= (f->size >> 1)) { + s->regs.sr1 &= ~TXHE; + } + + if (f->level >= f->size) { + s->regs.sr1 |= TXFF; + } + + DBG_L1("fifo1_push sr1 = 0x%08x\n", s->regs.sr1); +} + +static void pl041_fifo1_transmit(pl041_state *s) +{ + pl041_fifo *f = &s->fifo1; + uint32_t slots = s->regs.txcr1 & TXSLOT_MASK; + uint32_t written_samples; + + /* Check if FIFO1 transmit is enabled */ + if ((s->regs.txcr1 & TXEN) && (slots & (TXSLOT3 | TXSLOT4))) { + if (f->level >= f->half) { + int i; + + DBG_L1("Transfer FIFO level = %i\n", f->level); + + /* Try to transfer the whole FIFO */ + for (i = 0; i < f->level; i++) { + uint32_t sample = f->data[i]; + uint32_t slot3, slot4; + + /* Check the sample width */ + switch (s->regs.txcr1 & TSIZE_MASK) { + case TSIZE_16BITS: + /* 20-bit left justification */ + slot3 = (sample & 0xFFFF) << 4; + slot4 = ((sample >> 16) & 0xFFFF) << 4; + break; + case TSIZE_18BITS: + case TSIZE_20BITS: + case TSIZE_12BITS: + default: + hw_error("Unsupported TSize\n"); + break; + } + + /* Stop sending if the clock is disabled */ + if (aclink_bitclk_is_enabled(s->aclink) == 0) { + DBG_L1("bitclk is disabled => pause the transfer\n"); + break; + } + + /* Transmit a sample on the ACLINK bus */ + aclink_sdataout_slot34(s->aclink, slot3, slot4); + } + + written_samples = i; + if (written_samples > 0) { + /* Update the FIFO level */ + f->level -= written_samples; + + /* Move back the pending samples to the start of the FIFO */ + for (i = 0; i < f->level; i++) { + f->data[i] = f->data[written_samples + i]; + } + + /* Update the status register */ + s->regs.sr1 &= ~TXFF; + + if (f->level <= (f->size >> 1)) { + s->regs.sr1 |= TXHE; + } + + if (f->level == 0) { + s->regs.sr1 |= TXFE | TXUNDERRUN; + DBG_L1("Empty FIFO\n"); + } + } + } + } +} + +static void pl041_isr1_update(pl041_state *s) +{ + uint32_t mask = 0; + + /* Update ISR1 */ + if (s->regs.sr1 & TXUNDERRUN) { + s->regs.isr1 |= URINTR; + } else { + s->regs.isr1 &= ~URINTR; + } + + if (s->regs.sr1 & TXHE) { + s->regs.isr1 |= TXINTR; + } else { + s->regs.isr1 &= ~TXINTR; + } + + if (!(s->regs.sr1 & TXBUSY) && (s->regs.sr1 & TXFE)) { + s->regs.isr1 |= TXCINTR; + } else { + s->regs.isr1 &= ~TXCINTR; + } + + /* Set the irq mask */ + if (s->regs.ie1 & TXUIE) { + mask |= URINTR; + } + + if (s->regs.ie1 & TXIE) { + mask |= TXINTR; + } + + if (s->regs.ie1 & TXCIE) { + mask |= TXCINTR; + } + + /* Update the irq state */ + qemu_set_irq(s->irq, ((s->regs.isr1 & mask) > 0) ? 1 : 0); + DBG_L2("Set interrupt sr1 = 0x%08x isr1 = 0x%08x masked = 0x%08x\n", + s->regs.sr1, s->regs.isr1, s->regs.isr1 & mask); +} + +static void pl041_sdatain_slot12(DeviceState *dev, + uint32_t slot1, uint32_t slot2) +{ + pl041_state *s = (pl041_state *)dev; + + DBG_L1("pl041_sdatain_slot12 0x%08x 0x%08x\n", slot1, slot2); + + s->regs.sl1rx = slot1; + s->regs.sl2rx = slot2; + + s->regs.slfr &= ~SL1RXBUSY | ~SL2RXBUSY; + s->regs.slfr |= SL1RXVALID | SL2RXVALID; +} + +static void pl041_bitclk_state_changed(DeviceState *dev) +{ + pl041_state *s = (pl041_state *)dev; + + /* Check if the bitclk signal is enabled */ + if (aclink_bitclk_is_enabled(s->aclink) == 1) { + DBG_L1("bitclk enabled\n"); + + /* Trigger pending transfers */ + pl041_fifo1_transmit(s); + pl041_isr1_update(s); + } else { + DBG_L1("bitclk disabled\n"); + } +} + +static uint32_t pl041_read(void *opaque, target_phys_addr_t offset) +{ + pl041_state *s = (pl041_state *)opaque; + int value; + + if (offset >= 0xfe0 && offset < 0x1000) { + DBG_L1("pl041_read [0x%08x]\n", offset); + return pl041_id[(offset - 0xfe0) >> 2]; + } + + if (offset < 0x110) { + value = *((uint32_t *)&s->regs + (offset >> 2)); + } else { + hw_error("pl041_read: Bad offset %x\n", (int)offset); + } + + switch (offset) { + case PL041_allints: + value = s->regs.isr1 & 0x7F; + break; + } + + DBG_L1("pl041_read [0x%08x] %s => 0x%08x\n", offset, + get_reg_name(offset), value); + + return value; +} + +static void pl041_write(void *opaque, target_phys_addr_t offset, + uint32_t value) +{ + pl041_state *s = (pl041_state *)opaque; + + DBG_L1("pl041_write [0x%08x] %s <= 0x%08x\n", offset, + get_reg_name(offset), value); + + /* Write the register */ + if (offset < 0x110) { + *((uint32_t *)&s->regs + (offset >> 2)) = value; + } else { + hw_error("pl041_write: Bad offset %x\n", (int)offset); + } + + /* Execute the actions */ + switch (offset) { +#if defined(PL041_DEBUG_LEVEL) && (PL041_DEBUG_LEVEL >= 1) + case PL041_txcr1: + { + uint32_t txen = s->regs.txcr1 & TXEN; + uint32_t slots = (s->regs.txcr1 & TXSLOT_MASK) >> TXSLOT_MASK_BIT; + uint32_t tsize = (s->regs.txcr1 & TSIZE_MASK) >> TSIZE_MASK_BIT; + uint32_t compact_mode = (s->regs.txcr1 & TXCOMPACT) ? 1 : 0; + uint32_t txfen = (s->regs.txcr1 & TXFEN) > 0 ? 1 : 0; + DBG_L1("=> txen = %i slots = 0x%01x tsize = %i compact = %i" + "txfen = %i\n", txen, slots, tsize, compact_mode, txfen); + break; + } +#endif + case PL041_sl1tx: + s->regs.slfr &= ~SL1TXEMPTY; + aclink_sdataout_slot12(s->aclink, s->regs.sl1tx, s->regs.sl2tx); + break; + + case PL041_sl2tx: + s->regs.sl2tx = value; + s->regs.slfr &= ~SL2TXEMPTY; + break; + + case PL041_intclr: + DBG_L1("=> Clear interrupt intclr = 0x%08x isr1 = 0x%08x\n", + s->regs.intclr, s->regs.isr1); + + if (s->regs.intclr & TXUEC1) { + s->regs.sr1 &= ~TXUNDERRUN; + } + break; + +#if defined(PL041_DEBUG_LEVEL) + case PL041_maincr: + { + char debug[] = " AACIFE SL1RXEN SL1TXEN"; + if (!(value & AACIFE)) { + debug[0] = '!'; + } + if (!(value & SL1RXEN)) { + debug[8] = '!'; + } + if (!(value & SL1TXEN)) { + debug[17] = '!'; + } + DBG_L1("%s\n", debug); + break; + } +#endif + + case PL041_dr1_0: + case PL041_dr1_1: + case PL041_dr1_2: + case PL041_dr1_3: + pl041_fifo1_push(s, value); + break; + } + + /* Transmit the FIFO content */ + pl041_fifo1_transmit(s); + + /* Update the ISR1 register */ + pl041_isr1_update(s); +} + +static void pl041_reset(pl041_state *s) +{ + memset(&s->regs, 0x00, sizeof(pl041_regfile)); + + s->regs.slfr = SL1TXEMPTY | SL2TXEMPTY | SL12TXEMPTY; + s->regs.sr1 = TXFE | RXFE | TXHE; + s->regs.isr1 = 0; + + s->fifo1.size = FIFO_DEPTH; + s->fifo1.half = FIFO_DEPTH >> 1; + s->fifo1.level = 0; + memset(s->fifo1.data, 0x00, sizeof(s->fifo1.data)); +} + +static CPUReadMemoryFunc * const pl041_readfn[] = { + pl041_read, + pl041_read, + pl041_read +}; + +static CPUWriteMemoryFunc * const pl041_writefn[] = { + pl041_write, + pl041_write, + pl041_write +}; + +static ACLinkControllerInfo pl041_controller_info = { + .sdatain_slot12 = pl041_sdatain_slot12, + .bitclk_state_changed = pl041_bitclk_state_changed, +}; + +static int pl041_init(SysBusDevice *dev) +{ + pl041_state *s = FROM_SYSBUS(pl041_state, dev); + int iomemtype; + + DBG_L1("pl041_init\n"); + + /* Connect the device to the sysbus */ + iomemtype = cpu_register_io_memory(pl041_readfn, + pl041_writefn, + s, + DEVICE_NATIVE_ENDIAN); + sysbus_init_mmio(dev, 0x1000, iomemtype); + sysbus_init_irq(dev, &s->irq); + + /* Create the ACLink bus */ + s->aclink = aclink_create_bus(&dev->qdev, "aclink"); + aclink_set_controller_info(s->aclink, &pl041_controller_info); + + /* Reset the device */ + pl041_reset(s); + + return 0; +} + +static void pl041_register_devices(void) +{ + sysbus_register_dev("pl041", sizeof(pl041_state), pl041_init); +} + +device_init(pl041_register_devices) diff --git a/hw/pl041.h b/hw/pl041.h new file mode 100644 index 0000000..51b66e3 --- /dev/null +++ b/hw/pl041.h @@ -0,0 +1,126 @@ +/* + * Arm PrimeCell PL041 Advanced Audio Codec Interface + * + * Copyright (c) 2011 + * Written by Mathieu Sonet - www.elasticsheep.com + * + * This code is licenced under the GPL. + * + * ***************************************************************** + * + * This driver emulates the ARM AACI interface. + * It connects the system bus to an ACLink bus on which an audio + * codec can be connected. + * + */ + +/* Register file */ +#define REGISTER(name, offset) uint32_t name; +typedef struct { + #include "pl041.hx" +} pl041_regfile; +#undef REGISTER + +/* Register addresses */ +#define REGISTER(name, offset) PL041_##name = offset, +enum { + #include "pl041.hx" +}; +#undef REGISTER + +/* Register bits */ + +/* IEx */ +#define TXCIE (1 << 0) +#define RXTIE (1 << 1) +#define TXIE (1 << 2) +#define RXIE (1 << 3) +#define RXOIE (1 << 4) +#define TXUIE (1 << 5) +#define RXTOIE (1 << 6) + +/* TXCRx */ +#define TXEN (1 << 0) +#define TXSLOT1 (1 << 1) +#define TXSLOT2 (1 << 2) +#define TXSLOT3 (1 << 3) +#define TXSLOT4 (1 << 4) +#define TXCOMPACT (1 << 15) +#define TXFEN (1 << 16) + +#define TXSLOT_MASK_BIT (1) +#define TXSLOT_MASK (0xFFF << TXSLOT_MASK_BIT) + +#define TSIZE_MASK_BIT (13) +#define TSIZE_MASK (0x3 << TSIZE_MASK_BIT) + +#define TSIZE_16BITS (0x0 << TSIZE_MASK_BIT) +#define TSIZE_18BITS (0x1 << TSIZE_MASK_BIT) +#define TSIZE_20BITS (0x2 << TSIZE_MASK_BIT) +#define TSIZE_12BITS (0x3 << TSIZE_MASK_BIT) + +/* SRx */ +#define RXFE (1 << 0) +#define TXFE (1 << 1) +#define RXHF (1 << 2) +#define TXHE (1 << 3) +#define RXFF (1 << 4) +#define TXFF (1 << 5) +#define RXBUSY (1 << 6) +#define TXBUSY (1 << 7) +#define RXOVERRUN (1 << 8) +#define TXUNDERRUN (1 << 9) +#define RXTIMEOUT (1 << 10) +#define RXTOFE (1 << 11) + +/* ISRx */ +#define TXCINTR (1 << 0) +#define RXTOINTR (1 << 1) +#define TXINTR (1 << 2) +#define RXINTR (1 << 3) +#define ORINTR (1 << 4) +#define URINTR (1 << 5) +#define RXTOFEINTR (1 << 6) + +/* SLFR */ +#define SL1RXBUSY (1 << 0) +#define SL1TXBUSY (1 << 1) +#define SL2RXBUSY (1 << 2) +#define SL2TXBUSY (1 << 3) +#define SL12RXBUSY (1 << 4) +#define SL12TXBUSY (1 << 5) +#define SL1RXVALID (1 << 6) +#define SL1TXEMPTY (1 << 7) +#define SL2RXVALID (1 << 8) +#define SL2TXEMPTY (1 << 9) +#define SL12RXVALID (1 << 10) +#define SL12TXEMPTY (1 << 11) +#define RAWGPIOINT (1 << 12) +#define RWIS (1 << 13) + +/* MAINCR */ +#define AACIFE (1 << 0) +#define LOOPBACK (1 << 1) +#define LOWPOWER (1 << 2) +#define SL1RXEN (1 << 3) +#define SL1TXEN (1 << 4) +#define SL2RXEN (1 << 5) +#define SL2TXEN (1 << 6) +#define SL12RXEN (1 << 7) +#define SL12TXEN (1 << 8) +#define DMAENABLE (1 << 9) + +/* INTCLR */ +#define WISC (1 << 0) +#define RXOEC1 (1 << 1) +#define RXOEC2 (1 << 2) +#define RXOEC3 (1 << 3) +#define RXOEC4 (1 << 4) +#define TXUEC1 (1 << 5) +#define TXUEC2 (1 << 6) +#define TXUEC3 (1 << 7) +#define TXUEC4 (1 << 8) +#define RXTOFEC1 (1 << 9) +#define RXTOFEC2 (1 << 10) +#define RXTOFEC3 (1 << 11) +#define RXTOFEC4 (1 << 12) diff --git a/hw/pl041.hx b/hw/pl041.hx new file mode 100644 index 0000000..529f892 --- /dev/null +++ b/hw/pl041.hx @@ -0,0 +1,62 @@ +/* + * Arm PrimeCell PL041 Advanced Audio Codec Interface + * + * Copyright (c) 2011 + * Written by Mathieu Sonet - www.elasticsheep.com + * + * This code is licenced under the GPL. + * + * ***************************************************************** + * + * This driver emulates the ARM AACI interface. + * It connects the system bus to an ACLink bus on which an audio + * codec can be connected. + * + */ + +/* PL041 register file description */ + +REGISTER( rxcr1, 0x00 ) +REGISTER( txcr1, 0x04 ) +REGISTER( sr1, 0x08 ) +REGISTER( isr1, 0x0C ) +REGISTER( ie1, 0x10 ) +REGISTER( rxcr2, 0x14 ) +REGISTER( txcr2, 0x18 ) +REGISTER( sr2, 0x1C ) +REGISTER( isr2, 0x20 ) +REGISTER( ie2, 0x24 ) +REGISTER( rxcr3, 0x28 ) +REGISTER( txcr3, 0x2C ) +REGISTER( sr3, 0x30 ) +REGISTER( isr3, 0x34 ) +REGISTER( ie3, 0x38 ) +REGISTER( rxcr4, 0x3C ) +REGISTER( txcr4, 0x40 ) +REGISTER( sr4, 0x44 ) +REGISTER( isr4, 0x48 ) +REGISTER( ie4, 0x4C ) +REGISTER( sl1rx, 0x50 ) +REGISTER( sl1tx, 0x54 ) +REGISTER( sl2rx, 0x58 ) +REGISTER( sl2tx, 0x5C ) +REGISTER( sl12rx, 0x60 ) +REGISTER( sl12tx, 0x64 ) +REGISTER( slfr, 0x68 ) +REGISTER( slistat, 0x6C ) +REGISTER( slien, 0x70 ) +REGISTER( intclr, 0x74 ) +REGISTER( maincr, 0x78 ) +REGISTER( reset, 0x7C ) +REGISTER( sync, 0x80 ) +REGISTER( allints, 0x84 ) +REGISTER( mainfr, 0x88 ) +REGISTER( unused, 0x8C ) +REGISTER( dr1_0, 0x90 ) +REGISTER( dr1_1, 0x94 ) +REGISTER( dr1_2, 0x98 ) +REGISTER( dr1_3, 0x9C ) +REGISTER( dr1_4, 0xA0 ) +REGISTER( dr1_5, 0xA4 ) +REGISTER( dr1_6, 0xA8 ) +REGISTER( dr1_7, 0xAC ) diff --git a/hw/versatilepb.c b/hw/versatilepb.c index 46b6a3f..51e7d47 100644 --- a/hw/versatilepb.c +++ b/hw/versatilepb.c @@ -17,6 +17,7 @@ #include "usb-ohci.h" #include "boards.h" #include "blockdev.h" +#include "aclink.h" /* Primary interrupt controller. */ @@ -258,6 +259,11 @@ static void versatile_init(ram_addr_t ram_size, /* Add PL031 Real Time Clock. */ sysbus_create_simple("pl031", 0x101e8000, pic[10]); + /* Add PL041 AACI Interface and connect the LM4549 codec */ + dev = sysbus_create_varargs("pl041", 0x10004000, sic[24]); + ACLinkBus* bus = (ACLinkBus *)qdev_get_child_bus(dev, "aclink"); + aclink_create_device(bus, "lm4549"); + /* Memory map for Versatile/PB: */ /* 0x10000000 System registers. */ /* 0x10001000 PCI controller config registers. */ -- 1.7.0.4