From: Jan Kiszka <jan.kiszka@web.de>
To: qemu-devel@nongnu.org
Subject: [Qemu-devel] [RFC][PATCH 4/4] Add support for Marvell 88w8618 / MusicPal
Date: Sun, 13 Apr 2008 12:11:37 +0200 [thread overview]
Message-ID: <4801DC59.1010403@web.de> (raw)
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 <jan.kiszka@web.de>
---
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
+};
next reply other threads:[~2008-04-13 10:11 UTC|newest]
Thread overview: 23+ messages / expand[flat|nested] mbox.gz Atom feed top
2008-04-13 10:11 Jan Kiszka [this message]
2008-04-13 20:52 ` [Qemu-devel] [RFC][PATCH 4/4] Add support for Marvell 88w8618 / MusicPal malc
2008-04-14 6:59 ` [Qemu-devel] " Jan Kiszka
2008-04-14 13:12 ` Stuart Brady
2008-04-14 16:21 ` Jan Kiszka
2008-04-14 16:49 ` malc
2008-04-14 17:47 ` Jan Kiszka
2008-04-15 17:38 ` malc
2008-04-15 21:03 ` Jan Kiszka
2008-04-16 18:40 ` malc
2008-04-17 19:06 ` Jan Kiszka
2008-04-14 19:21 ` Jan Kiszka
2008-04-14 21:34 ` Jan Kiszka
2008-04-17 0:24 ` [Qemu-devel] " andrzej zaborowski
2008-04-17 0:46 ` andrzej zaborowski
2008-04-17 19:06 ` [Qemu-devel] " Jan Kiszka
2008-04-18 18:12 ` Jan Kiszka
2008-04-18 18:43 ` andrzej zaborowski
2008-04-19 19:01 ` Jan Kiszka
2008-04-20 4:11 ` andrzej zaborowski
2008-04-20 15:52 ` Jan Kiszka
2008-04-20 17:38 ` andrzej zaborowski
2008-04-20 16:32 ` [Qemu-devel] [PATCH] " Jan Kiszka
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=4801DC59.1010403@web.de \
--to=jan.kiszka@web.de \
--cc=qemu-devel@nongnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.