[Top][All Lists]
[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
+};
- [Qemu-devel] [RFC][PATCH 4/4] Add support for Marvell 88w8618 / MusicPal,
Jan Kiszka <=
- Re: [Qemu-devel] [RFC][PATCH 4/4] Add support for Marvell 88w8618 / MusicPal, malc, 2008/04/13
- [Qemu-devel] Re: [RFC][PATCH 4/4] Add support for Marvell 88w8618 / MusicPal, Jan Kiszka, 2008/04/14
- Re: [Qemu-devel] Re: [RFC][PATCH 4/4] Add support for Marvell 88w8618 / MusicPal, malc, 2008/04/14
- [Qemu-devel] Re: [RFC][PATCH 4/4] Add support for Marvell 88w8618 / MusicPal, Jan Kiszka, 2008/04/14
- Re: [Qemu-devel] Re: [RFC][PATCH 4/4] Add support for Marvell 88w8618 / MusicPal, malc, 2008/04/15
- [Qemu-devel] Re: [RFC][PATCH 4/4] Add support for Marvell 88w8618 / MusicPal, Jan Kiszka, 2008/04/15
- Re: [Qemu-devel] Re: [RFC][PATCH 4/4] Add support for Marvell 88w8618 / MusicPal, malc, 2008/04/16
- [Qemu-devel] Re: [RFC][PATCH 4/4] Add support for Marvell 88w8618 / MusicPal, Jan Kiszka, 2008/04/17