qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
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
+};

             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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).