qemu-devel
[Top][All Lists]
Advanced

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

[Qemu-devel] [RFC][PATCH 4/4] Add support for Marvell 88w8618 / MusicPal


From: Jan Kiszka
Subject: [Qemu-devel] [RFC][PATCH 4/4] Add support for Marvell 88w8618 / MusicPal
Date: Sun, 13 Apr 2008 12:11:37 +0200
User-agent: Thunderbird 2.0.0.12 (X11/20080226)

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

Signed-off-by: Jan Kiszka <address@hidden>
---
 Makefile.target |    1 
 hw/boards.h     |    3 
 hw/musicpal.c   | 1266 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 sysemu.h        |    2 
 vl.c            |    1 
 5 files changed, 1272 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,1266 @@
+/*
+ * 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 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 < 0xC0000000) {
+               if (addr >= ram_size)
+                       return NULL;
+               return (void *)(phys_ram_base + addr);
+       } else {
+               if (addr >= 0xC0000000 + SRAM_SIZE)
+                       return NULL;
+               return (void *)(phys_ram_base + sram_off + addr - 0xC0000000);
+       }
+}
+
+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 + 0xC0000000;
+}
+
+
+#ifdef HAS_AUDIO
+
+const char sound_name[] = "mv88w8618";
+
+typedef struct mv88w8618_sound_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;
+       QEMUSoundCard card;
+       SWVoiceOut *voice;
+       int64_t last_callback;
+} mv88w8618_sound_state;
+
+static mv88w8618_sound_state *sound_state;
+
+static void sound_fill_mixer_buffer(mv88w8618_sound_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 = 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 sound_callback(void *opaque, int free)
+{
+       mv88w8618_sound_state *s = opaque;
+       uint32_t old_status = s->status;
+       int64_t now = qemu_get_clock(rt_clock);
+       unsigned int n;
+
+       if (now - s->last_callback < (1000 * s->threshold/2) / (44100*2))
+               return;
+       s->last_callback = now;
+
+       while (free > 0) {
+               n = audio_MIN(s->threshold - s->play_pos, (unsigned int)free);
+               sound_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 |= 1 << 6;
+               if (s->play_pos == s->threshold) {
+                       s->status |= 1 << 7;
+                       s->play_pos = 0;
+                       break;
+               }
+       }
+       if ((s->status ^ old_status) & s->irq_enable)
+               qemu_irq_raise(s->irq);
+}
+
+static void mv88w8618_sound_set_mute(mv88w8618_sound_state *s, int mute)
+{
+       s->mute = mute;
+}
+
+static void mv88w8618_sound_set_volume(mv88w8618_sound_state *s, int volume)
+{
+       s->volume = volume;
+}
+
+static uint32_t mv88w8618_sound_read(void *opaque, target_phys_addr_t offset)
+{
+       mv88w8618_sound_state *s = opaque;
+
+       offset -= s->base;
+       switch (offset) {
+       case 0x00:
+               return s->playback_mode;
+
+       case 0x20:      /* Interrupt Status */
+               return s->status;
+
+       case 0x24:      /* Interrupt Enable */
+               return s->irq_enable;
+
+       default:
+//             printf("addr 0x%08lx\n", offset);
+               return 0;
+       }
+}
+
+static void mv88w8618_sound_write(void *opaque, target_phys_addr_t offset,
+                                 uint32_t value)
+{
+       mv88w8618_sound_state *s = opaque;
+
+       offset -= s->base;
+       switch (offset) {
+       case 0x00:      /* Playback Mode */
+               s->playback_mode = value;
+               if (value & (1 << 7)) {
+                       audsettings_t as = {0, 2, 0, 0};
+
+                       if (value & (1 << 9))
+                               as.freq = (24576000 / 64) / (s->clock_div + 1);
+                       else
+                               as.freq = (11289600 / 64) / (s->clock_div + 1);
+                       if (value & 1)
+                               as.fmt = AUD_FMT_S16;
+                       else
+                               as.fmt = AUD_FMT_S8;
+
+                       s->voice = AUD_open_out(&s->card, s->voice, sound_name,
+                                               s, sound_callback, &as);
+                       if (s->voice)
+                               AUD_set_active_out(s->voice, 1);
+                       else
+                               AUD_log(sound_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 0x18:      /* Clock Divider */
+               s->clock_div = (value >> 8) & 0xFF;
+               break;
+
+       case 0x20:      /* Interrupt Status */
+               s->status &= ~value;
+               break;
+
+       case 0x24:      /* Interrupt Enable */
+               s->irq_enable = value;
+               if (s->status & s->irq_enable)
+                       qemu_irq_raise(s->irq);
+               break;
+
+       case 0x28:      /* Tx Start Low */
+               s->phys_buf = (s->phys_buf & 0xFFFF0000) | (value & 0xFFFF);
+               s->target_buffer = target2host_addr(s->phys_buf);
+               s->play_pos = 0;
+               break;
+
+       case 0x2C:      /* Tx Threshold */
+               s->threshold = (value + 1) * 4;
+               break;
+
+       case 0x40:      /* Tx Start High */
+               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_sound_readfn[] = {
+       mv88w8618_sound_read,
+       mv88w8618_sound_read,
+       mv88w8618_sound_read
+};
+
+static CPUWriteMemoryFunc *mv88w8618_sound_writefn[] = {
+       mv88w8618_sound_write,
+       mv88w8618_sound_write,
+       mv88w8618_sound_write
+};
+
+static mv88w8618_sound_state *mv88w8618_sound_init(uint32_t base, qemu_irq irq)
+{
+       AudioState *audio;
+       mv88w8618_sound_state *s;
+       int iomemtype;
+
+       s = qemu_mallocz(sizeof(mv88w8618_sound_state));
+       if (!s)
+               return NULL;
+       s->base = base;
+       s->irq = irq;
+
+       audio = AUD_init();
+       if (!audio) {
+               AUD_log(sound_name, "No audio state\n");
+               return NULL;
+       }
+       AUD_register_card(audio, sound_name, &s->card);
+
+       iomemtype = cpu_register_io_memory(0, mv88w8618_sound_readfn,
+                                          mv88w8618_sound_writefn, s);
+       cpu_register_physical_memory(base, 0x00001000, iomemtype);
+
+       return s;
+}
+#else  /* !HAS_AUDIO */
+static mv88w8618_sound_state *mv88w8618_sound_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_sound_set_mute(sound_state, (data >> 3) & 1);
+                       break;
+
+               case 40:        /* LOUT2 */
+                       /*
+                        * Map volume:
+                        *  - no positive gain (avoid clipping)
+                        *  - smaller range
+                        */
+                       vol = ((data & 0x7F) - 0x7F) / 3;
+                       mv88w8618_sound_set_volume(sound_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_sound_set_mute(sound_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;
+
+               case 0x17:      /* IR? */
+                       event = 0x04000000;
+                       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(SRAM_SIZE);
+       cpu_register_physical_memory(0xC0000000, 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]);
+
+       sound_state = mv88w8618_sound_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
+};




reply via email to

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