qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Qemu-devel] [PATCH v2] Add AACI audio playback support to the ARM Versa


From: Mathieu Sonet
Subject: [Qemu-devel] [PATCH v2] Add AACI audio playback support to the ARM Versatile/PB platform
Date: Fri, 13 May 2011 15:08:10 -0700

The ACLink bus has been removed.
The PL041/AACI driver now directly control the LM4549 codec driver.

The AC97 emulation in ac97.c has not been reused. Making the current
implementation sharable is not trivial and could bring regression
to other platforms.

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 <address@hidden>
---
 Makefile.target  |    1 +
 hw/lm4549.c      |  307 +++++++++++++++++++++++++++++++++++++++++
 hw/lm4549.h      |   45 ++++++
 hw/pl041.c       |  406 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 hw/pl041.h       |  125 +++++++++++++++++
 hw/pl041.hx      |   61 ++++++++
 hw/versatilepb.c |    3 +
 7 files changed, 948 insertions(+), 0 deletions(-)
 create mode 100644 hw/lm4549.c
 create mode 100644 hw/lm4549.h
 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..1a6565f 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 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/lm4549.c b/hw/lm4549.c
new file mode 100644
index 0000000..e58d4d4
--- /dev/null
+++ b/hw/lm4549.c
@@ -0,0 +1,307 @@
+/*
+ * 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.
+ *
+ */
+
+#include "hw.h"
+#include "audio/audio.h"
+#include "lm4549.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
+
+/*** 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
+};
+
+/*** 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("store_init: 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("store: Out of bound offset 0x%x\n", (int)offset);
+    }
+
+    if (r->data[offset].read_only) {
+        DPRINTF("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("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];
+            }
+        }
+    }
+}
+
+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("audio_out_callback size = %i free = %i\n", size, free);
+#endif
+
+    /* Detect that no data are consumed
+       => 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);
+
+        /* Request more data */
+        if (s->data_req_cb != NULL) {
+            (s->data_req_cb)(s->opaque);
+        }
+    }
+}
+
+uint32_t lm4549_read(lm4549_state *s, target_phys_addr_t offset)
+{
+    uint32_t value = 0;
+
+    /* Read the stored value */
+    value = lm4549_load(s, offset);
+    DPRINTF("read [0x%02x] = 0x%04x\n", offset, value);
+
+    return value;
+}
+
+void lm4549_write(lm4549_state *s,
+                  target_phys_addr_t offset, uint32_t value)
+{
+    DPRINTF("write [0x%02x] = 0x%04x\n", offset, value);
+
+    switch (offset) {
+    case LM4549_Reset:
+        lm4549_store_reset(s);
+        break;
+    case LM4549_PCM_Front_DAC_Rate:
+        lm4549_store(s, offset, value);
+        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",
+            s,
+            lm4549_audio_out_callback,
+            &as
+        );
+        break;
+    case LM4549_Powerdown_Ctrl_Stat:
+        value &= ~0xf;
+        value |= lm4549_load(s, offset) & 0xf;
+        lm4549_store(s, offset, value);
+        break;
+    default:
+        /* Store the new value */
+        lm4549_store(s, offset, value);
+        break;
+    }
+}
+
+uint32_t lm4549_write_sample(lm4549_state *s, uint32_t sample)
+{
+    if (s->buffer_level >= BUFFER_SIZE) {
+        DPRINTF("write_sample Buffer full\n");
+        return 0;
+    }
+
+    /* 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);
+    }
+
+    return 1;
+}
+
+void lm4549_init(lm4549_state *s, lm4549_callback data_req_cb, void* opaque)
+{
+    struct audsettings as;
+
+    /* Store the callback and opaque pointer */
+    s->data_req_cb = data_req_cb;
+    s->opaque = opaque;
+
+    /* 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,       0x000f, 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);
+
+    /* 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
+}
diff --git a/hw/lm4549.h b/hw/lm4549.h
new file mode 100644
index 0000000..bc61d02
--- /dev/null
+++ b/hw/lm4549.h
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ *
+ */
+
+#include "audio/audio.h"
+
+typedef void (*lm4549_callback)(void *opaque);
+
+/*** LM4549 device state ***/
+typedef struct {
+    struct {
+        uint16_t value;
+        uint16_t default_value;
+        uint16_t read_only;
+    } data[128];
+} lm4549_registers;
+
+typedef struct {
+    lm4549_registers codec_regs;
+    QEMUSoundCard card;
+    SWVoiceOut *voice;
+
+    lm4549_callback data_req_cb;
+    void *opaque;
+
+#define BUFFER_SIZE (512)
+    uint32_t buffer[BUFFER_SIZE];
+    uint32_t buffer_level;
+} lm4549_state;
+
+/*** Public interface ***/
+void lm4549_init(lm4549_state *s, lm4549_callback data_req, void *opaque);
+uint32_t lm4549_read(lm4549_state *s, target_phys_addr_t offset);
+void lm4549_write(lm4549_state *s, target_phys_addr_t offset, uint32_t value);
+uint32_t lm4549_write_sample(lm4549_state *s, uint32_t sample);
diff --git a/hw/pl041.c b/hw/pl041.c
new file mode 100644
index 0000000..123612c
--- /dev/null
+++ b/hw/pl041.c
@@ -0,0 +1,406 @@
+/*
+ * 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
+ * connected to a LM4549 codec.
+ *
+ */
+
+#include "sysbus.h"
+
+#include "pl041.h"
+#include "lm4549.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
+
+#define SLOT1_RW    (1 << 19)
+
+/*** 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;
+    lm4549_state codec;
+} 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
+
+/*** Functions ***/
+
+#if defined(PL041_DEBUG_LEVEL)
+static const char *get_reg_name(target_phys_addr_t offset)
+{
+    if (offset <= 0xAC) {
+        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];
+
+                /* Transmit one sample to the codec */
+                if (lm4549_write_sample(&s->codec, sample) == 0) {
+                    DBG_L1("Codec buffer full\n");
+                    break;
+                }
+            }
+
+            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_request_data(void *opaque)
+{
+    pl041_state *s = (pl041_state *)opaque;
+
+    /* Trigger pending transfers */
+    pl041_fifo1_transmit(s);
+    pl041_isr1_update(s);
+}
+
+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;
+    uint16_t control, data;
+    uint32_t result;
+
+    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;
+
+        control = (s->regs.sl1tx >> 12) & 0x7F;
+        data = (s->regs.sl2tx >> 4) & 0xFFFF;
+
+        if ((s->regs.sl1tx & SLOT1_RW) == 0) {
+            /* Write operation */
+            lm4549_write(&s->codec, control, data);
+        } else {
+            /* Read operation */
+            result = lm4549_read(&s->codec, control);
+
+            /* Store the returned value */
+            s->regs.sl1rx = s->regs.sl1tx & ~SLOT1_RW;
+            s->regs.sl2rx = result << 4;
+
+            s->regs.slfr &= ~SL1RXBUSY | ~SL2RXBUSY;
+            s->regs.slfr |= SL1RXVALID | SL2RXVALID;
+        }
+        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 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);
+
+    /* Reset the device */
+    pl041_reset(s);
+
+    /* Init the codec */
+    lm4549_init(&s->codec, &pl041_request_data, (void *)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..ddf452d
--- /dev/null
+++ b/hw/pl041.h
@@ -0,0 +1,125 @@
+/*
+ * 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
+ * connected to a LM4549 codec.
+ *
+ */
+
+/* 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..95e7c4e
--- /dev/null
+++ b/hw/pl041.hx
@@ -0,0 +1,61 @@
+/*
+ * 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
+ * connected to a LM4549 codec.
+ *
+ */
+
+/* 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..f3e2222 100644
--- a/hw/versatilepb.c
+++ b/hw/versatilepb.c
@@ -258,6 +258,9 @@ 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 to the LM4549 codec */
+    dev = sysbus_create_varargs("pl041", 0x10004000, sic[24]);
+
     /* Memory map for Versatile/PB:  */
     /* 0x10000000 System registers.  */
     /* 0x10001000 PCI controller config registers.  */
--
1.7.0.4



reply via email to

[Prev in Thread] Current Thread [Next in Thread]