qemu-devel
[Top][All Lists]
Advanced

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

[Qemu-devel] Re: [RFC][PATCH 4/4] Add support for Marvell 88w8618 / Musi


From: Jan Kiszka
Subject: [Qemu-devel] Re: [RFC][PATCH 4/4] Add support for Marvell 88w8618 / MusicPal
Date: Mon, 14 Apr 2008 21:21:26 +0200
User-agent: Thunderbird 2.0.0.12 (X11/20080226)

Jan Kiszka wrote:
> This is the board emulation for Freecom's MusicPal, featuring
>  - rudimentary PIT and PIC
>  - up to 2 UARTs
>  - 88W8xx8 Ethernet controller
>  - 88W8618 audio controller
>  - Wolfson WM8750 mixer chip (volume control and mute only)
>  - 128×64 display with brightness control
>  - all input buttons
> 
> Using up to 32 MB flash, I hit a limit /wrt phys_ram_size. I worked
> around this for now by extending MAX_BIOS_SIZE to 32 MB, surely not a
> nice solution.
> 
> The emulation suffers a bit from limited time, specifically to implement
> details of hardware setup/shutdown. Another problem - not only for the
> emulation, but also for native Linux support - is that Marvell yet hides
> their specs from the community, and existing Linux code [1] is not that
> clear in every required detail. In case the specs remain Marvell secret,
> maybe the emulation can help a bit to reverse-engineer the remaining
> bits (I'm thinking of the binary-blobbed WLAN driver e.g.).
> 
> [1] http://www.musicpal.info/gpl.htm

Here is an update, fixing specifically the TX IRQ rate of the audio
emulation (which now allows me to play WMA 128kbit streams like "delta
radio" smoothly) and incorporating many of the comments the reviewers
provided (but not all: still home-brewed volume control, still many
unnamed magics).

Signed-off-by: Jan Kiszka <address@hidden>
---
 Makefile.target |    1 
 hw/boards.h     |    3 
 hw/musicpal.c   | 1286 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 sysemu.h        |    2 
 vl.c            |    1 
 5 files changed, 1292 insertions(+), 1 deletion(-)

Index: b/Makefile.target
===================================================================
--- a/Makefile.target
+++ b/Makefile.target
@@ -612,6 +612,7 @@ OBJS+= spitz.o ide.o serial.o nand.o ecc
 OBJS+= omap1.o omap_lcdc.o omap_dma.o omap_clk.o omap_mmc.o omap_i2c.o
 OBJS+= palm.o tsc210x.o
 OBJS+= mst_fpga.o mainstone.o
+OBJS+= musicpal.o pflash_cfi02.o
 CPPFLAGS += -DHAS_AUDIO
 endif
 ifeq ($(TARGET_BASE_ARCH), sh4)
Index: b/hw/boards.h
===================================================================
--- a/hw/boards.h
+++ b/hw/boards.h
@@ -101,4 +101,7 @@ extern QEMUMachine dummy_m68k_machine;
 /* mainstone.c */
 extern QEMUMachine mainstone2_machine;
 
+/* musicpal.c */
+extern QEMUMachine musicpal_machine;
+
 #endif
Index: b/vl.c
===================================================================
--- a/vl.c
+++ b/vl.c
@@ -8056,6 +8056,7 @@ static void register_machines(void)
     qemu_register_machine(&connex_machine);
     qemu_register_machine(&verdex_machine);
     qemu_register_machine(&mainstone2_machine);
+    qemu_register_machine(&musicpal_machine);
 #elif defined(TARGET_SH4)
     qemu_register_machine(&shix_machine);
     qemu_register_machine(&r2d_machine);
Index: b/sysemu.h
===================================================================
--- a/sysemu.h
+++ b/sysemu.h
@@ -109,7 +109,7 @@ extern unsigned int nb_prom_envs;
 #endif
 
 /* XXX: make it dynamic */
-#define MAX_BIOS_SIZE (4 * 1024 * 1024)
+#define MAX_BIOS_SIZE (32 * 1024 * 1024)
 #if defined (TARGET_PPC)
 #define BIOS_SIZE (1024 * 1024)
 #elif defined (TARGET_SPARC64)
Index: b/hw/musicpal.c
===================================================================
--- /dev/null
+++ b/hw/musicpal.c
@@ -0,0 +1,1286 @@
+/*
+ * Marvell MV88W8618 / Freecom MusicPal emulation.
+ *
+ * Copyright (c) 2008 Jan Kiszka
+ *
+ * This code is licenced under the GNU GPL v2.
+ */
+
+#include <math.h>
+
+#include "hw.h"
+#include "arm-misc.h"
+#include "devices.h"
+#include "net.h"
+#include "sysemu.h"
+#include "boards.h"
+#include "pc.h"
+#include "qemu-timer.h"
+#include "block.h"
+#include "flash.h"
+#include "console.h"
+#include "audio/audio.h"
+#include "i2c.h"
+
+#define MV_SRAM_BASE   0xC0000000
+#define MV_SRAM_SIZE   0x20000
+
+static uint32_t gpio_in_state = 0xffffffff;
+static uint32_t gpio_out_state;
+static ram_addr_t sram_off;
+
+
+static void *target2host_addr(uint32_t addr)
+{
+       if (addr < MV_SRAM_BASE) {
+               if (addr >= ram_size)
+                       return NULL;
+               return (void *)(phys_ram_base + addr);
+       } else {
+               if (addr >= MV_SRAM_BASE + MV_SRAM_SIZE)
+                       return NULL;
+               return (void *)(phys_ram_base + sram_off + addr - MV_SRAM_BASE);
+       }
+}
+
+static uint32_t host2target_addr(void *addr)
+{
+       if (addr < ((void *)phys_ram_base) + sram_off)
+               return (unsigned long)addr - (unsigned long)phys_ram_base;
+       else
+               return (unsigned long)addr - (unsigned long)phys_ram_base -
+                       sram_off + MV_SRAM_BASE;
+}
+
+
+#ifdef HAS_AUDIO
+
+/* Register offsets */
+#define MV_AUDIO_PLAYBACK_MODE 0x00
+#define MV_AUDIO_CLOCK_DIV     0x18
+#define MV_AUDIO_IRQ_STATUS    0x20
+#define MV_AUDIO_IRQ_ENABLE    0x24
+#define MV_AUDIO_TX_START_LO   0x28
+#define MV_AUDIO_TX_THRESHOLD  0x2C
+#define MV_AUDIO_TX_START_HI   0x40
+
+/* Status register and IRQ enable bits */
+#define MV_AUDIO_TX_HALF       (1 << 6)
+#define MV_AUDIO_TX_FULL       (1 << 7)
+
+/* Playback mode bits */
+#define MV_AUDIO_16BIT_SAMPLE  (1 << 0)
+#define MV_AUDIO_PLAYBACK_EN   (1 << 7)
+#define MV_AUDIO_CLOCK_24MHZ   (1 << 9)
+
+const char audio_name[] = "mv88w8618";
+
+typedef struct mv88w8618_audio_state {
+       uint32_t base;
+       qemu_irq irq;
+       uint32_t playback_mode;
+       uint32_t status;
+       uint32_t irq_enable;
+       unsigned long phys_buf;
+       void *target_buffer;
+       uint8_t mixer_buffer[4096];
+       int volume;
+       int mute;
+       unsigned int threshold;
+       unsigned int play_pos;
+       uint32_t clock_div;
+       int data_rate;
+       QEMUSoundCard card;
+       SWVoiceOut *voice;
+       int64_t last_callback;
+} mv88w8618_audio_state;
+
+static mv88w8618_audio_state *audio_state;
+
+static void audio_fill_mixer_buffer(mv88w8618_audio_state *s, unsigned int 
length)
+{
+       unsigned int pos;
+       double val;
+
+       if (s->mute) {
+               memset(s->mixer_buffer, 0, length);
+               return;
+       }
+
+       if (s->playback_mode & 1)
+               for (pos = 0; pos < length; pos += 2) {
+                       val = *(int16_t *)(s->target_buffer + s->play_pos + 
pos);
+                       val = le16_to_cpu(val) * pow(10.0, s->volume/20.0);
+                       *(int16_t *)(s->mixer_buffer + pos) = val;
+               }
+       else
+               for (pos = 0; pos < length; pos += 1) {
+                       val = *(int8_t *)(s->target_buffer + s->play_pos + pos);
+                       val = val * pow(10.0, s->volume/20.0);
+                       *(int8_t *)(s->mixer_buffer + pos) = val;
+               }
+}
+
+static void audio_callback(void *opaque, int free)
+{
+       mv88w8618_audio_state *s = opaque;
+       uint32_t old_status = s->status;
+       int64_t now = qemu_get_clock(vm_clock);
+       unsigned int n;
+
+       if (now - s->last_callback <
+           (1000000000 / s->data_rate) * s->threshold/4)
+               return;
+       s->last_callback = now;
+
+       while (free > 0) {
+               n = audio_MIN(s->threshold - s->play_pos, (unsigned int)free);
+               audio_fill_mixer_buffer(s, n);
+               n = AUD_write(s->voice, s->mixer_buffer, n);
+               if (!n)
+                       break;
+
+               s->play_pos += n;
+               free -= n;
+
+               if (s->play_pos >= s->threshold/2)
+                       s->status |= MV_AUDIO_TX_HALF;
+               if (s->play_pos == s->threshold) {
+                       s->status |= MV_AUDIO_TX_FULL;
+                       s->play_pos = 0;
+                       break;
+               }
+       }
+       if ((s->status ^ old_status) & s->irq_enable)
+               qemu_irq_raise(s->irq);
+}
+
+static void mv88w8618_audio_set_mute(mv88w8618_audio_state *s, int mute)
+{
+       s->mute = mute;
+}
+
+static void mv88w8618_audio_set_volume(mv88w8618_audio_state *s, int volume)
+{
+       s->volume = volume;
+}
+
+static uint32_t mv88w8618_audio_read(void *opaque, target_phys_addr_t offset)
+{
+       mv88w8618_audio_state *s = opaque;
+
+       offset -= s->base;
+       switch (offset) {
+       case MV_AUDIO_PLAYBACK_MODE:
+               return s->playback_mode;
+
+       case MV_AUDIO_IRQ_STATUS:
+               return s->status;
+
+       case MV_AUDIO_IRQ_ENABLE:
+               return s->irq_enable;
+
+       default:
+//             printf("addr 0x%08lx\n", offset);
+               return 0;
+       }
+}
+
+static void mv88w8618_audio_write(void *opaque, target_phys_addr_t offset,
+                                 uint32_t value)
+{
+       mv88w8618_audio_state *s = opaque;
+
+       offset -= s->base;
+       switch (offset) {
+       case MV_AUDIO_PLAYBACK_MODE:
+               s->playback_mode = value;
+               if (value & MV_AUDIO_PLAYBACK_EN) {
+                       audsettings_t as = {0, 2, 0, AUDIO_HOST_ENDIANNESS};
+
+                       if (value & MV_AUDIO_CLOCK_24MHZ)
+                               as.freq = (24576000 / 64) / (s->clock_div + 1);
+                       else
+                               as.freq = (11289600 / 64) / (s->clock_div + 1);
+                       if (value & MV_AUDIO_16BIT_SAMPLE) {
+                               as.fmt = AUD_FMT_S16;
+                               s->data_rate = as.freq * 2;
+                       } else {
+                               as.fmt = AUD_FMT_S8;
+                               s->data_rate = as.freq;
+                       }
+
+                       s->voice = AUD_open_out(&s->card, s->voice, audio_name,
+                                               s, audio_callback, &as);
+                       if (s->voice)
+                               AUD_set_active_out(s->voice, 1);
+                       else
+                               AUD_log(audio_name, "Could not open voice\n");
+                       s->last_callback = 0;
+                       s->status = 0;
+               } else if (s->voice)
+                       AUD_set_active_out(s->voice, 0);
+               break;
+
+       case MV_AUDIO_CLOCK_DIV:
+               s->clock_div = (value >> 8) & 0xFF;
+               break;
+
+       case MV_AUDIO_IRQ_STATUS:
+               s->status &= ~value;
+               break;
+
+       case MV_AUDIO_IRQ_ENABLE:
+               s->irq_enable = value;
+               if (s->status & s->irq_enable)
+                       qemu_irq_raise(s->irq);
+               break;
+
+       case MV_AUDIO_TX_START_LO:
+               s->phys_buf = (s->phys_buf & 0xFFFF0000) | (value & 0xFFFF);
+               s->target_buffer = target2host_addr(s->phys_buf);
+               s->play_pos = 0;
+               break;
+
+       case MV_AUDIO_TX_THRESHOLD:
+               s->threshold = (value + 1) * 4;
+               break;
+
+       case MV_AUDIO_TX_START_HI:
+               s->phys_buf = (s->phys_buf & 0xFFFF) | (value << 16);
+               s->target_buffer = target2host_addr(s->phys_buf);
+               s->play_pos = 0;
+               break;
+       }
+
+//     printf("addr 0x%08lx, value 0x%08x\n", offset, value);
+}
+
+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 mv88w8618_audio_state *mv88w8618_audio_init(uint32_t base, qemu_irq irq)
+{
+       AudioState *audio;
+       mv88w8618_audio_state *s;
+       int iomemtype;
+
+       s = qemu_mallocz(sizeof(mv88w8618_audio_state));
+       if (!s)
+               return NULL;
+       s->base = base;
+       s->irq = irq;
+
+       audio = AUD_init();
+       if (!audio) {
+               AUD_log(audio_name, "No audio state\n");
+               return NULL;
+       }
+       AUD_register_card(audio, audio_name, &s->card);
+
+       iomemtype = cpu_register_io_memory(0, mv88w8618_audio_readfn,
+                                          mv88w8618_audio_writefn, s);
+       cpu_register_physical_memory(base, 0x00001000, iomemtype);
+
+       return s;
+}
+#else  /* !HAS_AUDIO */
+static mv88w8618_audio_state *mv88w8618_audio_init(uint32_t base, qemu_irq irq)
+{
+       return NULL;
+}
+#endif /* !HAS_AUDIO */
+
+
+typedef struct mv88w8618_tx_desc {
+       uint32_t cmdstat;
+       uint16_t res;
+       uint16_t bytes;
+       uint32_t buffer;
+       uint32_t next;
+} mv88w8618_tx_desc;
+
+typedef struct mv88w8618_rx_desc {
+       uint32_t cmdstat;
+       uint16_t bytes;
+       uint16_t buffer_size;
+       uint32_t buffer;
+       uint32_t next;
+} mv88w8618_rx_desc;
+
+typedef struct mv88w8618_eth_state {
+       uint32_t base;
+       qemu_irq irq;
+       uint32_t icr;
+       uint32_t imr;
+       int vlan_header;
+       mv88w8618_tx_desc *tx_queue[2];
+       mv88w8618_rx_desc *rx_queue[4];
+       mv88w8618_rx_desc *frx_queue[4];
+       mv88w8618_rx_desc *cur_rx[4];
+       VLANClientState *vc;
+} mv88w8618_eth_state;
+
+static int eth_can_receive(void *opaque)
+{
+       return 1;
+}
+
+static void eth_receive(void *opaque, const uint8_t *buf, int size)
+{
+       mv88w8618_eth_state *s = opaque;
+       mv88w8618_rx_desc *desc;
+       int i;
+
+//     printf("Receiving %d bytes\n", size);
+       for (i = 0; i < 4; i++) {
+               desc = s->cur_rx[i];
+               if (!desc)
+                       continue;
+               do {
+                       if (le32_to_cpu(desc->cmdstat) & (1 << 31) &&
+                           le16_to_cpu(desc->buffer_size) >= size) {
+                               
memcpy(target2host_addr(le32_to_cpu(desc->buffer) + s->vlan_header),
+                                      buf, size);
+                               desc->bytes = cpu_to_le16(size + 
s->vlan_header);
+                               desc->cmdstat = 
cpu_to_le32(le32_to_cpu(desc->cmdstat) & ~(1 << 31));
+                               s->cur_rx[i] = 
target2host_addr(le32_to_cpu(desc->next));
+
+                               s->icr |= 1;
+                               if (s->icr & s->imr)
+                                       qemu_irq_raise(s->irq);
+                               return;
+                       }
+                       desc = target2host_addr(le32_to_cpu(desc->next));
+               } while (desc != s->rx_queue[i]);
+       }
+       printf(">>>Dropping packet!\n");
+}
+
+static void eth_send(mv88w8618_eth_state *s, int queue_index)
+{
+       mv88w8618_tx_desc *desc = s->tx_queue[queue_index];
+
+       do {
+               if (le32_to_cpu(desc->cmdstat) & (1 << 31)) {
+//                     printf("Sending %d bytes\n", le16_to_cpu(desc->bytes));
+                       qemu_send_packet(s->vc, 
target2host_addr(le32_to_cpu(desc->buffer)),
+                                        le16_to_cpu(desc->bytes));
+                       desc->cmdstat = cpu_to_le32(le32_to_cpu(desc->cmdstat) 
& ~(1 << 31));
+                       s->icr |= 1 << (3 - queue_index);
+               }
+               desc = target2host_addr(le32_to_cpu(desc->next));
+       } while (desc != s->tx_queue[queue_index]);
+}
+
+static uint32_t mv88w8618_eth_read(void *opaque, target_phys_addr_t offset)
+{
+       mv88w8618_eth_state *s = opaque;
+
+       offset -= s->base;
+       switch (offset) {
+       case 0x10:      /* smir - always ready */
+               return 1 << 27 | 0x004;
+
+       case 0x450:     /* icr */
+               return s->icr;
+
+       case 0x458:     /* imr */
+               return s->imr;
+
+       case 0x480 ... 0x48c:   /* frdp[0..3] */
+               return host2target_addr(s->frx_queue[(offset - 0x480)/4]);
+
+       case 0x4a0 ... 0x4ac:   /* crdp[0..3] */
+               return host2target_addr(s->rx_queue[(offset - 0x4a0)/4]);
+
+       case 0x4e0 ... 0x4e4:   /* ctdp[0..2] */
+               return host2target_addr(s->tx_queue[(offset - 0x4e0)/4]);
+
+       default:
+               return 0;
+       }
+}
+
+static void mv88w8618_eth_write(void *opaque, target_phys_addr_t offset,
+                               uint32_t value)
+{
+       mv88w8618_eth_state *s = opaque;
+
+       offset -= s->base;
+       switch (offset) {
+       case 0x408:     /* pcxr */
+               s->vlan_header = (value >> 27) & 2;
+               break;
+
+       case 0x448:     /* sdcmr */
+               if (value & (1 << 23))
+                       eth_send(s, 1);
+               if (value & (1 << 24))
+                       eth_send(s, 0);
+               if (value & (3 << 23) && s->icr & s->imr)
+                       qemu_irq_raise(s->irq);
+               break;
+
+       case 0x450:     /* icr */
+               s->icr &= value;
+               break;
+
+       case 0x458:     /* imr */
+               s->imr = value;
+               if (s->icr & s->imr)
+                       qemu_irq_raise(s->irq);
+               break;
+
+       case 0x480 ... 0x48c:   /* frdp[0..3] */
+               s->frx_queue[(offset - 0x480)/4] = target2host_addr(value);
+               break;
+
+       case 0x4a0 ... 0x4ac:   /* crdp[0..3] */
+               s->rx_queue[(offset - 0x4a0)/4] = s->cur_rx[(offset - 0x4a0)/4] 
=
+                       target2host_addr(value);
+               break;
+
+       case 0x4e0 ... 0x4e4:   /* ctdp[0..2] */
+               s->tx_queue[(offset - 0x4e0)/4] = target2host_addr(value);
+               break;
+       }
+}
+
+static CPUReadMemoryFunc *mv88w8618_eth_readfn[] = {
+       mv88w8618_eth_read,
+       mv88w8618_eth_read,
+       mv88w8618_eth_read
+};
+
+static CPUWriteMemoryFunc *mv88w8618_eth_writefn[] = {
+       mv88w8618_eth_write,
+       mv88w8618_eth_write,
+       mv88w8618_eth_write
+};
+
+static void mv88w8618_eth_init(NICInfo *nd, uint32_t base, qemu_irq irq)
+{
+       mv88w8618_eth_state *s;
+       int iomemtype;
+
+       s = qemu_mallocz(sizeof(mv88w8618_eth_state));
+       if (!s)
+               return;
+       s->base = base;
+       s->irq = irq;
+       s->vc = qemu_new_vlan_client(nd->vlan, eth_receive, eth_can_receive, s);
+       iomemtype = cpu_register_io_memory(0, mv88w8618_eth_readfn,
+                                          mv88w8618_eth_writefn, s);
+       cpu_register_physical_memory(base, 0x00001000, iomemtype);
+}
+
+
+typedef struct musicpal_lcd_state {
+       uint32_t base;
+       enum { NONE, DATA, CMD } mode;
+       int page;
+       int page_off;
+       DisplayState *ds;
+       uint8_t video_ram[128*64/8];
+} musicpal_lcd_state;
+
+static uint32_t lcd_brightness;
+
+static uint8_t scale_lcd_color(uint8_t col)
+{
+       int tmp = col;
+
+       switch (lcd_brightness) {
+       case 0x00070000:        /* 0 */
+               return 0;
+
+       case 0x00000002:        /* 1 */
+               return (tmp * 1) / 7;
+
+       case 0x00010002:        /* 2 */
+               return (tmp * 2) / 7;
+
+       case 0x00000004:        /* 3 */
+               return (tmp * 3) / 7;
+
+       case 0x00060001:        /* 4 */
+               return (tmp * 4) / 7;
+
+       case 0x00050002:        /* 5 */
+               return (tmp * 5) / 7;
+
+       case 0x00030004:        /* 6 */
+               return (tmp * 6) / 7;
+
+       case 0x00040003:        /* 7 */
+       default:
+               return col;
+       }
+}
+
+static void set_lcd_pixel(musicpal_lcd_state *s, int x, int y, int col)
+{
+       int dx, dy;
+
+       for (dy = 0; dy < 3; dy++)
+               for (dx = 0; dx < 3; dx++) {
+                       s->ds->data[(x*3 + dx + (y*3 + dy) * 128*3) * 4 + 0] =
+                               scale_lcd_color(col);
+                       s->ds->data[(x*3 + dx + (y*3 + dy) * 128*3) * 4 + 1] =
+                               scale_lcd_color(col >> 8);
+                       s->ds->data[(x*3 + dx + (y*3 + dy) * 128*3) * 4 + 2] =
+                               scale_lcd_color(col >> 16);
+               }
+}
+
+static void lcd_refresh(void *opaque)
+{
+       musicpal_lcd_state *s = opaque;
+       int x, y;
+
+       for (x = 0; x < 128; x++)
+               for (y = 0; y < 64; y++)
+                       if (s->video_ram[x + (y/8)*128] & (1 << (y % 8)))
+                               set_lcd_pixel(s, x, y, 0x00e0e0ff);
+                       else
+                               set_lcd_pixel(s, x, y, 0);
+
+       dpy_update(s->ds, 0, 0, 128*3, 64*3);
+}
+
+static uint32_t musicpal_lcd_read(void *opaque, target_phys_addr_t offset)
+{
+       musicpal_lcd_state *s = opaque;
+
+       offset -= s->base;
+       switch (offset) {
+       case 0x180:
+               return 0x00400000;
+
+       default:
+               return 0;
+       }
+}
+
+static void musicpal_lcd_write(void *opaque, target_phys_addr_t offset,
+                               uint32_t value)
+{
+       musicpal_lcd_state *s = opaque;
+
+       offset -= s->base;
+       switch (offset) {
+       case 0x1ac:
+               if (value == 0x00100011)
+                       s->mode = DATA;
+               else if (value == 0x00104011)
+                       s->mode = CMD;
+               else
+                       s->mode = NONE;
+               break;
+
+       case 0x1bc:
+               if (value >= 0xB0 && value <= 0xB7) {
+                       s->page = value - 0xB0;
+                       s->page_off = 0;
+               }
+               break;
+
+       case 0x1c0:
+               if (s->mode == CMD) {
+                       if (value >= 0xB0 && value <= 0xB7) {
+                               s->page = value - 0xB0;
+                               s->page_off = 0;
+                       }
+               } else if (s->mode == DATA) {
+                       s->video_ram[s->page*128 + s->page_off] = value;
+                       s->page_off++;
+               }
+               break;
+       }
+}
+
+static CPUReadMemoryFunc *musicpal_lcd_readfn[] = {
+       musicpal_lcd_read,
+       musicpal_lcd_read,
+       musicpal_lcd_read
+};
+
+static CPUWriteMemoryFunc *musicpal_lcd_writefn[] = {
+       musicpal_lcd_write,
+       musicpal_lcd_write,
+       musicpal_lcd_write
+};
+
+static void musicpal_lcd_init(DisplayState *ds, uint32_t base)
+{
+       musicpal_lcd_state *s;
+       int iomemtype;
+
+       s = qemu_mallocz(sizeof(musicpal_lcd_state));
+       if (!s)
+               return;
+       s->base = base;
+       s->ds = ds;
+       iomemtype = cpu_register_io_memory(0, musicpal_lcd_readfn,
+                                          musicpal_lcd_writefn, s);
+       cpu_register_physical_memory(base, 0x000001d0, iomemtype);
+
+       graphic_console_init(ds, lcd_refresh, NULL, NULL, NULL, s);
+       dpy_resize(ds, 128*3, 64*3);
+}
+
+
+typedef struct mv88w8618_pic_state
+{
+       uint32_t base;
+       uint32_t level;
+       uint32_t enabled;
+       qemu_irq parent_irq;
+} mv88w8618_pic_state;
+
+
+static void mv88w8618_pic_update(mv88w8618_pic_state *s)
+{
+       qemu_set_irq(s->parent_irq, (s->level & s->enabled));
+       qemu_set_irq(s->parent_irq, (s->level & s->enabled));
+}
+
+static void mv88w8618_pic_set_irq(void *opaque, int irq, int level)
+{
+       mv88w8618_pic_state *s = opaque;
+
+       if (level)
+               s->level |= 1 << irq;
+       else
+               s->level &= ~(1 << irq);
+       mv88w8618_pic_update(s);
+}
+
+static uint32_t mv88w8618_pic_read(void *opaque, target_phys_addr_t offset)
+{
+       mv88w8618_pic_state *s = opaque;
+
+       offset -= s->base;
+       switch (offset) {
+       case 0x00:
+               return s->level;
+
+       default:
+               return 0;
+       }
+}
+
+static void mv88w8618_pic_write(void *opaque, target_phys_addr_t offset,
+                               uint32_t value)
+{
+       mv88w8618_pic_state *s = opaque;
+
+       offset -= s->base;
+       switch (offset) {
+       case 0x08:
+               s->enabled |= value;
+               break;
+
+       case 0x0C:
+               s->enabled &= ~value;
+               s->level &= ~value;
+               break;
+       }
+       mv88w8618_pic_update(s);
+}
+
+static CPUReadMemoryFunc *mv88w8618_pic_readfn[] = {
+       mv88w8618_pic_read,
+       mv88w8618_pic_read,
+       mv88w8618_pic_read
+};
+
+static CPUWriteMemoryFunc *mv88w8618_pic_writefn[] = {
+       mv88w8618_pic_write,
+       mv88w8618_pic_write,
+       mv88w8618_pic_write
+};
+
+static qemu_irq *mv88w8618_pic_init(uint32_t base, qemu_irq parent_irq)
+{
+       mv88w8618_pic_state *s;
+       int iomemtype;
+       qemu_irq *qi;
+
+       s = qemu_mallocz(sizeof(mv88w8618_pic_state));
+       if (!s)
+               return NULL;
+       qi = qemu_allocate_irqs(mv88w8618_pic_set_irq, s, 32);
+       s->base = base;
+       s->parent_irq = parent_irq;
+       iomemtype = cpu_register_io_memory(0, mv88w8618_pic_readfn,
+                                          mv88w8618_pic_writefn, s);
+       cpu_register_physical_memory(base, 0x00000020, iomemtype);
+
+       return qi;
+}
+
+typedef struct mv88w8618_timer_state {
+       ptimer_state *timer;
+       uint32_t limit;
+       int freq;
+       qemu_irq irq;
+} mv88w8618_timer_state;
+
+typedef struct mv88w8618_pit_state {
+       void *timer[4];
+       uint32_t control;
+       uint32_t base;
+} mv88w8618_pit_state;
+
+static void mv88w8618_timer_tick(void *opaque)
+{
+       mv88w8618_timer_state *s = opaque;
+
+       qemu_irq_raise(s->irq);
+}
+
+static void *mv88w8618_timer_init(uint32_t freq, qemu_irq irq)
+{
+       mv88w8618_timer_state *s;
+       QEMUBH *bh;
+
+       s = qemu_mallocz(sizeof(mv88w8618_timer_state));
+       s->irq = irq;
+       s->freq = freq;
+
+       bh = qemu_bh_new(mv88w8618_timer_tick, s);
+       s->timer = ptimer_init(bh);
+
+       return s;
+}
+
+static uint32_t mv88w8618_pit_read(void *opaque, target_phys_addr_t offset)
+{
+       mv88w8618_pit_state *s = opaque;
+       mv88w8618_timer_state *t;
+
+       offset -= s->base;
+       switch (offset) {
+       case 0x14 ... 0x20:
+               t = s->timer[(offset-0x14) >> 2];
+               return ptimer_get_count(t->timer);
+
+       default:
+               return 0;
+       }
+}
+
+static void mv88w8618_pit_write(void *opaque, target_phys_addr_t offset,
+                          uint32_t value)
+{
+       mv88w8618_pit_state *s = opaque;
+       mv88w8618_timer_state *t;
+       int i;
+
+       offset -= s->base;
+       switch (offset) {
+       case 0x00 ... 0x0c:
+               t = s->timer[offset >> 2];
+               t->limit = value;
+               ptimer_set_limit(t->timer, t->limit, 1);
+               break;
+
+       case 0x10:
+               for (i = 0; i < 4; i++) {
+                       if (value & 0xf) {
+                               t = s->timer[i];
+                               ptimer_set_limit(t->timer, t->limit, 0);
+                               ptimer_set_freq(t->timer, t->freq);
+                               ptimer_run(t->timer, 0);
+                       }
+                       value >>= 4;
+               }
+               break;
+       }
+}
+
+static CPUReadMemoryFunc *mv88w8618_pit_readfn[] = {
+       mv88w8618_pit_read,
+       mv88w8618_pit_read,
+       mv88w8618_pit_read
+};
+
+static CPUWriteMemoryFunc *mv88w8618_pit_writefn[] = {
+       mv88w8618_pit_write,
+       mv88w8618_pit_write,
+       mv88w8618_pit_write
+};
+
+static void mv88w8618_pit_init(uint32_t base, qemu_irq *pic, int irq)
+{
+       int iomemtype;
+       mv88w8618_pit_state *s;
+
+       s = qemu_mallocz(sizeof(mv88w8618_pit_state));
+       if (!s)
+               return;
+
+       s->base = base;
+       s->timer[0] = mv88w8618_timer_init(1000000, pic[irq]);
+       s->timer[1] = mv88w8618_timer_init(1000000, pic[irq + 1]);
+       s->timer[2] = mv88w8618_timer_init(1000000, pic[irq + 2]);
+       s->timer[3] = mv88w8618_timer_init(1000000, pic[irq + 3]);
+
+       iomemtype = cpu_register_io_memory(0, mv88w8618_pit_readfn,
+                                          mv88w8618_pit_writefn, s);
+       cpu_register_physical_memory(base, 0x00000034, iomemtype);
+}
+
+
+typedef enum i2c_state {
+       STOPPED = 0,
+       INITIALIZING,
+       SENDING_BIT7,
+       SENDING_BIT6,
+       SENDING_BIT5,
+       SENDING_BIT4,
+       SENDING_BIT3,
+       SENDING_BIT2,
+       SENDING_BIT1,
+       SENDING_BIT0,
+       WAITING_FOR_ACK,
+       RECEIVING_BIT7,
+       RECEIVING_BIT6,
+       RECEIVING_BIT5,
+       RECEIVING_BIT4,
+       RECEIVING_BIT3,
+       RECEIVING_BIT2,
+       RECEIVING_BIT1,
+       RECEIVING_BIT0,
+       SENDING_ACK
+} i2c_state;
+
+typedef struct i2c_interface {
+       i2c_bus *bus;
+       i2c_state state;
+       int last_data;
+       int last_clock;
+       uint8_t buffer;
+       int current_addr;
+} i2c_interface;
+
+static i2c_interface *mixer_i2c;
+
+static void i2c_enter_stop(i2c_interface *i2c)
+{
+       if (i2c->current_addr >= 0)
+               i2c_end_transfer(i2c->bus);
+       i2c->current_addr = -1;
+       i2c->state = STOPPED;
+}
+
+static void i2c_state_update(i2c_interface *i2c, int data, int clock)
+{
+       switch (i2c->state) {
+       case STOPPED:
+               if (data == 0 && i2c->last_data == 1 && clock == 1)
+                       i2c->state = INITIALIZING;
+               break;
+
+       case INITIALIZING:
+               if (clock == 0 && i2c->last_clock == 1 && data == 0)
+                       i2c->state = SENDING_BIT7;
+               else
+                       i2c_enter_stop(i2c);
+               break;
+
+       case SENDING_BIT7 ... SENDING_BIT0:
+               if (clock == 0 && i2c->last_clock == 1) {
+                       i2c->buffer = (i2c->buffer << 1) | data;
+                       i2c->state++; /* will end up in WAITING_FOR_ACK */
+               } else if (data == 1 && i2c->last_data == 0 && clock == 1)
+                       i2c_enter_stop(i2c);
+               break;
+
+       case WAITING_FOR_ACK:
+               if (clock == 0 && i2c->last_clock == 1) {
+                       if (i2c->current_addr < 0) {
+                               i2c->current_addr = i2c->buffer;
+                               i2c_start_transfer(i2c->bus,
+                                                  i2c->current_addr & 0xfe,
+                                                  i2c->buffer & 1);
+                       } else
+                               i2c_send(i2c->bus, i2c->buffer);
+                       if (i2c->current_addr & 1) {
+                               i2c->state = RECEIVING_BIT7;
+                               i2c->buffer = i2c_recv(i2c->bus);
+                       } else
+                               i2c->state = SENDING_BIT7;
+               } else if (data == 1 && i2c->last_data == 0 && clock == 1)
+                       i2c_enter_stop(i2c);
+               break;
+
+       case RECEIVING_BIT7 ... RECEIVING_BIT0:
+               if (clock == 0 && i2c->last_clock == 1) {
+                       i2c->state++; /* will end up in SENDING_ACK */
+                       i2c->buffer <<= 1;
+               } else if (data == 1 && i2c->last_data == 0 && clock == 1)
+                       i2c_enter_stop(i2c);
+               break;
+
+       case SENDING_ACK:
+               if (clock == 0 && i2c->last_clock == 1) {
+                       i2c->state = RECEIVING_BIT7;
+                       if (data == 0)
+                               i2c->buffer = i2c_recv(i2c->bus);
+                       else
+                               i2c_nack(i2c->bus);
+               } else if (data == 1 && i2c->last_data == 0 && clock == 1)
+                       i2c_enter_stop(i2c);
+               break;
+       }
+
+       i2c->last_data = data;
+       i2c->last_clock = clock;
+}
+
+static int i2c_get_data(i2c_interface *i2c)
+{
+       switch (i2c->state) {
+       case RECEIVING_BIT7 ... RECEIVING_BIT0:
+               return (i2c->buffer >> 7);
+
+       case WAITING_FOR_ACK:
+       default:
+               return 0;
+       }
+}
+
+
+typedef struct wm8750_state {
+       i2c_slave slave;
+       enum { STANDBY, WRITING_REG_0, WRITING_REG_1, READING_REG_0, 
READING_REG_1 } mode;
+       uint8_t buffer;
+       int volume;
+} wm8750_state;
+
+static void wm8750_i2c_event(i2c_slave *dev, enum i2c_event event)
+{
+       wm8750_state *s = (wm8750_state *)dev;
+
+       switch (event) {
+       case I2C_START_SEND:
+               if (s->mode == STANDBY)
+                       s->mode = WRITING_REG_0;
+               break;
+
+       case I2C_START_RECV:
+               if (s->mode == STANDBY)
+                       s->mode = READING_REG_0;
+               break;
+
+       case I2C_FINISH:
+               s->mode = STANDBY;
+               break;
+
+       default:
+               break;
+       }
+}
+
+static int wm8750_i2c_recv(i2c_slave *dev)
+{
+//     wm8750_state *s = (wm8750_state *)dev;
+
+//     printf("%s()\n", __FUNCTION__);
+       return 0;
+}
+
+static int wm8750_i2c_send(i2c_slave *dev, uint8_t data)
+{
+       wm8750_state *s = (wm8750_state *)dev;
+       int vol;
+
+       switch (s->mode) {
+       case WRITING_REG_0:
+       case READING_REG_0:
+               s->buffer = data;
+               s->mode++;
+               return 0;
+
+       case WRITING_REG_1:
+               switch (s->buffer >> 1) {
+               case 5:         /* ADCDAC_CONTROL */
+                       mv88w8618_audio_set_mute(audio_state, (data >> 3) & 1);
+                       break;
+
+               case 40:        /* LOUT2 */
+                       /*
+                        * Map volume:
+                        *  - no positive gain (avoid clipping)
+                        *  - smaller range
+                        */
+                       vol = ((data & 0x7F) - 0x7F) / 3;
+                       mv88w8618_audio_set_volume(audio_state, vol);
+                       break;
+               }
+               return 0;
+
+       default:
+               hw_error("wm8750: Getting data while in invalid state!\n");
+               return -1;
+       }
+}
+
+static i2c_interface *musicpal_mixer_init(void)
+{
+       i2c_interface *i2c;
+       wm8750_state *s;
+
+       i2c = qemu_mallocz(sizeof(i2c_interface));
+       if (!i2c)
+               return NULL;
+       i2c->bus = i2c_init_bus();
+       i2c->current_addr = -1;
+
+       s = (wm8750_state *)i2c_slave_init(i2c->bus, 0x34, 
sizeof(wm8750_state));
+       if (!s)
+               return NULL;
+       s->slave.event = wm8750_i2c_event;
+       s->slave.recv = wm8750_i2c_recv;
+       s->slave.send = wm8750_i2c_send;
+
+       mv88w8618_audio_set_mute(audio_state, 1);
+
+       return i2c;
+}
+
+
+static uint32_t musicpal_read(void *opaque, target_phys_addr_t addr)
+{
+       switch (addr) {
+       case 0x80002018:        /* Board revision */
+               return 0x0031;
+
+       case 0x8000d00c:        /* GPIO_OUT Lo */
+               return gpio_out_state & 0xFFFF;
+       case 0x8000d50c:        /* GPIO_OUT Hi */
+               return gpio_out_state >> 16;
+
+       case 0x8000d010:        /* GPIO_IN Lo */
+               return gpio_in_state & 0xFFFF;
+       case 0x8000d510:        /* GPIO_IN Hi */
+               gpio_in_state = (gpio_in_state & ~(1 << 29)) |
+                       (i2c_get_data(mixer_i2c) << 29);
+               return gpio_in_state >> 16;
+
+       case 0x8000d020:        /* GPIO IRQ Status Lo */
+               return ~gpio_in_state & 0xFFFF;
+       case 0x8000d520:        /* GPIO IRQ Status Hi */
+               return ~gpio_in_state >> 16;
+
+       case 0x8000d508:        /* LCD Brightness */
+               return lcd_brightness >> 16;
+
+       case 0x90006004:        /* Flash size */
+               return 0xfeffFFFF;
+
+       /* Workaround to allow loading the binary-only wlandrv.ko crap */
+       case 0x8000c11c:
+               return ~3;
+       case 0x8000c124:
+               return -1;
+
+       default:
+//             printf("addr 0x%08lx\n", addr);
+               return 0;
+       }
+}
+
+static void musicpal_write(void *opaque, target_phys_addr_t addr, uint32_t 
value)
+{
+       switch (addr) {
+       case 0x8000d00c:        /* GPIO_OUT Lo */
+               gpio_out_state = (gpio_out_state & 0xFFFF0000) | (value & 
0xFFFF);
+               break;
+
+       case 0x8000d50c:        /* GPIO_OUT Hi / LCD Brightness */
+               gpio_out_state = (gpio_out_state & 0xFFFF) | (value << 16);
+               lcd_brightness = (lcd_brightness & 0xFFFF0000) | (value & 
0x0007);
+               i2c_state_update(mixer_i2c, (gpio_out_state >> 29) & 1,
+                                (gpio_out_state >> 30) & 1);
+               break;
+
+       case 0x8000d508:        /* LCD Brightness */
+               lcd_brightness = (lcd_brightness & 0xFFFF) | ((value << 16) & 
0x00070000);
+               break;
+
+       case 0x90009034:        /* Reset */
+               if (value & 0x10000)
+                       qemu_system_reset_request();
+               break;
+       }
+}
+
+static CPUReadMemoryFunc *musicpal_readfn[] = {
+       musicpal_read,
+       musicpal_read,
+       musicpal_read,
+};
+
+static CPUWriteMemoryFunc *musicpal_writefn[] = {
+       musicpal_write,
+       musicpal_write,
+       musicpal_write,
+};
+
+
+static void musicpal_key_event(void *opaque, int keycode)
+{
+       qemu_irq irq = opaque;
+       uint32_t event = 0;
+       static int kbd_escape;
+
+       if (keycode == 0xe0) {
+               kbd_escape = 1;
+               return;
+       }
+
+       if (kbd_escape)
+               switch (keycode & 0x7F) {
+               case 0x48:      /* Nav - */
+                       event = 0x00000c00;
+                       break;
+
+               case 0x50:      /* Nav + */
+                       event = 0x00000400;
+                       break;
+
+               case 0x4b:      /* Vol - */
+                       event = 0x00000300;
+                       break;
+
+               case 0x4d:      /* Vol + */
+                       event = 0x00000100;
+                       break;
+               }
+       else
+               switch (keycode & 0x7F) {
+               case 0x21:      /* Fav */
+                       event = 0x00080000;
+                       break;
+
+               case 0x0f:      /* Vol */
+                       event = 0x00200000;
+                       break;
+
+               case 0x1c:      /* Nav */
+                       event = 0x00400000;
+                       break;
+
+               case 0x32:      /* Menu */
+                       event = 0x00100000;
+                       break;
+               }
+
+       if (keycode & 0x80)
+               gpio_in_state |= event;
+       else if (gpio_in_state & event) {
+                       gpio_in_state &= ~event;
+                       qemu_irq_raise(irq);
+       }
+
+       kbd_escape = 0;
+}
+
+
+/* Board init.  */
+static void musicpal_init(int ram_size, int vga_ram_size,
+                          const char *boot_device, DisplayState *ds,
+                          const char *kernel_filename, const char 
*kernel_cmdline,
+                          const char *initrd_filename, const char *cpu_model)
+{
+       CPUState *env;
+       qemu_irq *pic;
+       int index;
+       int iomemtype;
+       unsigned long flash_size;
+
+       if (!cpu_model)
+               cpu_model = "arm926";
+
+       env = cpu_init(cpu_model);
+       if (!env) {
+               fprintf(stderr, "Unable to find CPU definition\n");
+               exit(1);
+       }
+       pic = arm_pic_init_cpu(env);
+
+       cpu_register_physical_memory(0, ram_size, qemu_ram_alloc(ram_size));
+
+       sram_off = qemu_ram_alloc(MV_SRAM_SIZE);
+       cpu_register_physical_memory(MV_SRAM_BASE, MV_SRAM_SIZE, sram_off);
+
+       pic = mv88w8618_pic_init(0x90008000, pic[ARM_PIC_CPU_IRQ]);
+       mv88w8618_pit_init(0x90009000, pic, 4);
+
+       iomemtype = cpu_register_io_memory(0, musicpal_readfn, 
musicpal_writefn, first_cpu);
+       cpu_register_physical_memory(0x80000000, 0x10000, iomemtype);
+       cpu_register_physical_memory(0x90006004, 0x0004, iomemtype);
+       cpu_register_physical_memory(0x90009034, 0x0004, iomemtype);
+
+       serial_mm_init(0x8000C840, 2, pic[11], 1825000, serial_hds[0], 1);
+       if (serial_hds[1])
+               serial_mm_init(0x8000C940, 2, pic[11], 1825000, serial_hds[1], 
1);
+
+       /* Register flash */
+       index = drive_get_index(IF_PFLASH, 0, 0);
+       if (index != -1) {
+               flash_size = bdrv_getlength(drives_table[index].bdrv);
+               if (flash_size != 8*1024*1024 && flash_size != 16*1024*1024 &&
+                   flash_size != 32*1024*1024) {
+                       fprintf(stderr, "Invalid flash image size\n");
+                       exit(1);
+               }
+
+               /*
+                * The original U-Boot accesses the flash at 0xFE000000 (-32 
MB) instead of
+                * 0xFF800000 (if there is 8 MB flash). So remap flash access 
if the image
+                * is smaller than 32 MB.
+                */
+               pflash_cfi02_register(0-32*1024*1024, 
qemu_ram_alloc(flash_size),
+                               drives_table[index].bdrv, 0x10000,
+                               (flash_size + 0xffff) >> 16, 32*1024*1024 / 
flash_size,
+                               2, 0x00BF, 0x236D, 0x0000, 0x0000, 0x5555, 
0x2AAA);
+       }
+
+       musicpal_lcd_init(ds, 0x9000c000);
+
+       qemu_add_kbd_event_handler(musicpal_key_event, pic[12]);
+
+       /*
+        * Wait a bit to catch menu button during U-Boot start-up
+        * (to trigger emergency update).
+        */
+       sleep(1);
+
+       mv88w8618_eth_init(&nd_table[0], 0x80008000, pic[9]);
+
+       audio_state = mv88w8618_audio_init(0x90007000, pic[30]);
+       mixer_i2c = musicpal_mixer_init();
+
+       arm_load_kernel(first_cpu, ram_size, kernel_filename, kernel_cmdline,
+                       initrd_filename, 0x20e, 0x0);
+}
+
+QEMUMachine musicpal_machine = {
+       "musicpal",
+       "Marvell 88w8618 / MusicPal (ARM926EJ-S)",
+       musicpal_init
+};

Attachment: signature.asc
Description: OpenPGP digital signature


reply via email to

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