* [Qemu-devel] [RFC][PATCH 4/4] Add support for Marvell 88w8618 / MusicPal
@ 2008-04-13 10:11 Jan Kiszka
2008-04-13 20:52 ` malc
` (3 more replies)
0 siblings, 4 replies; 23+ messages in thread
From: Jan Kiszka @ 2008-04-13 10:11 UTC (permalink / raw)
To: qemu-devel
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
+};
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [Qemu-devel] [RFC][PATCH 4/4] Add support for Marvell 88w8618 / MusicPal
2008-04-13 10:11 [Qemu-devel] [RFC][PATCH 4/4] Add support for Marvell 88w8618 / MusicPal Jan Kiszka
@ 2008-04-13 20:52 ` malc
2008-04-14 6:59 ` [Qemu-devel] " Jan Kiszka
2008-04-14 19:21 ` Jan Kiszka
` (2 subsequent siblings)
3 siblings, 1 reply; 23+ messages in thread
From: malc @ 2008-04-13 20:52 UTC (permalink / raw)
To: qemu-devel
On Sun, 13 Apr 2008, Jan Kiszka wrote:
> This is the board emulation for Freecom's MusicPal, featuring
> - rudimentary PIT and PIC
> - up to 2 UARTs
> - 88W8xx8 Ethernet controller
> - 88W8618 audio controller
> - Wolfson WM8750 mixer chip (volume control and mute only)
> - 128?64 display with brightness control
> - all input buttons
>
[..snip..]
> +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);
Zero is not correct "mute" value for signed formats.
> + 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;
> + }
On the surface it's sounds like endianess problems are ahead.
> + 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;
> + }
> +}
There's actually a generic framework for volume manipulation in QEMUs
audio, unfortunatelly it's unconditionally disabled (define NOVOL in
audio/mixeng.c) for the reasons i can't recall now.
Also adlib emulation is only conditionally available due to
usage of floating point arithmetics, maybe rules have changed now
though, but "fixing" this particular issue in this particular case
seems easy enough.
> +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;
Here two clocks are being used for things - the audio one which influences
`free' paramter and rt_clock, even if there are reasons why free could not
be used choice of rt_clock over vm_clock seems wrong.
> + 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);
While might be perfectly fine the above checking when to fire IRQ
looks suspicious.
[..snip..]
> +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};
Endianess set to 0 while the actual volume manipulations imply host byte
order..
> + 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);
[..snip..]
--
mailto:av1474@comtv.ru
^ permalink raw reply [flat|nested] 23+ messages in thread
* [Qemu-devel] Re: [RFC][PATCH 4/4] Add support for Marvell 88w8618 / MusicPal
2008-04-13 20:52 ` malc
@ 2008-04-14 6:59 ` Jan Kiszka
2008-04-14 13:12 ` Stuart Brady
2008-04-14 16:49 ` malc
0 siblings, 2 replies; 23+ messages in thread
From: Jan Kiszka @ 2008-04-14 6:59 UTC (permalink / raw)
To: qemu-devel
[-- Attachment #1: Type: text/plain, Size: 5690 bytes --]
malc wrote:
> On Sun, 13 Apr 2008, Jan Kiszka wrote:
>
>> This is the board emulation for Freecom's MusicPal, featuring
>> - rudimentary PIT and PIC
>> - up to 2 UARTs
>> - 88W8xx8 Ethernet controller
>> - 88W8618 audio controller
>> - Wolfson WM8750 mixer chip (volume control and mute only)
>> - 128?64 display with brightness control
>> - all input buttons
>>
> [..snip..]
>
>> +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);
>
> Zero is not correct "mute" value for signed formats.
Hmm, maybe it's still too early for me - but what else??
>
>> + 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;
>> + }
>
> On the surface it's sounds like endianess problems are ahead.
Oh, yes.
>
>> + 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;
>> + }
>> +}
>
> There's actually a generic framework for volume manipulation in QEMUs
> audio, unfortunatelly it's unconditionally disabled (define NOVOL in
> audio/mixeng.c) for the reasons i can't recall now.
Yeah, looked around a bit before hacking those loops but indeed found
nothing ready to use.
>
> Also adlib emulation is only conditionally available due to
> usage of floating point arithmetics, maybe rules have changed now
> though, but "fixing" this particular issue in this particular case
> seems easy enough.
Sorry, didn't get what you mean here.
>
>> +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;
>
> Here two clocks are being used for things - the audio one which influences
> `free' paramter and rt_clock, even if there are reasons why free could not
> be used choice of rt_clock over vm_clock seems wrong.
Yes, using the monotonic vm_clock is better. Will change.
Starring at the condition above, I realized that it is also bogus, in
several ways. It should still cause far more interrupts than on the real
device...
>
>> + 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);
>
> While might be perfectly fine the above checking when to fire IRQ
> looks suspicious.
Both status and irq_enable use the same bit semantics, so playing with
bit operations is indeed OK here.
>
> [..snip..]
>
>> +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};
>
> Endianess set to 0 while the actual volume manipulations imply host byte
> order..
Yes, will "officially" switch to host order.
>
>> + 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);
>
> [..snip..]
>
Thanks a lot for your review!
Jan
[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 254 bytes --]
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [Qemu-devel] Re: [RFC][PATCH 4/4] Add support for Marvell 88w8618 / MusicPal
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
1 sibling, 1 reply; 23+ messages in thread
From: Stuart Brady @ 2008-04-14 13:12 UTC (permalink / raw)
To: qemu-devel
On Mon, Apr 14, 2008 at 08:59:30AM +0200, Jan Kiszka wrote:
> malc wrote:
> > Zero is not correct "mute" value for signed formats.
>
> Hmm, maybe it's still too early for me - but what else??
Well, zero is not correct for unsigned formats, but I cannot see any
such formats in use in your emulation...
BTW, there are quite a few magic constants that it would probably be
better to #define.
Cheers,
--
Stuart Brady
^ permalink raw reply [flat|nested] 23+ messages in thread
* [Qemu-devel] Re: [RFC][PATCH 4/4] Add support for Marvell 88w8618 / MusicPal
2008-04-14 13:12 ` Stuart Brady
@ 2008-04-14 16:21 ` Jan Kiszka
0 siblings, 0 replies; 23+ messages in thread
From: Jan Kiszka @ 2008-04-14 16:21 UTC (permalink / raw)
To: qemu-devel
[-- Attachment #1: Type: text/plain, Size: 599 bytes --]
Stuart Brady wrote:
> On Mon, Apr 14, 2008 at 08:59:30AM +0200, Jan Kiszka wrote:
>> malc wrote:
>>> Zero is not correct "mute" value for signed formats.
>> Hmm, maybe it's still too early for me - but what else??
>
> Well, zero is not correct for unsigned formats, but I cannot see any
> such formats in use in your emulation...
/me too.
> BTW, there are quite a few magic constants that it would probably be
> better to #define.
I know. Comes a bit from the initial approach: "Hey, let's see how far
this hack will get."
Will try to fix those spots where possible.
Jan
[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 254 bytes --]
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [Qemu-devel] Re: [RFC][PATCH 4/4] Add support for Marvell 88w8618 / MusicPal
2008-04-14 6:59 ` [Qemu-devel] " Jan Kiszka
2008-04-14 13:12 ` Stuart Brady
@ 2008-04-14 16:49 ` malc
2008-04-14 17:47 ` Jan Kiszka
1 sibling, 1 reply; 23+ messages in thread
From: malc @ 2008-04-14 16:49 UTC (permalink / raw)
To: qemu-devel
On Mon, 14 Apr 2008, Jan Kiszka wrote:
> malc wrote:
>> On Sun, 13 Apr 2008, Jan Kiszka wrote:
[..snip..]
>>> +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);
>>
>> Zero is not correct "mute" value for signed formats.
>
> Hmm, maybe it's still too early for me - but what else??
Well.. 0x80 for S8 and 0x8000 for S16 (audio_pcm_info_clear_buf in
audio.c)
>>
>>> + 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;
>>> + }
>>
>> On the surface it's sounds like endianess problems are ahead.
>
> Oh, yes.
>
>>
>>> + 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;
>>> + }
>>> +}
>>
>> There's actually a generic framework for volume manipulation in QEMUs
>> audio, unfortunatelly it's unconditionally disabled (define NOVOL in
>> audio/mixeng.c) for the reasons i can't recall now.
>
> Yeah, looked around a bit before hacking those loops but indeed found
> nothing ready to use.
It's just a question of adding(and using) AUD_set_volume[_in/out],
which will set vol field of respective soft voice and removing
uncoditional #define NOVOL from mixeng.c.
>>
>> Also adlib emulation is only conditionally available due to
>> usage of floating point arithmetics, maybe rules have changed now
>> though, but "fixing" this particular issue in this particular case
>> seems easy enough.
>
> Sorry, didn't get what you mean here.
Adlib is not built by default, requires explicit --enable-adlib switch
to configure, because fmopl.c uses FPU and Fabrice didn't like the idea.
Fixing it here is just a matter of switching to fixed point.
Then again if volume manipulation in main audio proper are brought up
to date this all can be scrapped and it will also "magically" solve the
endianness issue.
>>
>>> +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;
>>
>> Here two clocks are being used for things - the audio one which influences
>> `free' paramter and rt_clock, even if there are reasons why free could not
>> be used choice of rt_clock over vm_clock seems wrong.
>
> Yes, using the monotonic vm_clock is better. Will change.
Why is it needed at all? `free' already supplies you with an audio clock
source.
[..snip..]
>
> Thanks a lot for your review!
You are welcome.
--
mailto:av1474@comtv.ru
^ permalink raw reply [flat|nested] 23+ messages in thread
* [Qemu-devel] Re: [RFC][PATCH 4/4] Add support for Marvell 88w8618 / MusicPal
2008-04-14 16:49 ` malc
@ 2008-04-14 17:47 ` Jan Kiszka
2008-04-15 17:38 ` malc
0 siblings, 1 reply; 23+ messages in thread
From: Jan Kiszka @ 2008-04-14 17:47 UTC (permalink / raw)
To: qemu-devel
[-- Attachment #1: Type: text/plain, Size: 4438 bytes --]
malc wrote:
> On Mon, 14 Apr 2008, Jan Kiszka wrote:
>
>> malc wrote:
>>> On Sun, 13 Apr 2008, Jan Kiszka wrote:
>
> [..snip..]
>
>>>> +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);
>>>
>>> Zero is not correct "mute" value for signed formats.
>>
>> Hmm, maybe it's still too early for me - but what else??
>
> Well.. 0x80 for S8 and 0x8000 for S16 (audio_pcm_info_clear_buf in
> audio.c)
void audio_pcm_info_clear_buf (struct audio_pcm_info *info, ...
{
...
if (info->sign) {
memset (buf, 0x00, len << info->shift);
}
>>>
>>>> + 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;
>>>> + }
>>>
>>> On the surface it's sounds like endianess problems are ahead.
>>
>> Oh, yes.
>>
>>>
>>>> + 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;
>>>> + }
>>>> +}
>>>
>>> There's actually a generic framework for volume manipulation in QEMUs
>>> audio, unfortunatelly it's unconditionally disabled (define NOVOL in
>>> audio/mixeng.c) for the reasons i can't recall now.
>>
>> Yeah, looked around a bit before hacking those loops but indeed found
>> nothing ready to use.
>
> It's just a question of adding(and using) AUD_set_volume[_in/out],
> which will set vol field of respective soft voice and removing
> uncoditional #define NOVOL from mixeng.c.
Well, I was (and still am) deterred by the plain fact that there are
some dead AUD_set_volume spots in ac97.c. If it is that simple, why do
we still lack an implementation? My feeling is that you are far deeper
into this than I am at this point. So maybe you would like me to test
some AUD_set_volume-providing patch of yours? :->
>>>
>>> Also adlib emulation is only conditionally available due to
>>> usage of floating point arithmetics, maybe rules have changed now
>>> though, but "fixing" this particular issue in this particular case
>>> seems easy enough.
>>
>> Sorry, didn't get what you mean here.
>
> Adlib is not built by default, requires explicit --enable-adlib switch
> to configure, because fmopl.c uses FPU and Fabrice didn't like the idea.
> Fixing it here is just a matter of switching to fixed point.
>
> Then again if volume manipulation in main audio proper are brought up
> to date this all can be scrapped and it will also "magically" solve the
> endianness issue.
I do agree that such stuff should be solved generically, but for now
adding a simple le16_to_cpu does not compare to fixing QEMU's
soft-volume management for me (given limited time).
>>>
>>>> +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;
>>>
>>> Here two clocks are being used for things - the audio one which
>>> influences
>>> `free' paramter and rt_clock, even if there are reasons why free
>>> could not
>>> be used choice of rt_clock over vm_clock seems wrong.
>>
>> Yes, using the monotonic vm_clock is better. Will change.
>
> Why is it needed at all? `free' already supplies you with an audio clock
> source.
The callback mechanism fires far more often than the emulated hardware
expects (and can handle), I need throttling.
The sad truth is that my original version was still off my an order of
magnitude, causing overload for the guest while decoding obviously more
complex 128kbit-WMA streams. Now it runs smoothly. :)
Maybe there is a way to customize the callback rate, I haven't looked
that close, but I'm open for suggestions.
Thanks again,
Jan
[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 254 bytes --]
^ permalink raw reply [flat|nested] 23+ messages in thread
* [Qemu-devel] Re: [RFC][PATCH 4/4] Add support for Marvell 88w8618 / MusicPal
2008-04-13 10:11 [Qemu-devel] [RFC][PATCH 4/4] Add support for Marvell 88w8618 / MusicPal Jan Kiszka
2008-04-13 20:52 ` malc
@ 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-20 16:32 ` [Qemu-devel] [PATCH] " Jan Kiszka
3 siblings, 1 reply; 23+ messages in thread
From: Jan Kiszka @ 2008-04-14 19:21 UTC (permalink / raw)
To: qemu-devel
[-- Attachment #1: Type: text/plain, Size: 34130 bytes --]
Jan Kiszka wrote:
> This is the board emulation for Freecom's MusicPal, featuring
> - rudimentary PIT and PIC
> - up to 2 UARTs
> - 88W8xx8 Ethernet controller
> - 88W8618 audio controller
> - Wolfson WM8750 mixer chip (volume control and mute only)
> - 128×64 display with brightness control
> - all input buttons
>
> Using up to 32 MB flash, I hit a limit /wrt phys_ram_size. I worked
> around this for now by extending MAX_BIOS_SIZE to 32 MB, surely not a
> nice solution.
>
> The emulation suffers a bit from limited time, specifically to implement
> details of hardware setup/shutdown. Another problem - not only for the
> emulation, but also for native Linux support - is that Marvell yet hides
> their specs from the community, and existing Linux code [1] is not that
> clear in every required detail. In case the specs remain Marvell secret,
> maybe the emulation can help a bit to reverse-engineer the remaining
> bits (I'm thinking of the binary-blobbed WLAN driver e.g.).
>
> [1] http://www.musicpal.info/gpl.htm
Here is an update, fixing specifically the TX IRQ rate of the audio
emulation (which now allows me to play WMA 128kbit streams like "delta
radio" smoothly) and incorporating many of the comments the reviewers
provided (but not all: still home-brewed volume control, still many
unnamed magics).
Signed-off-by: Jan Kiszka <jan.kiszka@web.de>
---
Makefile.target | 1
hw/boards.h | 3
hw/musicpal.c | 1286 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
sysemu.h | 2
vl.c | 1
5 files changed, 1292 insertions(+), 1 deletion(-)
Index: b/Makefile.target
===================================================================
--- a/Makefile.target
+++ b/Makefile.target
@@ -612,6 +612,7 @@ OBJS+= spitz.o ide.o serial.o nand.o ecc
OBJS+= omap1.o omap_lcdc.o omap_dma.o omap_clk.o omap_mmc.o omap_i2c.o
OBJS+= palm.o tsc210x.o
OBJS+= mst_fpga.o mainstone.o
+OBJS+= musicpal.o pflash_cfi02.o
CPPFLAGS += -DHAS_AUDIO
endif
ifeq ($(TARGET_BASE_ARCH), sh4)
Index: b/hw/boards.h
===================================================================
--- a/hw/boards.h
+++ b/hw/boards.h
@@ -101,4 +101,7 @@ extern QEMUMachine dummy_m68k_machine;
/* mainstone.c */
extern QEMUMachine mainstone2_machine;
+/* musicpal.c */
+extern QEMUMachine musicpal_machine;
+
#endif
Index: b/vl.c
===================================================================
--- a/vl.c
+++ b/vl.c
@@ -8056,6 +8056,7 @@ static void register_machines(void)
qemu_register_machine(&connex_machine);
qemu_register_machine(&verdex_machine);
qemu_register_machine(&mainstone2_machine);
+ qemu_register_machine(&musicpal_machine);
#elif defined(TARGET_SH4)
qemu_register_machine(&shix_machine);
qemu_register_machine(&r2d_machine);
Index: b/sysemu.h
===================================================================
--- a/sysemu.h
+++ b/sysemu.h
@@ -109,7 +109,7 @@ extern unsigned int nb_prom_envs;
#endif
/* XXX: make it dynamic */
-#define MAX_BIOS_SIZE (4 * 1024 * 1024)
+#define MAX_BIOS_SIZE (32 * 1024 * 1024)
#if defined (TARGET_PPC)
#define BIOS_SIZE (1024 * 1024)
#elif defined (TARGET_SPARC64)
Index: b/hw/musicpal.c
===================================================================
--- /dev/null
+++ b/hw/musicpal.c
@@ -0,0 +1,1286 @@
+/*
+ * Marvell MV88W8618 / Freecom MusicPal emulation.
+ *
+ * Copyright (c) 2008 Jan Kiszka
+ *
+ * This code is licenced under the GNU GPL v2.
+ */
+
+#include <math.h>
+
+#include "hw.h"
+#include "arm-misc.h"
+#include "devices.h"
+#include "net.h"
+#include "sysemu.h"
+#include "boards.h"
+#include "pc.h"
+#include "qemu-timer.h"
+#include "block.h"
+#include "flash.h"
+#include "console.h"
+#include "audio/audio.h"
+#include "i2c.h"
+
+#define MV_SRAM_BASE 0xC0000000
+#define MV_SRAM_SIZE 0x20000
+
+static uint32_t gpio_in_state = 0xffffffff;
+static uint32_t gpio_out_state;
+static ram_addr_t sram_off;
+
+
+static void *target2host_addr(uint32_t addr)
+{
+ if (addr < MV_SRAM_BASE) {
+ if (addr >= ram_size)
+ return NULL;
+ return (void *)(phys_ram_base + addr);
+ } else {
+ if (addr >= MV_SRAM_BASE + MV_SRAM_SIZE)
+ return NULL;
+ return (void *)(phys_ram_base + sram_off + addr - MV_SRAM_BASE);
+ }
+}
+
+static uint32_t host2target_addr(void *addr)
+{
+ if (addr < ((void *)phys_ram_base) + sram_off)
+ return (unsigned long)addr - (unsigned long)phys_ram_base;
+ else
+ return (unsigned long)addr - (unsigned long)phys_ram_base -
+ sram_off + MV_SRAM_BASE;
+}
+
+
+#ifdef HAS_AUDIO
+
+/* Register offsets */
+#define MV_AUDIO_PLAYBACK_MODE 0x00
+#define MV_AUDIO_CLOCK_DIV 0x18
+#define MV_AUDIO_IRQ_STATUS 0x20
+#define MV_AUDIO_IRQ_ENABLE 0x24
+#define MV_AUDIO_TX_START_LO 0x28
+#define MV_AUDIO_TX_THRESHOLD 0x2C
+#define MV_AUDIO_TX_START_HI 0x40
+
+/* Status register and IRQ enable bits */
+#define MV_AUDIO_TX_HALF (1 << 6)
+#define MV_AUDIO_TX_FULL (1 << 7)
+
+/* Playback mode bits */
+#define MV_AUDIO_16BIT_SAMPLE (1 << 0)
+#define MV_AUDIO_PLAYBACK_EN (1 << 7)
+#define MV_AUDIO_CLOCK_24MHZ (1 << 9)
+
+const char audio_name[] = "mv88w8618";
+
+typedef struct mv88w8618_audio_state {
+ uint32_t base;
+ qemu_irq irq;
+ uint32_t playback_mode;
+ uint32_t status;
+ uint32_t irq_enable;
+ unsigned long phys_buf;
+ void *target_buffer;
+ uint8_t mixer_buffer[4096];
+ int volume;
+ int mute;
+ unsigned int threshold;
+ unsigned int play_pos;
+ uint32_t clock_div;
+ int data_rate;
+ QEMUSoundCard card;
+ SWVoiceOut *voice;
+ int64_t last_callback;
+} mv88w8618_audio_state;
+
+static mv88w8618_audio_state *audio_state;
+
+static void audio_fill_mixer_buffer(mv88w8618_audio_state *s, unsigned int length)
+{
+ unsigned int pos;
+ double val;
+
+ if (s->mute) {
+ memset(s->mixer_buffer, 0, length);
+ return;
+ }
+
+ if (s->playback_mode & 1)
+ for (pos = 0; pos < length; pos += 2) {
+ val = *(int16_t *)(s->target_buffer + s->play_pos + pos);
+ val = le16_to_cpu(val) * pow(10.0, s->volume/20.0);
+ *(int16_t *)(s->mixer_buffer + pos) = val;
+ }
+ else
+ for (pos = 0; pos < length; pos += 1) {
+ val = *(int8_t *)(s->target_buffer + s->play_pos + pos);
+ val = val * pow(10.0, s->volume/20.0);
+ *(int8_t *)(s->mixer_buffer + pos) = val;
+ }
+}
+
+static void audio_callback(void *opaque, int free)
+{
+ mv88w8618_audio_state *s = opaque;
+ uint32_t old_status = s->status;
+ int64_t now = qemu_get_clock(vm_clock);
+ unsigned int n;
+
+ if (now - s->last_callback <
+ (1000000000 / s->data_rate) * s->threshold/4)
+ return;
+ s->last_callback = now;
+
+ while (free > 0) {
+ n = audio_MIN(s->threshold - s->play_pos, (unsigned int)free);
+ audio_fill_mixer_buffer(s, n);
+ n = AUD_write(s->voice, s->mixer_buffer, n);
+ if (!n)
+ break;
+
+ s->play_pos += n;
+ free -= n;
+
+ if (s->play_pos >= s->threshold/2)
+ s->status |= MV_AUDIO_TX_HALF;
+ if (s->play_pos == s->threshold) {
+ s->status |= MV_AUDIO_TX_FULL;
+ s->play_pos = 0;
+ break;
+ }
+ }
+ if ((s->status ^ old_status) & s->irq_enable)
+ qemu_irq_raise(s->irq);
+}
+
+static void mv88w8618_audio_set_mute(mv88w8618_audio_state *s, int mute)
+{
+ s->mute = mute;
+}
+
+static void mv88w8618_audio_set_volume(mv88w8618_audio_state *s, int volume)
+{
+ s->volume = volume;
+}
+
+static uint32_t mv88w8618_audio_read(void *opaque, target_phys_addr_t offset)
+{
+ mv88w8618_audio_state *s = opaque;
+
+ offset -= s->base;
+ switch (offset) {
+ case MV_AUDIO_PLAYBACK_MODE:
+ return s->playback_mode;
+
+ case MV_AUDIO_IRQ_STATUS:
+ return s->status;
+
+ case MV_AUDIO_IRQ_ENABLE:
+ return s->irq_enable;
+
+ default:
+// printf("addr 0x%08lx\n", offset);
+ return 0;
+ }
+}
+
+static void mv88w8618_audio_write(void *opaque, target_phys_addr_t offset,
+ uint32_t value)
+{
+ mv88w8618_audio_state *s = opaque;
+
+ offset -= s->base;
+ switch (offset) {
+ case MV_AUDIO_PLAYBACK_MODE:
+ s->playback_mode = value;
+ if (value & MV_AUDIO_PLAYBACK_EN) {
+ audsettings_t as = {0, 2, 0, AUDIO_HOST_ENDIANNESS};
+
+ if (value & MV_AUDIO_CLOCK_24MHZ)
+ as.freq = (24576000 / 64) / (s->clock_div + 1);
+ else
+ as.freq = (11289600 / 64) / (s->clock_div + 1);
+ if (value & MV_AUDIO_16BIT_SAMPLE) {
+ as.fmt = AUD_FMT_S16;
+ s->data_rate = as.freq * 2;
+ } else {
+ as.fmt = AUD_FMT_S8;
+ s->data_rate = as.freq;
+ }
+
+ s->voice = AUD_open_out(&s->card, s->voice, audio_name,
+ s, audio_callback, &as);
+ if (s->voice)
+ AUD_set_active_out(s->voice, 1);
+ else
+ AUD_log(audio_name, "Could not open voice\n");
+ s->last_callback = 0;
+ s->status = 0;
+ } else if (s->voice)
+ AUD_set_active_out(s->voice, 0);
+ break;
+
+ case MV_AUDIO_CLOCK_DIV:
+ s->clock_div = (value >> 8) & 0xFF;
+ break;
+
+ case MV_AUDIO_IRQ_STATUS:
+ s->status &= ~value;
+ break;
+
+ case MV_AUDIO_IRQ_ENABLE:
+ s->irq_enable = value;
+ if (s->status & s->irq_enable)
+ qemu_irq_raise(s->irq);
+ break;
+
+ case MV_AUDIO_TX_START_LO:
+ s->phys_buf = (s->phys_buf & 0xFFFF0000) | (value & 0xFFFF);
+ s->target_buffer = target2host_addr(s->phys_buf);
+ s->play_pos = 0;
+ break;
+
+ case MV_AUDIO_TX_THRESHOLD:
+ s->threshold = (value + 1) * 4;
+ break;
+
+ case MV_AUDIO_TX_START_HI:
+ s->phys_buf = (s->phys_buf & 0xFFFF) | (value << 16);
+ s->target_buffer = target2host_addr(s->phys_buf);
+ s->play_pos = 0;
+ break;
+ }
+
+// printf("addr 0x%08lx, value 0x%08x\n", offset, value);
+}
+
+static CPUReadMemoryFunc *mv88w8618_audio_readfn[] = {
+ mv88w8618_audio_read,
+ mv88w8618_audio_read,
+ mv88w8618_audio_read
+};
+
+static CPUWriteMemoryFunc *mv88w8618_audio_writefn[] = {
+ mv88w8618_audio_write,
+ mv88w8618_audio_write,
+ mv88w8618_audio_write
+};
+
+static mv88w8618_audio_state *mv88w8618_audio_init(uint32_t base, qemu_irq irq)
+{
+ AudioState *audio;
+ mv88w8618_audio_state *s;
+ int iomemtype;
+
+ s = qemu_mallocz(sizeof(mv88w8618_audio_state));
+ if (!s)
+ return NULL;
+ s->base = base;
+ s->irq = irq;
+
+ audio = AUD_init();
+ if (!audio) {
+ AUD_log(audio_name, "No audio state\n");
+ return NULL;
+ }
+ AUD_register_card(audio, audio_name, &s->card);
+
+ iomemtype = cpu_register_io_memory(0, mv88w8618_audio_readfn,
+ mv88w8618_audio_writefn, s);
+ cpu_register_physical_memory(base, 0x00001000, iomemtype);
+
+ return s;
+}
+#else /* !HAS_AUDIO */
+static mv88w8618_audio_state *mv88w8618_audio_init(uint32_t base, qemu_irq irq)
+{
+ return NULL;
+}
+#endif /* !HAS_AUDIO */
+
+
+typedef struct mv88w8618_tx_desc {
+ uint32_t cmdstat;
+ uint16_t res;
+ uint16_t bytes;
+ uint32_t buffer;
+ uint32_t next;
+} mv88w8618_tx_desc;
+
+typedef struct mv88w8618_rx_desc {
+ uint32_t cmdstat;
+ uint16_t bytes;
+ uint16_t buffer_size;
+ uint32_t buffer;
+ uint32_t next;
+} mv88w8618_rx_desc;
+
+typedef struct mv88w8618_eth_state {
+ uint32_t base;
+ qemu_irq irq;
+ uint32_t icr;
+ uint32_t imr;
+ int vlan_header;
+ mv88w8618_tx_desc *tx_queue[2];
+ mv88w8618_rx_desc *rx_queue[4];
+ mv88w8618_rx_desc *frx_queue[4];
+ mv88w8618_rx_desc *cur_rx[4];
+ VLANClientState *vc;
+} mv88w8618_eth_state;
+
+static int eth_can_receive(void *opaque)
+{
+ return 1;
+}
+
+static void eth_receive(void *opaque, const uint8_t *buf, int size)
+{
+ mv88w8618_eth_state *s = opaque;
+ mv88w8618_rx_desc *desc;
+ int i;
+
+// printf("Receiving %d bytes\n", size);
+ for (i = 0; i < 4; i++) {
+ desc = s->cur_rx[i];
+ if (!desc)
+ continue;
+ do {
+ if (le32_to_cpu(desc->cmdstat) & (1 << 31) &&
+ le16_to_cpu(desc->buffer_size) >= size) {
+ memcpy(target2host_addr(le32_to_cpu(desc->buffer) + s->vlan_header),
+ buf, size);
+ desc->bytes = cpu_to_le16(size + s->vlan_header);
+ desc->cmdstat = cpu_to_le32(le32_to_cpu(desc->cmdstat) & ~(1 << 31));
+ s->cur_rx[i] = target2host_addr(le32_to_cpu(desc->next));
+
+ s->icr |= 1;
+ if (s->icr & s->imr)
+ qemu_irq_raise(s->irq);
+ return;
+ }
+ desc = target2host_addr(le32_to_cpu(desc->next));
+ } while (desc != s->rx_queue[i]);
+ }
+ printf(">>>Dropping packet!\n");
+}
+
+static void eth_send(mv88w8618_eth_state *s, int queue_index)
+{
+ mv88w8618_tx_desc *desc = s->tx_queue[queue_index];
+
+ do {
+ if (le32_to_cpu(desc->cmdstat) & (1 << 31)) {
+// printf("Sending %d bytes\n", le16_to_cpu(desc->bytes));
+ qemu_send_packet(s->vc, target2host_addr(le32_to_cpu(desc->buffer)),
+ le16_to_cpu(desc->bytes));
+ desc->cmdstat = cpu_to_le32(le32_to_cpu(desc->cmdstat) & ~(1 << 31));
+ s->icr |= 1 << (3 - queue_index);
+ }
+ desc = target2host_addr(le32_to_cpu(desc->next));
+ } while (desc != s->tx_queue[queue_index]);
+}
+
+static uint32_t mv88w8618_eth_read(void *opaque, target_phys_addr_t offset)
+{
+ mv88w8618_eth_state *s = opaque;
+
+ offset -= s->base;
+ switch (offset) {
+ case 0x10: /* smir - always ready */
+ return 1 << 27 | 0x004;
+
+ case 0x450: /* icr */
+ return s->icr;
+
+ case 0x458: /* imr */
+ return s->imr;
+
+ case 0x480 ... 0x48c: /* frdp[0..3] */
+ return host2target_addr(s->frx_queue[(offset - 0x480)/4]);
+
+ case 0x4a0 ... 0x4ac: /* crdp[0..3] */
+ return host2target_addr(s->rx_queue[(offset - 0x4a0)/4]);
+
+ case 0x4e0 ... 0x4e4: /* ctdp[0..2] */
+ return host2target_addr(s->tx_queue[(offset - 0x4e0)/4]);
+
+ default:
+ return 0;
+ }
+}
+
+static void mv88w8618_eth_write(void *opaque, target_phys_addr_t offset,
+ uint32_t value)
+{
+ mv88w8618_eth_state *s = opaque;
+
+ offset -= s->base;
+ switch (offset) {
+ case 0x408: /* pcxr */
+ s->vlan_header = (value >> 27) & 2;
+ break;
+
+ case 0x448: /* sdcmr */
+ if (value & (1 << 23))
+ eth_send(s, 1);
+ if (value & (1 << 24))
+ eth_send(s, 0);
+ if (value & (3 << 23) && s->icr & s->imr)
+ qemu_irq_raise(s->irq);
+ break;
+
+ case 0x450: /* icr */
+ s->icr &= value;
+ break;
+
+ case 0x458: /* imr */
+ s->imr = value;
+ if (s->icr & s->imr)
+ qemu_irq_raise(s->irq);
+ break;
+
+ case 0x480 ... 0x48c: /* frdp[0..3] */
+ s->frx_queue[(offset - 0x480)/4] = target2host_addr(value);
+ break;
+
+ case 0x4a0 ... 0x4ac: /* crdp[0..3] */
+ s->rx_queue[(offset - 0x4a0)/4] = s->cur_rx[(offset - 0x4a0)/4] =
+ target2host_addr(value);
+ break;
+
+ case 0x4e0 ... 0x4e4: /* ctdp[0..2] */
+ s->tx_queue[(offset - 0x4e0)/4] = target2host_addr(value);
+ break;
+ }
+}
+
+static CPUReadMemoryFunc *mv88w8618_eth_readfn[] = {
+ mv88w8618_eth_read,
+ mv88w8618_eth_read,
+ mv88w8618_eth_read
+};
+
+static CPUWriteMemoryFunc *mv88w8618_eth_writefn[] = {
+ mv88w8618_eth_write,
+ mv88w8618_eth_write,
+ mv88w8618_eth_write
+};
+
+static void mv88w8618_eth_init(NICInfo *nd, uint32_t base, qemu_irq irq)
+{
+ mv88w8618_eth_state *s;
+ int iomemtype;
+
+ s = qemu_mallocz(sizeof(mv88w8618_eth_state));
+ if (!s)
+ return;
+ s->base = base;
+ s->irq = irq;
+ s->vc = qemu_new_vlan_client(nd->vlan, eth_receive, eth_can_receive, s);
+ iomemtype = cpu_register_io_memory(0, mv88w8618_eth_readfn,
+ mv88w8618_eth_writefn, s);
+ cpu_register_physical_memory(base, 0x00001000, iomemtype);
+}
+
+
+typedef struct musicpal_lcd_state {
+ uint32_t base;
+ enum { NONE, DATA, CMD } mode;
+ int page;
+ int page_off;
+ DisplayState *ds;
+ uint8_t video_ram[128*64/8];
+} musicpal_lcd_state;
+
+static uint32_t lcd_brightness;
+
+static uint8_t scale_lcd_color(uint8_t col)
+{
+ int tmp = col;
+
+ switch (lcd_brightness) {
+ case 0x00070000: /* 0 */
+ return 0;
+
+ case 0x00000002: /* 1 */
+ return (tmp * 1) / 7;
+
+ case 0x00010002: /* 2 */
+ return (tmp * 2) / 7;
+
+ case 0x00000004: /* 3 */
+ return (tmp * 3) / 7;
+
+ case 0x00060001: /* 4 */
+ return (tmp * 4) / 7;
+
+ case 0x00050002: /* 5 */
+ return (tmp * 5) / 7;
+
+ case 0x00030004: /* 6 */
+ return (tmp * 6) / 7;
+
+ case 0x00040003: /* 7 */
+ default:
+ return col;
+ }
+}
+
+static void set_lcd_pixel(musicpal_lcd_state *s, int x, int y, int col)
+{
+ int dx, dy;
+
+ for (dy = 0; dy < 3; dy++)
+ for (dx = 0; dx < 3; dx++) {
+ s->ds->data[(x*3 + dx + (y*3 + dy) * 128*3) * 4 + 0] =
+ scale_lcd_color(col);
+ s->ds->data[(x*3 + dx + (y*3 + dy) * 128*3) * 4 + 1] =
+ scale_lcd_color(col >> 8);
+ s->ds->data[(x*3 + dx + (y*3 + dy) * 128*3) * 4 + 2] =
+ scale_lcd_color(col >> 16);
+ }
+}
+
+static void lcd_refresh(void *opaque)
+{
+ musicpal_lcd_state *s = opaque;
+ int x, y;
+
+ for (x = 0; x < 128; x++)
+ for (y = 0; y < 64; y++)
+ if (s->video_ram[x + (y/8)*128] & (1 << (y % 8)))
+ set_lcd_pixel(s, x, y, 0x00e0e0ff);
+ else
+ set_lcd_pixel(s, x, y, 0);
+
+ dpy_update(s->ds, 0, 0, 128*3, 64*3);
+}
+
+static uint32_t musicpal_lcd_read(void *opaque, target_phys_addr_t offset)
+{
+ musicpal_lcd_state *s = opaque;
+
+ offset -= s->base;
+ switch (offset) {
+ case 0x180:
+ return 0x00400000;
+
+ default:
+ return 0;
+ }
+}
+
+static void musicpal_lcd_write(void *opaque, target_phys_addr_t offset,
+ uint32_t value)
+{
+ musicpal_lcd_state *s = opaque;
+
+ offset -= s->base;
+ switch (offset) {
+ case 0x1ac:
+ if (value == 0x00100011)
+ s->mode = DATA;
+ else if (value == 0x00104011)
+ s->mode = CMD;
+ else
+ s->mode = NONE;
+ break;
+
+ case 0x1bc:
+ if (value >= 0xB0 && value <= 0xB7) {
+ s->page = value - 0xB0;
+ s->page_off = 0;
+ }
+ break;
+
+ case 0x1c0:
+ if (s->mode == CMD) {
+ if (value >= 0xB0 && value <= 0xB7) {
+ s->page = value - 0xB0;
+ s->page_off = 0;
+ }
+ } else if (s->mode == DATA) {
+ s->video_ram[s->page*128 + s->page_off] = value;
+ s->page_off++;
+ }
+ break;
+ }
+}
+
+static CPUReadMemoryFunc *musicpal_lcd_readfn[] = {
+ musicpal_lcd_read,
+ musicpal_lcd_read,
+ musicpal_lcd_read
+};
+
+static CPUWriteMemoryFunc *musicpal_lcd_writefn[] = {
+ musicpal_lcd_write,
+ musicpal_lcd_write,
+ musicpal_lcd_write
+};
+
+static void musicpal_lcd_init(DisplayState *ds, uint32_t base)
+{
+ musicpal_lcd_state *s;
+ int iomemtype;
+
+ s = qemu_mallocz(sizeof(musicpal_lcd_state));
+ if (!s)
+ return;
+ s->base = base;
+ s->ds = ds;
+ iomemtype = cpu_register_io_memory(0, musicpal_lcd_readfn,
+ musicpal_lcd_writefn, s);
+ cpu_register_physical_memory(base, 0x000001d0, iomemtype);
+
+ graphic_console_init(ds, lcd_refresh, NULL, NULL, NULL, s);
+ dpy_resize(ds, 128*3, 64*3);
+}
+
+
+typedef struct mv88w8618_pic_state
+{
+ uint32_t base;
+ uint32_t level;
+ uint32_t enabled;
+ qemu_irq parent_irq;
+} mv88w8618_pic_state;
+
+
+static void mv88w8618_pic_update(mv88w8618_pic_state *s)
+{
+ qemu_set_irq(s->parent_irq, (s->level & s->enabled));
+ qemu_set_irq(s->parent_irq, (s->level & s->enabled));
+}
+
+static void mv88w8618_pic_set_irq(void *opaque, int irq, int level)
+{
+ mv88w8618_pic_state *s = opaque;
+
+ if (level)
+ s->level |= 1 << irq;
+ else
+ s->level &= ~(1 << irq);
+ mv88w8618_pic_update(s);
+}
+
+static uint32_t mv88w8618_pic_read(void *opaque, target_phys_addr_t offset)
+{
+ mv88w8618_pic_state *s = opaque;
+
+ offset -= s->base;
+ switch (offset) {
+ case 0x00:
+ return s->level;
+
+ default:
+ return 0;
+ }
+}
+
+static void mv88w8618_pic_write(void *opaque, target_phys_addr_t offset,
+ uint32_t value)
+{
+ mv88w8618_pic_state *s = opaque;
+
+ offset -= s->base;
+ switch (offset) {
+ case 0x08:
+ s->enabled |= value;
+ break;
+
+ case 0x0C:
+ s->enabled &= ~value;
+ s->level &= ~value;
+ break;
+ }
+ mv88w8618_pic_update(s);
+}
+
+static CPUReadMemoryFunc *mv88w8618_pic_readfn[] = {
+ mv88w8618_pic_read,
+ mv88w8618_pic_read,
+ mv88w8618_pic_read
+};
+
+static CPUWriteMemoryFunc *mv88w8618_pic_writefn[] = {
+ mv88w8618_pic_write,
+ mv88w8618_pic_write,
+ mv88w8618_pic_write
+};
+
+static qemu_irq *mv88w8618_pic_init(uint32_t base, qemu_irq parent_irq)
+{
+ mv88w8618_pic_state *s;
+ int iomemtype;
+ qemu_irq *qi;
+
+ s = qemu_mallocz(sizeof(mv88w8618_pic_state));
+ if (!s)
+ return NULL;
+ qi = qemu_allocate_irqs(mv88w8618_pic_set_irq, s, 32);
+ s->base = base;
+ s->parent_irq = parent_irq;
+ iomemtype = cpu_register_io_memory(0, mv88w8618_pic_readfn,
+ mv88w8618_pic_writefn, s);
+ cpu_register_physical_memory(base, 0x00000020, iomemtype);
+
+ return qi;
+}
+
+typedef struct mv88w8618_timer_state {
+ ptimer_state *timer;
+ uint32_t limit;
+ int freq;
+ qemu_irq irq;
+} mv88w8618_timer_state;
+
+typedef struct mv88w8618_pit_state {
+ void *timer[4];
+ uint32_t control;
+ uint32_t base;
+} mv88w8618_pit_state;
+
+static void mv88w8618_timer_tick(void *opaque)
+{
+ mv88w8618_timer_state *s = opaque;
+
+ qemu_irq_raise(s->irq);
+}
+
+static void *mv88w8618_timer_init(uint32_t freq, qemu_irq irq)
+{
+ mv88w8618_timer_state *s;
+ QEMUBH *bh;
+
+ s = qemu_mallocz(sizeof(mv88w8618_timer_state));
+ s->irq = irq;
+ s->freq = freq;
+
+ bh = qemu_bh_new(mv88w8618_timer_tick, s);
+ s->timer = ptimer_init(bh);
+
+ return s;
+}
+
+static uint32_t mv88w8618_pit_read(void *opaque, target_phys_addr_t offset)
+{
+ mv88w8618_pit_state *s = opaque;
+ mv88w8618_timer_state *t;
+
+ offset -= s->base;
+ switch (offset) {
+ case 0x14 ... 0x20:
+ t = s->timer[(offset-0x14) >> 2];
+ return ptimer_get_count(t->timer);
+
+ default:
+ return 0;
+ }
+}
+
+static void mv88w8618_pit_write(void *opaque, target_phys_addr_t offset,
+ uint32_t value)
+{
+ mv88w8618_pit_state *s = opaque;
+ mv88w8618_timer_state *t;
+ int i;
+
+ offset -= s->base;
+ switch (offset) {
+ case 0x00 ... 0x0c:
+ t = s->timer[offset >> 2];
+ t->limit = value;
+ ptimer_set_limit(t->timer, t->limit, 1);
+ break;
+
+ case 0x10:
+ for (i = 0; i < 4; i++) {
+ if (value & 0xf) {
+ t = s->timer[i];
+ ptimer_set_limit(t->timer, t->limit, 0);
+ ptimer_set_freq(t->timer, t->freq);
+ ptimer_run(t->timer, 0);
+ }
+ value >>= 4;
+ }
+ break;
+ }
+}
+
+static CPUReadMemoryFunc *mv88w8618_pit_readfn[] = {
+ mv88w8618_pit_read,
+ mv88w8618_pit_read,
+ mv88w8618_pit_read
+};
+
+static CPUWriteMemoryFunc *mv88w8618_pit_writefn[] = {
+ mv88w8618_pit_write,
+ mv88w8618_pit_write,
+ mv88w8618_pit_write
+};
+
+static void mv88w8618_pit_init(uint32_t base, qemu_irq *pic, int irq)
+{
+ int iomemtype;
+ mv88w8618_pit_state *s;
+
+ s = qemu_mallocz(sizeof(mv88w8618_pit_state));
+ if (!s)
+ return;
+
+ s->base = base;
+ s->timer[0] = mv88w8618_timer_init(1000000, pic[irq]);
+ s->timer[1] = mv88w8618_timer_init(1000000, pic[irq + 1]);
+ s->timer[2] = mv88w8618_timer_init(1000000, pic[irq + 2]);
+ s->timer[3] = mv88w8618_timer_init(1000000, pic[irq + 3]);
+
+ iomemtype = cpu_register_io_memory(0, mv88w8618_pit_readfn,
+ mv88w8618_pit_writefn, s);
+ cpu_register_physical_memory(base, 0x00000034, iomemtype);
+}
+
+
+typedef enum i2c_state {
+ STOPPED = 0,
+ INITIALIZING,
+ SENDING_BIT7,
+ SENDING_BIT6,
+ SENDING_BIT5,
+ SENDING_BIT4,
+ SENDING_BIT3,
+ SENDING_BIT2,
+ SENDING_BIT1,
+ SENDING_BIT0,
+ WAITING_FOR_ACK,
+ RECEIVING_BIT7,
+ RECEIVING_BIT6,
+ RECEIVING_BIT5,
+ RECEIVING_BIT4,
+ RECEIVING_BIT3,
+ RECEIVING_BIT2,
+ RECEIVING_BIT1,
+ RECEIVING_BIT0,
+ SENDING_ACK
+} i2c_state;
+
+typedef struct i2c_interface {
+ i2c_bus *bus;
+ i2c_state state;
+ int last_data;
+ int last_clock;
+ uint8_t buffer;
+ int current_addr;
+} i2c_interface;
+
+static i2c_interface *mixer_i2c;
+
+static void i2c_enter_stop(i2c_interface *i2c)
+{
+ if (i2c->current_addr >= 0)
+ i2c_end_transfer(i2c->bus);
+ i2c->current_addr = -1;
+ i2c->state = STOPPED;
+}
+
+static void i2c_state_update(i2c_interface *i2c, int data, int clock)
+{
+ switch (i2c->state) {
+ case STOPPED:
+ if (data == 0 && i2c->last_data == 1 && clock == 1)
+ i2c->state = INITIALIZING;
+ break;
+
+ case INITIALIZING:
+ if (clock == 0 && i2c->last_clock == 1 && data == 0)
+ i2c->state = SENDING_BIT7;
+ else
+ i2c_enter_stop(i2c);
+ break;
+
+ case SENDING_BIT7 ... SENDING_BIT0:
+ if (clock == 0 && i2c->last_clock == 1) {
+ i2c->buffer = (i2c->buffer << 1) | data;
+ i2c->state++; /* will end up in WAITING_FOR_ACK */
+ } else if (data == 1 && i2c->last_data == 0 && clock == 1)
+ i2c_enter_stop(i2c);
+ break;
+
+ case WAITING_FOR_ACK:
+ if (clock == 0 && i2c->last_clock == 1) {
+ if (i2c->current_addr < 0) {
+ i2c->current_addr = i2c->buffer;
+ i2c_start_transfer(i2c->bus,
+ i2c->current_addr & 0xfe,
+ i2c->buffer & 1);
+ } else
+ i2c_send(i2c->bus, i2c->buffer);
+ if (i2c->current_addr & 1) {
+ i2c->state = RECEIVING_BIT7;
+ i2c->buffer = i2c_recv(i2c->bus);
+ } else
+ i2c->state = SENDING_BIT7;
+ } else if (data == 1 && i2c->last_data == 0 && clock == 1)
+ i2c_enter_stop(i2c);
+ break;
+
+ case RECEIVING_BIT7 ... RECEIVING_BIT0:
+ if (clock == 0 && i2c->last_clock == 1) {
+ i2c->state++; /* will end up in SENDING_ACK */
+ i2c->buffer <<= 1;
+ } else if (data == 1 && i2c->last_data == 0 && clock == 1)
+ i2c_enter_stop(i2c);
+ break;
+
+ case SENDING_ACK:
+ if (clock == 0 && i2c->last_clock == 1) {
+ i2c->state = RECEIVING_BIT7;
+ if (data == 0)
+ i2c->buffer = i2c_recv(i2c->bus);
+ else
+ i2c_nack(i2c->bus);
+ } else if (data == 1 && i2c->last_data == 0 && clock == 1)
+ i2c_enter_stop(i2c);
+ break;
+ }
+
+ i2c->last_data = data;
+ i2c->last_clock = clock;
+}
+
+static int i2c_get_data(i2c_interface *i2c)
+{
+ switch (i2c->state) {
+ case RECEIVING_BIT7 ... RECEIVING_BIT0:
+ return (i2c->buffer >> 7);
+
+ case WAITING_FOR_ACK:
+ default:
+ return 0;
+ }
+}
+
+
+typedef struct wm8750_state {
+ i2c_slave slave;
+ enum { STANDBY, WRITING_REG_0, WRITING_REG_1, READING_REG_0, READING_REG_1 } mode;
+ uint8_t buffer;
+ int volume;
+} wm8750_state;
+
+static void wm8750_i2c_event(i2c_slave *dev, enum i2c_event event)
+{
+ wm8750_state *s = (wm8750_state *)dev;
+
+ switch (event) {
+ case I2C_START_SEND:
+ if (s->mode == STANDBY)
+ s->mode = WRITING_REG_0;
+ break;
+
+ case I2C_START_RECV:
+ if (s->mode == STANDBY)
+ s->mode = READING_REG_0;
+ break;
+
+ case I2C_FINISH:
+ s->mode = STANDBY;
+ break;
+
+ default:
+ break;
+ }
+}
+
+static int wm8750_i2c_recv(i2c_slave *dev)
+{
+// wm8750_state *s = (wm8750_state *)dev;
+
+// printf("%s()\n", __FUNCTION__);
+ return 0;
+}
+
+static int wm8750_i2c_send(i2c_slave *dev, uint8_t data)
+{
+ wm8750_state *s = (wm8750_state *)dev;
+ int vol;
+
+ switch (s->mode) {
+ case WRITING_REG_0:
+ case READING_REG_0:
+ s->buffer = data;
+ s->mode++;
+ return 0;
+
+ case WRITING_REG_1:
+ switch (s->buffer >> 1) {
+ case 5: /* ADCDAC_CONTROL */
+ mv88w8618_audio_set_mute(audio_state, (data >> 3) & 1);
+ break;
+
+ case 40: /* LOUT2 */
+ /*
+ * Map volume:
+ * - no positive gain (avoid clipping)
+ * - smaller range
+ */
+ vol = ((data & 0x7F) - 0x7F) / 3;
+ mv88w8618_audio_set_volume(audio_state, vol);
+ break;
+ }
+ return 0;
+
+ default:
+ hw_error("wm8750: Getting data while in invalid state!\n");
+ return -1;
+ }
+}
+
+static i2c_interface *musicpal_mixer_init(void)
+{
+ i2c_interface *i2c;
+ wm8750_state *s;
+
+ i2c = qemu_mallocz(sizeof(i2c_interface));
+ if (!i2c)
+ return NULL;
+ i2c->bus = i2c_init_bus();
+ i2c->current_addr = -1;
+
+ s = (wm8750_state *)i2c_slave_init(i2c->bus, 0x34, sizeof(wm8750_state));
+ if (!s)
+ return NULL;
+ s->slave.event = wm8750_i2c_event;
+ s->slave.recv = wm8750_i2c_recv;
+ s->slave.send = wm8750_i2c_send;
+
+ mv88w8618_audio_set_mute(audio_state, 1);
+
+ return i2c;
+}
+
+
+static uint32_t musicpal_read(void *opaque, target_phys_addr_t addr)
+{
+ switch (addr) {
+ case 0x80002018: /* Board revision */
+ return 0x0031;
+
+ case 0x8000d00c: /* GPIO_OUT Lo */
+ return gpio_out_state & 0xFFFF;
+ case 0x8000d50c: /* GPIO_OUT Hi */
+ return gpio_out_state >> 16;
+
+ case 0x8000d010: /* GPIO_IN Lo */
+ return gpio_in_state & 0xFFFF;
+ case 0x8000d510: /* GPIO_IN Hi */
+ gpio_in_state = (gpio_in_state & ~(1 << 29)) |
+ (i2c_get_data(mixer_i2c) << 29);
+ return gpio_in_state >> 16;
+
+ case 0x8000d020: /* GPIO IRQ Status Lo */
+ return ~gpio_in_state & 0xFFFF;
+ case 0x8000d520: /* GPIO IRQ Status Hi */
+ return ~gpio_in_state >> 16;
+
+ case 0x8000d508: /* LCD Brightness */
+ return lcd_brightness >> 16;
+
+ case 0x90006004: /* Flash size */
+ return 0xfeffFFFF;
+
+ /* Workaround to allow loading the binary-only wlandrv.ko crap */
+ case 0x8000c11c:
+ return ~3;
+ case 0x8000c124:
+ return -1;
+
+ default:
+// printf("addr 0x%08lx\n", addr);
+ return 0;
+ }
+}
+
+static void musicpal_write(void *opaque, target_phys_addr_t addr, uint32_t value)
+{
+ switch (addr) {
+ case 0x8000d00c: /* GPIO_OUT Lo */
+ gpio_out_state = (gpio_out_state & 0xFFFF0000) | (value & 0xFFFF);
+ break;
+
+ case 0x8000d50c: /* GPIO_OUT Hi / LCD Brightness */
+ gpio_out_state = (gpio_out_state & 0xFFFF) | (value << 16);
+ lcd_brightness = (lcd_brightness & 0xFFFF0000) | (value & 0x0007);
+ i2c_state_update(mixer_i2c, (gpio_out_state >> 29) & 1,
+ (gpio_out_state >> 30) & 1);
+ break;
+
+ case 0x8000d508: /* LCD Brightness */
+ lcd_brightness = (lcd_brightness & 0xFFFF) | ((value << 16) & 0x00070000);
+ break;
+
+ case 0x90009034: /* Reset */
+ if (value & 0x10000)
+ qemu_system_reset_request();
+ break;
+ }
+}
+
+static CPUReadMemoryFunc *musicpal_readfn[] = {
+ musicpal_read,
+ musicpal_read,
+ musicpal_read,
+};
+
+static CPUWriteMemoryFunc *musicpal_writefn[] = {
+ musicpal_write,
+ musicpal_write,
+ musicpal_write,
+};
+
+
+static void musicpal_key_event(void *opaque, int keycode)
+{
+ qemu_irq irq = opaque;
+ uint32_t event = 0;
+ static int kbd_escape;
+
+ if (keycode == 0xe0) {
+ kbd_escape = 1;
+ return;
+ }
+
+ if (kbd_escape)
+ switch (keycode & 0x7F) {
+ case 0x48: /* Nav - */
+ event = 0x00000c00;
+ break;
+
+ case 0x50: /* Nav + */
+ event = 0x00000400;
+ break;
+
+ case 0x4b: /* Vol - */
+ event = 0x00000300;
+ break;
+
+ case 0x4d: /* Vol + */
+ event = 0x00000100;
+ break;
+ }
+ else
+ switch (keycode & 0x7F) {
+ case 0x21: /* Fav */
+ event = 0x00080000;
+ break;
+
+ case 0x0f: /* Vol */
+ event = 0x00200000;
+ break;
+
+ case 0x1c: /* Nav */
+ event = 0x00400000;
+ break;
+
+ case 0x32: /* Menu */
+ event = 0x00100000;
+ break;
+ }
+
+ if (keycode & 0x80)
+ gpio_in_state |= event;
+ else if (gpio_in_state & event) {
+ gpio_in_state &= ~event;
+ qemu_irq_raise(irq);
+ }
+
+ kbd_escape = 0;
+}
+
+
+/* Board init. */
+static void musicpal_init(int ram_size, int vga_ram_size,
+ const char *boot_device, DisplayState *ds,
+ const char *kernel_filename, const char *kernel_cmdline,
+ const char *initrd_filename, const char *cpu_model)
+{
+ CPUState *env;
+ qemu_irq *pic;
+ int index;
+ int iomemtype;
+ unsigned long flash_size;
+
+ if (!cpu_model)
+ cpu_model = "arm926";
+
+ env = cpu_init(cpu_model);
+ if (!env) {
+ fprintf(stderr, "Unable to find CPU definition\n");
+ exit(1);
+ }
+ pic = arm_pic_init_cpu(env);
+
+ cpu_register_physical_memory(0, ram_size, qemu_ram_alloc(ram_size));
+
+ sram_off = qemu_ram_alloc(MV_SRAM_SIZE);
+ cpu_register_physical_memory(MV_SRAM_BASE, MV_SRAM_SIZE, sram_off);
+
+ pic = mv88w8618_pic_init(0x90008000, pic[ARM_PIC_CPU_IRQ]);
+ mv88w8618_pit_init(0x90009000, pic, 4);
+
+ iomemtype = cpu_register_io_memory(0, musicpal_readfn, musicpal_writefn, first_cpu);
+ cpu_register_physical_memory(0x80000000, 0x10000, iomemtype);
+ cpu_register_physical_memory(0x90006004, 0x0004, iomemtype);
+ cpu_register_physical_memory(0x90009034, 0x0004, iomemtype);
+
+ serial_mm_init(0x8000C840, 2, pic[11], 1825000, serial_hds[0], 1);
+ if (serial_hds[1])
+ serial_mm_init(0x8000C940, 2, pic[11], 1825000, serial_hds[1], 1);
+
+ /* Register flash */
+ index = drive_get_index(IF_PFLASH, 0, 0);
+ if (index != -1) {
+ flash_size = bdrv_getlength(drives_table[index].bdrv);
+ if (flash_size != 8*1024*1024 && flash_size != 16*1024*1024 &&
+ flash_size != 32*1024*1024) {
+ fprintf(stderr, "Invalid flash image size\n");
+ exit(1);
+ }
+
+ /*
+ * The original U-Boot accesses the flash at 0xFE000000 (-32 MB) instead of
+ * 0xFF800000 (if there is 8 MB flash). So remap flash access if the image
+ * is smaller than 32 MB.
+ */
+ pflash_cfi02_register(0-32*1024*1024, qemu_ram_alloc(flash_size),
+ drives_table[index].bdrv, 0x10000,
+ (flash_size + 0xffff) >> 16, 32*1024*1024 / flash_size,
+ 2, 0x00BF, 0x236D, 0x0000, 0x0000, 0x5555, 0x2AAA);
+ }
+
+ musicpal_lcd_init(ds, 0x9000c000);
+
+ qemu_add_kbd_event_handler(musicpal_key_event, pic[12]);
+
+ /*
+ * Wait a bit to catch menu button during U-Boot start-up
+ * (to trigger emergency update).
+ */
+ sleep(1);
+
+ mv88w8618_eth_init(&nd_table[0], 0x80008000, pic[9]);
+
+ audio_state = mv88w8618_audio_init(0x90007000, pic[30]);
+ mixer_i2c = musicpal_mixer_init();
+
+ arm_load_kernel(first_cpu, ram_size, kernel_filename, kernel_cmdline,
+ initrd_filename, 0x20e, 0x0);
+}
+
+QEMUMachine musicpal_machine = {
+ "musicpal",
+ "Marvell 88w8618 / MusicPal (ARM926EJ-S)",
+ musicpal_init
+};
[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 254 bytes --]
^ permalink raw reply [flat|nested] 23+ messages in thread
* [Qemu-devel] Re: [RFC][PATCH 4/4] Add support for Marvell 88w8618 / MusicPal
2008-04-14 19:21 ` Jan Kiszka
@ 2008-04-14 21:34 ` Jan Kiszka
0 siblings, 0 replies; 23+ messages in thread
From: Jan Kiszka @ 2008-04-14 21:34 UTC (permalink / raw)
To: qemu-devel
[-- Attachment #1: Type: text/plain, Size: 873 bytes --]
Jan Kiszka wrote:
> +static void audio_fill_mixer_buffer(mv88w8618_audio_state *s, unsigned int length)
> +{
> + unsigned int pos;
> + double val;
> +
> + if (s->mute) {
> + memset(s->mixer_buffer, 0, length);
> + return;
> + }
> +
> + if (s->playback_mode & 1)
> + for (pos = 0; pos < length; pos += 2) {
> + val = *(int16_t *)(s->target_buffer + s->play_pos + pos);
> + val = le16_to_cpu(val) * pow(10.0, s->volume/20.0);
> + *(int16_t *)(s->mixer_buffer + pos) = val;
> + }
This variant "sounds" better:
for (pos = 0; pos < length; pos += 2) {
uint16_t tmp = *(uint16_t *)
(s->target_buffer + s->play_pos + pos);
val = (int16_t)le16_to_cpu(tmp);
val = val * pow(10.0, s->volume/20.0);
*(int16_t *)(s->mixer_buffer + pos) = val;
}
Find latest patches at http://home.arcor.de/jan.kiszka/patches/QEMU/
Jan
[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 254 bytes --]
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [Qemu-devel] Re: [RFC][PATCH 4/4] Add support for Marvell 88w8618 / MusicPal
2008-04-14 17:47 ` Jan Kiszka
@ 2008-04-15 17:38 ` malc
2008-04-15 21:03 ` Jan Kiszka
0 siblings, 1 reply; 23+ messages in thread
From: malc @ 2008-04-15 17:38 UTC (permalink / raw)
To: qemu-devel
On Mon, 14 Apr 2008, Jan Kiszka wrote:
> malc wrote:
>> On Mon, 14 Apr 2008, Jan Kiszka wrote:
>>
>>> malc wrote:
>>>> On Sun, 13 Apr 2008, Jan Kiszka wrote:
>>
>> [..snip..]
>>
>>>>> +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);
>>>>
>>>> Zero is not correct "mute" value for signed formats.
>>>
>>> Hmm, maybe it's still too early for me - but what else??
>>
>> Well.. 0x80 for S8 and 0x8000 for S16 (audio_pcm_info_clear_buf in
>> audio.c)
>
> void audio_pcm_info_clear_buf (struct audio_pcm_info *info, ...
> {
> ...
> if (info->sign) {
> memset (buf, 0x00, len << info->shift);
> }
Apparently i should get some sleep..
[..snip..]
>>
>> It's just a question of adding(and using) AUD_set_volume[_in/out],
>> which will set vol field of respective soft voice and removing
>> uncoditional #define NOVOL from mixeng.c.
>
> Well, I was (and still am) deterred by the plain fact that there are
> some dead AUD_set_volume spots in ac97.c. If it is that simple, why do
> we still lack an implementation? My feeling is that you are far deeper
> into this than I am at this point. So maybe you would like me to test
> some AUD_set_volume-providing patch of yours? :->
Heh. The reason why it's in this state is probably that i has only one
(disabled) user, namely ac97, and this one is relative newcomer. But this
are indeed that simple, one must provide this AUD_set_volume_out thing
that would fill in the vol structure of the soft voice, there are 3 fields
to set (left, right and mute) two of them being 32.32 fixed point (if my
memory serves me). The above blunder, however, shows that i'm in no shape
of doing that, at least not untill i get some sleep.
>>>>
>>>> Also adlib emulation is only conditionally available due to
>>>> usage of floating point arithmetics, maybe rules have changed now
>>>> though, but "fixing" this particular issue in this particular case
>>>> seems easy enough.
>>>
>>> Sorry, didn't get what you mean here.
>>
>> Adlib is not built by default, requires explicit --enable-adlib switch
>> to configure, because fmopl.c uses FPU and Fabrice didn't like the idea.
>> Fixing it here is just a matter of switching to fixed point.
>>
>> Then again if volume manipulation in main audio proper are brought up
>> to date this all can be scrapped and it will also "magically" solve the
>> endianness issue.
>
> I do agree that such stuff should be solved generically, but for now
> adding a simple le16_to_cpu does not compare to fixing QEMU's
> soft-volume management for me (given limited time).
And not forgetting to set aud_settings endiannes to AUDIO_HOST_ENDIANESS
>>>>
>>>>> +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;
>>>>
>>>> Here two clocks are being used for things - the audio one which
>>>> influences
>>>> `free' paramter and rt_clock, even if there are reasons why free
>>>> could not
>>>> be used choice of rt_clock over vm_clock seems wrong.
>>>
>>> Yes, using the monotonic vm_clock is better. Will change.
>>
>> Why is it needed at all? `free' already supplies you with an audio clock
>> source.
>
> The callback mechanism fires far more often than the emulated hardware
> expects (and can handle), I need throttling.
So just leave the callback until free get's big enough, you will get
dropouts either way, it's just that. I guess you might also use the
AUD_init_time_stamp_out/AUD_get_elapsed_usec_out mechanism (implemenation
is right at the end of audio/audio_template.h), but i still think that
just going with free is saner approach.
> The sad truth is that my original version was still off my an order of
> magnitude, causing overload for the guest while decoding obviously more
> complex 128kbit-WMA streams. Now it runs smoothly. :)
Great.
> Maybe there is a way to customize the callback rate, I haven't looked
> that close, but I'm open for suggestions.
No, you can't customize that.
--
mailto:av1474@comtv.ru
^ permalink raw reply [flat|nested] 23+ messages in thread
* [Qemu-devel] Re: [RFC][PATCH 4/4] Add support for Marvell 88w8618 / MusicPal
2008-04-15 17:38 ` malc
@ 2008-04-15 21:03 ` Jan Kiszka
2008-04-16 18:40 ` malc
0 siblings, 1 reply; 23+ messages in thread
From: Jan Kiszka @ 2008-04-15 21:03 UTC (permalink / raw)
To: qemu-devel
[-- Attachment #1: Type: text/plain, Size: 1327 bytes --]
malc wrote:
> So just leave the callback until free get's big enough, you will get
> dropouts either way, it's just that. I guess you might also use the
> AUD_init_time_stamp_out/AUD_get_elapsed_usec_out mechanism (implemenation
> is right at the end of audio/audio_template.h), but i still think that
> just going with free is saner approach.
Well, took some iterations to switch my mind completely to the new
model, but now this timestamp-free version runs as smooth as the
previous one:
static void audio_callback(void *opaque, int free)
{
mv88w8618_audio_state *s = opaque;
unsigned int block_size, written;
block_size = s->threshold/2 - s->block_written;
if (free - s->last_free < block_size)
return;
audio_fill_mixer_buffer(s, block_size);
written = AUD_write(s->voice, s->mixer_buffer, block_size);
s->last_free = free - written;
if (written < block_size) {
s->block_written += written;
return;
}
s->block_written = 0;
if (s->play_pos == 0) {
s->status |= MP_AUDIO_TX_HALF;
s->play_pos = s->threshold/2;
} else {
s->status |= MP_AUDIO_TX_FULL;
s->play_pos = 0;
}
if (s->status & s->irq_enable)
qemu_irq_raise(s->irq);
}
[ This would be even simpler if I could assume written == block_size ||
written == 0. But who knows... ]
Jan
[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 254 bytes --]
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [Qemu-devel] Re: [RFC][PATCH 4/4] Add support for Marvell 88w8618 / MusicPal
2008-04-15 21:03 ` Jan Kiszka
@ 2008-04-16 18:40 ` malc
2008-04-17 19:06 ` Jan Kiszka
0 siblings, 1 reply; 23+ messages in thread
From: malc @ 2008-04-16 18:40 UTC (permalink / raw)
To: qemu-devel
On Tue, 15 Apr 2008, Jan Kiszka wrote:
> malc wrote:
>> So just leave the callback until free get's big enough, you will get
>> dropouts either way, it's just that. I guess you might also use the
>> AUD_init_time_stamp_out/AUD_get_elapsed_usec_out mechanism (implemenation
>> is right at the end of audio/audio_template.h), but i still think that
>> just going with free is saner approach.
>
> Well, took some iterations to switch my mind completely to the new
> model, but now this timestamp-free version runs as smooth as the
> previous one:
Glad to hear that.
> static void audio_callback(void *opaque, int free)
> {
> mv88w8618_audio_state *s = opaque;
> unsigned int block_size, written;
>
> block_size = s->threshold/2 - s->block_written;
> if (free - s->last_free < block_size)
> return;
>
> audio_fill_mixer_buffer(s, block_size);
> written = AUD_write(s->voice, s->mixer_buffer, block_size);
>
> s->last_free = free - written;
>
> if (written < block_size) {
> s->block_written += written;
> return;
> }
> s->block_written = 0;
>
> if (s->play_pos == 0) {
> s->status |= MP_AUDIO_TX_HALF;
> s->play_pos = s->threshold/2;
> } else {
> s->status |= MP_AUDIO_TX_FULL;
> s->play_pos = 0;
> }
>
> if (s->status & s->irq_enable)
> qemu_irq_raise(s->irq);
> }
>
> [ This would be even simpler if I could assume written == block_size ||
> written == 0. But who knows... ]
The API has no way to enforce this, therefore the only sane option is to
assume it will happen. I confess i don't quite get the logic behind the
last_free, but as you said, it works smoothly, so be it.
--
mailto:av1474@comtv.ru
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [Qemu-devel] [RFC][PATCH 4/4] Add support for Marvell 88w8618 / MusicPal
2008-04-13 10:11 [Qemu-devel] [RFC][PATCH 4/4] Add support for Marvell 88w8618 / MusicPal Jan Kiszka
2008-04-13 20:52 ` malc
2008-04-14 19:21 ` Jan Kiszka
@ 2008-04-17 0:24 ` andrzej zaborowski
2008-04-17 0:46 ` andrzej zaborowski
2008-04-17 19:06 ` [Qemu-devel] " Jan Kiszka
2008-04-20 16:32 ` [Qemu-devel] [PATCH] " Jan Kiszka
3 siblings, 2 replies; 23+ messages in thread
From: andrzej zaborowski @ 2008-04-17 0:24 UTC (permalink / raw)
To: qemu-devel
Hi,
On 13/04/2008, Jan Kiszka <jan.kiszka@web.de> wrote:
> This is the board emulation for Freecom's MusicPal, featuring
> - rudimentary PIT and PIC
> - up to 2 UARTs
> - 88W8xx8 Ethernet controller
> - 88W8618 audio controller
> - Wolfson WM8750 mixer chip (volume control and mute only)
Are you sure that hw/wm8750.c is not reusable? It's probably better
to extend it with volume control, and audio data transmission through
i2c, instead of having two implementations in QEMU.
Regarding volume control, since there are only 0x7f possible values
it's nicer to use a const table than pull in math.h. Actually I
wouldn't worry about that at all because the layout of volume levels
on the host is not guaranteed to be linear (I think?), it may very
well be already exponential. For example the Linux WM8750 driver
doesn't do the reverse transformation which would mean that on a host
whose ALSA output device is a WM8750, the Qemu vm would have a wrong
volume scale.
> - 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.
You can use -m 150 or similar.
Please also format the code similarly to rest of Qemu.
--
Please do not print this email unless absolutely necessary. Spread
environmental awareness.
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [Qemu-devel] [RFC][PATCH 4/4] Add support for Marvell 88w8618 / MusicPal
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
1 sibling, 0 replies; 23+ messages in thread
From: andrzej zaborowski @ 2008-04-17 0:46 UTC (permalink / raw)
To: qemu-devel
On 17/04/2008, andrzej zaborowski <balrogg@gmail.com> wrote:
> On 13/04/2008, Jan Kiszka <jan.kiszka@web.de> wrote:
> > This is the board emulation for Freecom's MusicPal, featuring
> > - rudimentary PIT and PIC
> > - up to 2 UARTs
> > - 88W8xx8 Ethernet controller
> > - 88W8618 audio controller
> > - Wolfson WM8750 mixer chip (volume control and mute only)
>
> Are you sure that hw/wm8750.c is not reusable? It's probably better
> to extend it with volume control, and audio data transmission through
> i2c, instead of having two implementations in QEMU.
>
> Regarding volume control, since there are only 0x7f possible values
> it's nicer to use a const table than pull in math.h. Actually I
> wouldn't worry about that at all because the layout of volume levels
> on the host is not guaranteed to be linear (I think?), it may very
> well be already exponential. For example the Linux WM8750 driver
> doesn't do the reverse transformation which would mean that on a host
> whose ALSA output device is a WM8750, the Qemu vm would have a wrong
> volume scale.
Ah, we're multiplying the samples, rather than setting volume on host.
Please ignore the above comment, but it would still be good to avoid
pow() (especially if return value is invariant across the loop and
perhaps across many buffer fills).
--
Please do not print this email unless absolutely necessary. Spread
environmental awareness.
^ permalink raw reply [flat|nested] 23+ messages in thread
* [Qemu-devel] Re: [RFC][PATCH 4/4] Add support for Marvell 88w8618 / MusicPal
2008-04-17 0:24 ` [Qemu-devel] " andrzej zaborowski
2008-04-17 0:46 ` andrzej zaborowski
@ 2008-04-17 19:06 ` Jan Kiszka
2008-04-18 18:12 ` Jan Kiszka
1 sibling, 1 reply; 23+ messages in thread
From: Jan Kiszka @ 2008-04-17 19:06 UTC (permalink / raw)
To: qemu-devel
[-- Attachment #1: Type: text/plain, Size: 1890 bytes --]
andrzej zaborowski wrote:
> Hi,
>
> On 13/04/2008, Jan Kiszka <jan.kiszka@web.de> wrote:
>> This is the board emulation for Freecom's MusicPal, featuring
>> - rudimentary PIT and PIC
>> - up to 2 UARTs
>> - 88W8xx8 Ethernet controller
>> - 88W8618 audio controller
>> - Wolfson WM8750 mixer chip (volume control and mute only)
>
> Are you sure that hw/wm8750.c is not reusable? It's probably better
> to extend it with volume control, and audio data transmission through
> i2c, instead of having two implementations in QEMU.
Will check again, but I don't think it is helpful, at least at this
point. The thing is that the MusicPal uses the on-chip DAC, not the one
of the Wolfson. The latter seems to be responsible for analogous mixing
only.
>
> Regarding volume control, since there are only 0x7f possible values
> it's nicer to use a const table than pull in math.h. Actually I
> wouldn't worry about that at all because the layout of volume levels
> on the host is not guaranteed to be linear (I think?), it may very
> well be already exponential. For example the Linux WM8750 driver
> doesn't do the reverse transformation which would mean that on a host
> whose ALSA output device is a WM8750, the Qemu vm would have a wrong
> volume scale.
[ Ignoring the above as advised in the follow-up, but will think about
avoiding or reducing the usage of pow(). ]
>
>> - 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.
>
> You can use -m 150 or similar.
>
> Please also format the code similarly to rest of Qemu.
That would just increase ram_size, thus won't help as I need memory
beyond it (here for the pflash in R/W mode).
Jan
[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 254 bytes --]
^ permalink raw reply [flat|nested] 23+ messages in thread
* [Qemu-devel] Re: [RFC][PATCH 4/4] Add support for Marvell 88w8618 / MusicPal
2008-04-16 18:40 ` malc
@ 2008-04-17 19:06 ` Jan Kiszka
0 siblings, 0 replies; 23+ messages in thread
From: Jan Kiszka @ 2008-04-17 19:06 UTC (permalink / raw)
To: qemu-devel
[-- Attachment #1: Type: text/plain, Size: 2049 bytes --]
malc wrote:
> On Tue, 15 Apr 2008, Jan Kiszka wrote:
>
>> malc wrote:
>>> So just leave the callback until free get's big enough, you will get
>>> dropouts either way, it's just that. I guess you might also use the
>>> AUD_init_time_stamp_out/AUD_get_elapsed_usec_out mechanism
>>> (implemenation
>>> is right at the end of audio/audio_template.h), but i still think that
>>> just going with free is saner approach.
>>
>> Well, took some iterations to switch my mind completely to the new
>> model, but now this timestamp-free version runs as smooth as the
>> previous one:
>
> Glad to hear that.
>
>> static void audio_callback(void *opaque, int free)
>> {
>> mv88w8618_audio_state *s = opaque;
>> unsigned int block_size, written;
>>
>> block_size = s->threshold/2 - s->block_written;
>> if (free - s->last_free < block_size)
>> return;
>>
>> audio_fill_mixer_buffer(s, block_size);
>> written = AUD_write(s->voice, s->mixer_buffer, block_size);
>>
>> s->last_free = free - written;
>>
>> if (written < block_size) {
>> s->block_written += written;
>> return;
>> }
>> s->block_written = 0;
>>
>> if (s->play_pos == 0) {
>> s->status |= MP_AUDIO_TX_HALF;
>> s->play_pos = s->threshold/2;
>> } else {
>> s->status |= MP_AUDIO_TX_FULL;
>> s->play_pos = 0;
>> }
>>
>> if (s->status & s->irq_enable)
>> qemu_irq_raise(s->irq);
>> }
>>
>> [ This would be even simpler if I could assume written == block_size ||
>> written == 0. But who knows... ]
>
> The API has no way to enforce this, therefore the only sane option is to
> assume it will happen. I confess i don't quite get the logic behind the
> last_free, but as you said, it works smoothly, so be it.
It's kind of a beautification: If the host buffer is larger than
s->threshold (the buffer size the guest wishes to use), there would be
an initial IRQ burst otherwise until the host buffer is consumed.
Jan
[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 254 bytes --]
^ permalink raw reply [flat|nested] 23+ messages in thread
* [Qemu-devel] Re: [RFC][PATCH 4/4] Add support for Marvell 88w8618 / MusicPal
2008-04-17 19:06 ` [Qemu-devel] " Jan Kiszka
@ 2008-04-18 18:12 ` Jan Kiszka
2008-04-18 18:43 ` andrzej zaborowski
0 siblings, 1 reply; 23+ messages in thread
From: Jan Kiszka @ 2008-04-18 18:12 UTC (permalink / raw)
To: qemu-devel
[-- Attachment #1: Type: text/plain, Size: 2426 bytes --]
Jan Kiszka wrote:
> andrzej zaborowski wrote:
>> Hi,
>>
>> On 13/04/2008, Jan Kiszka <jan.kiszka@web.de> wrote:
>>> This is the board emulation for Freecom's MusicPal, featuring
>>> - rudimentary PIT and PIC
>>> - up to 2 UARTs
>>> - 88W8xx8 Ethernet controller
>>> - 88W8618 audio controller
>>> - Wolfson WM8750 mixer chip (volume control and mute only)
>> Are you sure that hw/wm8750.c is not reusable? It's probably better
>> to extend it with volume control, and audio data transmission through
>> i2c, instead of having two implementations in QEMU.
>
> Will check again, but I don't think it is helpful, at least at this
> point. The thing is that the MusicPal uses the on-chip DAC, not the one
> of the Wolfson. The latter seems to be responsible for analogous mixing
> only.
That was nonsense: The audio architecture of the MusicPal appears to be
much like the one of the Spitz. Redirecting the audio stream to the
Wolfson should be feasible. However, lacking support for volume control
and muting currently prevents this.
Andrzej, as you have written the wm8750, do you already know where which
volume level would have to be applied (open-coded or via some
AUD_set_volume)? I'm currently only using LOUT2VOL, and I'm a bit lazy
to study the datasheet /wrt all the mixer details.
>>> - 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.
>> You can use -m 150 or similar.
>>
>> Please also format the code similarly to rest of Qemu.
>
> That would just increase ram_size, thus won't help as I need memory
> beyond it (here for the pflash in R/W mode).
OK, I see what you mean after looking at your N800 patches: You apply a
fixed RAM size, leaving the rest of what the user provided via -m to
SRAM and flash. Not optimal IMHO, you may sometimes also want to play
with the RAM size even if the real devices has a fixed amount. And it is
far from being intuitive as well.
The only true solution I see right now is moving qemu_vmalloc into the
machine initialization code. Is there anything between current
qemu_vmalloc and machine->init that relies on phys_ram_base being valid
(and which can't be moved after the machine init) and thus prevents this?
Jan
[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 254 bytes --]
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [Qemu-devel] Re: [RFC][PATCH 4/4] Add support for Marvell 88w8618 / MusicPal
2008-04-18 18:12 ` Jan Kiszka
@ 2008-04-18 18:43 ` andrzej zaborowski
2008-04-19 19:01 ` Jan Kiszka
0 siblings, 1 reply; 23+ messages in thread
From: andrzej zaborowski @ 2008-04-18 18:43 UTC (permalink / raw)
To: qemu-devel
On 18/04/2008, Jan Kiszka <jan.kiszka@web.de> wrote:
> Andrzej, as you have written the wm8750, do you already know where which
> volume level would have to be applied (open-coded or via some
> AUD_set_volume)? I'm currently only using LOUT2VOL, and I'm a bit lazy
> to study the datasheet /wrt all the mixer details.
My idea was to open
http://www.wolfsonmicro.com/uploads/documents/en/WM8750.pdf and on the
first page every Wolfson datasheet has its diagram of all audio paths
(of which there are always too many) and then trace with my finger the
path between the source (the I2C or I2S interfaces) and the sink (the
analog output), and then multiply all the volume values that are
applied there (both analog and digital) and pass that to host mixer
through some functions in audio/ for the given SWVoice - but we don't
have any such functions and I'm ok with using the host mixer manually.
(VirtualBox has them implemented iirc) So yes, maybe it makes sense
to multiply the samples for the moment and use only LOUTnVOL /
ROUTnVOL values as these are used by the guests we're interested in.
>
>
> >>> - 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.
> >> You can use -m 150 or similar.
> >>
> >> Please also format the code similarly to rest of Qemu.
> >
> > That would just increase ram_size, thus won't help as I need memory
> > beyond it (here for the pflash in R/W mode).
Yes, I had not looked at how ram_size was used in the musicpal board
initialisation, sorry.
>
>
> OK, I see what you mean after looking at your N800 patches: You apply a
> fixed RAM size, leaving the rest of what the user provided via -m to
> SRAM and flash. Not optimal IMHO, you may sometimes also want to play
> with the RAM size even if the real devices has a fixed amount. And it is
> far from being intuitive as well.
Yes, although you allow the user to set also a smaller RAM than what
the virtual machine expects.
>
> The only true solution I see right now is moving qemu_vmalloc into the
> machine initialization code. Is there anything between current
> qemu_vmalloc and machine->init that relies on phys_ram_base being valid
> (and which can't be moved after the machine init) and thus prevents this?
I had a different idea: add a field ram_constraint in struct
QEMUMachine, which would hold the amount of RAM the machine always
needs (e.g. bios and video RAM), and the low bit could hold a flag
RAM_SIZE_FIXED for machines that have only such RAM (basically the
criteria should be whether it's possible for the guest to detect the
memory size there is on board - on machines like Spitz there's no way)
and for such machines the -m parameter would be invalid. I'll try to
come up with a patch.
--
Please do not print this email unless absolutely necessary. Spread
environmental awareness.
^ permalink raw reply [flat|nested] 23+ messages in thread
* [Qemu-devel] Re: [RFC][PATCH 4/4] Add support for Marvell 88w8618 / MusicPal
2008-04-18 18:43 ` andrzej zaborowski
@ 2008-04-19 19:01 ` Jan Kiszka
2008-04-20 4:11 ` andrzej zaborowski
0 siblings, 1 reply; 23+ messages in thread
From: Jan Kiszka @ 2008-04-19 19:01 UTC (permalink / raw)
To: qemu-devel
[-- Attachment #1: Type: text/plain, Size: 4069 bytes --]
andrzej zaborowski wrote:
> On 18/04/2008, Jan Kiszka <jan.kiszka@web.de> wrote:
>> Andrzej, as you have written the wm8750, do you already know where which
>> volume level would have to be applied (open-coded or via some
>> AUD_set_volume)? I'm currently only using LOUT2VOL, and I'm a bit lazy
>> to study the datasheet /wrt all the mixer details.
>
> My idea was to open
> http://www.wolfsonmicro.com/uploads/documents/en/WM8750.pdf and on the
> first page every Wolfson datasheet has its diagram of all audio paths
> (of which there are always too many) and then trace with my finger the
> path between the source (the I2C or I2S interfaces) and the sink (the
> analog output), and then multiply all the volume values that are
> applied there (both analog and digital) and pass that to host mixer
> through some functions in audio/ for the given SWVoice - but we don't
> have any such functions and I'm ok with using the host mixer manually.
> (VirtualBox has them implemented iirc) So yes, maybe it makes sense
> to multiply the samples for the moment and use only LOUTnVOL /
> ROUTnVOL values as these are used by the guests we're interested in.
Done, and it finally works. One of the two quirks I found in wm8750 made
the switch a bit hairy. Patches will follow.
>
>>
>> >>> - 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.
>> >> You can use -m 150 or similar.
>> >>
>> >> Please also format the code similarly to rest of Qemu.
>> >
>> > That would just increase ram_size, thus won't help as I need memory
>> > beyond it (here for the pflash in R/W mode).
>
> Yes, I had not looked at how ram_size was used in the musicpal board
> initialisation, sorry.
>
>>
>> OK, I see what you mean after looking at your N800 patches: You apply a
>> fixed RAM size, leaving the rest of what the user provided via -m to
>> SRAM and flash. Not optimal IMHO, you may sometimes also want to play
>> with the RAM size even if the real devices has a fixed amount. And it is
>> far from being intuitive as well.
>
> Yes, although you allow the user to set also a smaller RAM than what
> the virtual machine expects.
That's indeed something the machine should take of (if there are such
hard limits).
>
>> The only true solution I see right now is moving qemu_vmalloc into the
>> machine initialization code. Is there anything between current
>> qemu_vmalloc and machine->init that relies on phys_ram_base being valid
>> (and which can't be moved after the machine init) and thus prevents this?
>
> I had a different idea: add a field ram_constraint in struct
> QEMUMachine, which would hold the amount of RAM the machine always
> needs (e.g. bios and video RAM), and the low bit could hold a flag
> RAM_SIZE_FIXED for machines that have only such RAM (basically the
> criteria should be whether it's possible for the guest to detect the
> memory size there is on board - on machines like Spitz there's no way)
IIRC, embedded boards let the boot loader "detect" this. I see valid
scenarios where one wants to play with different sizes and may therefore
patch U-Boot - or load the kernel directly which should make QEMU set
the related ATAG field appropriately, no?
> and for such machines the -m parameter would be invalid. I'll try to
> come up with a patch.
I originally had the same idea but I dropped it because it would still
overload -m with semantics that don't belong there. IMHO -m should only
describe the main RAM size, not any additionally by QEMU required memory
for establishing fixed SRAM or even for backing up flash devices. That's
at least what I would expect from this switch and what the documentation
suggests as well so far.
Thus, back to square #1: Can't we move qemu_vmalloc into the
machine-specific init handlers?
Jan
[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 254 bytes --]
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [Qemu-devel] Re: [RFC][PATCH 4/4] Add support for Marvell 88w8618 / MusicPal
2008-04-19 19:01 ` Jan Kiszka
@ 2008-04-20 4:11 ` andrzej zaborowski
2008-04-20 15:52 ` Jan Kiszka
0 siblings, 1 reply; 23+ messages in thread
From: andrzej zaborowski @ 2008-04-20 4:11 UTC (permalink / raw)
To: qemu-devel
On 19/04/2008, Jan Kiszka <jan.kiszka@web.de> wrote:
> andrzej zaborowski wrote:
> > On 18/04/2008, Jan Kiszka <jan.kiszka@web.de> wrote:
> >> Andrzej, as you have written the wm8750, do you already know where which
> >> volume level would have to be applied (open-coded or via some
> >> AUD_set_volume)? I'm currently only using LOUT2VOL, and I'm a bit lazy
> >> to study the datasheet /wrt all the mixer details.
> >
> > My idea was to open
> > http://www.wolfsonmicro.com/uploads/documents/en/WM8750.pdf and on the
> > first page every Wolfson datasheet has its diagram of all audio paths
> > (of which there are always too many) and then trace with my finger the
> > path between the source (the I2C or I2S interfaces) and the sink (the
> > analog output), and then multiply all the volume values that are
> > applied there (both analog and digital) and pass that to host mixer
> > through some functions in audio/ for the given SWVoice - but we don't
> > have any such functions and I'm ok with using the host mixer manually.
> > (VirtualBox has them implemented iirc) So yes, maybe it makes sense
> > to multiply the samples for the moment and use only LOUTnVOL /
> > ROUTnVOL values as these are used by the guests we're interested in.
>
>
> Done, and it finally works. One of the two quirks I found in wm8750 made
> the switch a bit hairy. Patches will follow.
Thanks. I pushed the patch with fixes. Regarding the wm8750_fini
patch, I'll #if 0 it because it's possible that a board will have this
chip on something hotpluggable and will need to create and destroy it
various times and it's easy to miss something in the clean-up.
Regarding the volume patch, I'll make a look-up table at one point,
and then merge. Also, if we have 16-bit data and 7-bit volume scale
maybe we're fine with scalling only the most-significant-byte and
avoiding endianness headaches (or maybe not). Nevertheless the
MusicPal emulator should be bootable without that.
>
>
> >
> >>
> >> >>> - 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.
> >> >> You can use -m 150 or similar.
> >> >>
> >> >> Please also format the code similarly to rest of Qemu.
> >> >
> >> > That would just increase ram_size, thus won't help as I need memory
> >> > beyond it (here for the pflash in R/W mode).
> >
> > Yes, I had not looked at how ram_size was used in the musicpal board
> > initialisation, sorry.
> >
> >>
> >> OK, I see what you mean after looking at your N800 patches: You apply a
> >> fixed RAM size, leaving the rest of what the user provided via -m to
> >> SRAM and flash. Not optimal IMHO, you may sometimes also want to play
> >> with the RAM size even if the real devices has a fixed amount. And it is
> >> far from being intuitive as well.
> >
> > Yes, although you allow the user to set also a smaller RAM than what
> > the virtual machine expects.
>
>
> That's indeed something the machine should take of (if there are such
> hard limits).
>
>
> >
> >> The only true solution I see right now is moving qemu_vmalloc into the
> >> machine initialization code. Is there anything between current
> >> qemu_vmalloc and machine->init that relies on phys_ram_base being valid
> >> (and which can't be moved after the machine init) and thus prevents this?
> >
> > I had a different idea: add a field ram_constraint in struct
> > QEMUMachine, which would hold the amount of RAM the machine always
> > needs (e.g. bios and video RAM), and the low bit could hold a flag
> > RAM_SIZE_FIXED for machines that have only such RAM (basically the
> > criteria should be whether it's possible for the guest to detect the
> > memory size there is on board - on machines like Spitz there's no way)
>
>
> IIRC, embedded boards let the boot loader "detect" this. I see valid
> scenarios where one wants to play with different sizes and may therefore
> patch U-Boot - or load the kernel directly which should make QEMU set
> the related ATAG field appropriately, no?
Yes, in case of a standard firmware like Linux or U-boot - but we
probably don't need to provide options for everything one may want to
play with unless it's a valid hardware configuration (like in the PC
case where you can add and take away RAM sticks), at some point the
user needs to edit the source either way.
Anyway almost half of the boards in qemu ignored ram_size until now
and risked the provided size being too low and segfaulting, so with
the patch I sent in another mail at least there's a check, and the
check is only done once for all boards so it can be removed from the
few boards that did it.
>
>
> > and for such machines the -m parameter would be invalid. I'll try to
> > come up with a patch.
>
>
> I originally had the same idea but I dropped it because it would still
> overload -m with semantics that don't belong there. IMHO -m should only
> describe the main RAM size, not any additionally by QEMU required memory
> for establishing fixed SRAM or even for backing up flash devices. That's
> at least what I would expect from this switch and what the documentation
> suggests as well so far.
This property is not changed by the patch (I hope).
>
> Thus, back to square #1: Can't we move qemu_vmalloc into the
> machine-specific init handlers?
I think it would work (but I wouldn't do that).
Cheers
--
Please do not print this email unless absolutely necessary. Spread
environmental awareness.
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [Qemu-devel] Re: [RFC][PATCH 4/4] Add support for Marvell 88w8618 / MusicPal
2008-04-20 4:11 ` andrzej zaborowski
@ 2008-04-20 15:52 ` Jan Kiszka
2008-04-20 17:38 ` andrzej zaborowski
0 siblings, 1 reply; 23+ messages in thread
From: Jan Kiszka @ 2008-04-20 15:52 UTC (permalink / raw)
To: qemu-devel
[-- Attachment #1: Type: text/plain, Size: 6182 bytes --]
andrzej zaborowski wrote:
> On 19/04/2008, Jan Kiszka <jan.kiszka@web.de> wrote:
>> andrzej zaborowski wrote:
>> > On 18/04/2008, Jan Kiszka <jan.kiszka@web.de> wrote:
>> >> Andrzej, as you have written the wm8750, do you already know where which
>> >> volume level would have to be applied (open-coded or via some
>> >> AUD_set_volume)? I'm currently only using LOUT2VOL, and I'm a bit lazy
>> >> to study the datasheet /wrt all the mixer details.
>> >
>> > My idea was to open
>> > http://www.wolfsonmicro.com/uploads/documents/en/WM8750.pdf and on the
>> > first page every Wolfson datasheet has its diagram of all audio paths
>> > (of which there are always too many) and then trace with my finger the
>> > path between the source (the I2C or I2S interfaces) and the sink (the
>> > analog output), and then multiply all the volume values that are
>> > applied there (both analog and digital) and pass that to host mixer
>> > through some functions in audio/ for the given SWVoice - but we don't
>> > have any such functions and I'm ok with using the host mixer manually.
>> > (VirtualBox has them implemented iirc) So yes, maybe it makes sense
>> > to multiply the samples for the moment and use only LOUTnVOL /
>> > ROUTnVOL values as these are used by the guests we're interested in.
>>
>>
>> Done, and it finally works. One of the two quirks I found in wm8750 made
>> the switch a bit hairy. Patches will follow.
>
> Thanks. I pushed the patch with fixes. Regarding the wm8750_fini
> patch, I'll #if 0 it because it's possible that a board will have this
> chip on something hotpluggable and will need to create and destroy it
> various times and it's easy to miss something in the clean-up.
> Regarding the volume patch, I'll make a look-up table at one point,
Don't understand yet why (are you afraid of pow, libm, or float in
general?), but if it helps to get things merged... ;)
> and then merge. Also, if we have 16-bit data and 7-bit volume scale
> maybe we're fine with scalling only the most-significant-byte and
Hmm, wasn't endianness about finding out which byte is most-significant
and which not? :->
> avoiding endianness headaches (or maybe not). Nevertheless the
> MusicPal emulator should be bootable without that.
>
>>
>> >
>> >>
>> >> >>> - 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.
>> >> >> You can use -m 150 or similar.
>> >> >>
>> >> >> Please also format the code similarly to rest of Qemu.
>> >> >
>> >> > That would just increase ram_size, thus won't help as I need memory
>> >> > beyond it (here for the pflash in R/W mode).
>> >
>> > Yes, I had not looked at how ram_size was used in the musicpal board
>> > initialisation, sorry.
>> >
>> >>
>> >> OK, I see what you mean after looking at your N800 patches: You apply a
>> >> fixed RAM size, leaving the rest of what the user provided via -m to
>> >> SRAM and flash. Not optimal IMHO, you may sometimes also want to play
>> >> with the RAM size even if the real devices has a fixed amount. And it is
>> >> far from being intuitive as well.
>> >
>> > Yes, although you allow the user to set also a smaller RAM than what
>> > the virtual machine expects.
>>
>>
>> That's indeed something the machine should take of (if there are such
>> hard limits).
>>
>>
>> >
>> >> The only true solution I see right now is moving qemu_vmalloc into the
>> >> machine initialization code. Is there anything between current
>> >> qemu_vmalloc and machine->init that relies on phys_ram_base being valid
>> >> (and which can't be moved after the machine init) and thus prevents this?
>> >
>> > I had a different idea: add a field ram_constraint in struct
>> > QEMUMachine, which would hold the amount of RAM the machine always
>> > needs (e.g. bios and video RAM), and the low bit could hold a flag
>> > RAM_SIZE_FIXED for machines that have only such RAM (basically the
>> > criteria should be whether it's possible for the guest to detect the
>> > memory size there is on board - on machines like Spitz there's no way)
>>
>>
>> IIRC, embedded boards let the boot loader "detect" this. I see valid
>> scenarios where one wants to play with different sizes and may therefore
>> patch U-Boot - or load the kernel directly which should make QEMU set
>> the related ATAG field appropriately, no?
>
> Yes, in case of a standard firmware like Linux or U-boot - but we
> probably don't need to provide options for everything one may want to
> play with unless it's a valid hardware configuration (like in the PC
> case where you can add and take away RAM sticks), at some point the
> user needs to edit the source either way.
>
> Anyway almost half of the boards in qemu ignored ram_size until now
> and risked the provided size being too low and segfaulting, so with
> the patch I sent in another mail at least there's a check, and the
> check is only done once for all boards so it can be removed from the
> few boards that did it.
>
>>
>> > and for such machines the -m parameter would be invalid. I'll try to
>> > come up with a patch.
>>
>>
>> I originally had the same idea but I dropped it because it would still
>> overload -m with semantics that don't belong there. IMHO -m should only
>> describe the main RAM size, not any additionally by QEMU required memory
>> for establishing fixed SRAM or even for backing up flash devices. That's
>> at least what I would expect from this switch and what the documentation
>> suggests as well so far.
>
> This property is not changed by the patch (I hope).
Yes, it restores the original semantic, at least as long as
RAMSIZE_FIXED is not set. That case is still a bit suboptimal as you
have to provide pessimistic values, e.g. the maximum flash size that can
be used. But I can live with it I guess.
Jan
[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 254 bytes --]
^ permalink raw reply [flat|nested] 23+ messages in thread
* [Qemu-devel] [PATCH] Add support for Marvell 88w8618 / MusicPal
2008-04-13 10:11 [Qemu-devel] [RFC][PATCH 4/4] Add support for Marvell 88w8618 / MusicPal Jan Kiszka
` (2 preceding siblings ...)
2008-04-17 0:24 ` [Qemu-devel] " andrzej zaborowski
@ 2008-04-20 16:32 ` Jan Kiszka
3 siblings, 0 replies; 23+ messages in thread
From: Jan Kiszka @ 2008-04-20 16:32 UTC (permalink / raw)
To: qemu-devel
Here comes a polished and further improved version of the MusicPal
emulation. Changes since last posting:
- Exploit existing wm8750 model (enhanced version)
- Fix IRQ re-entrance due to wrong PIC status
- (For now) fixed RAM size, using recently proposed per-machine
ram_require
- Code cleanup, all magics pushed into #defines as well
- Reformatting according to QEMU coding style
This patch depends on
- SVN #4225
- Andrzej's per-machine memory size patch
- wm8750 volume patch
Signed-off-by: Jan Kiszka <jan.kiszka@web.de>
---
Makefile.target | 1
hw/boards.h | 3
hw/musicpal.c | 1456 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
vl.c | 1
4 files changed, 1461 insertions(+)
Index: b/Makefile.target
===================================================================
--- a/Makefile.target
+++ b/Makefile.target
@@ -614,6 +614,7 @@ OBJS+= omap2.o omap_dss.o
OBJS+= palm.o tsc210x.o
OBJS+= nseries.o blizzard.o onenand.o vga.o cbus.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
@@ -107,4 +107,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
@@ -8057,6 +8057,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/hw/musicpal.c
===================================================================
--- /dev/null
+++ b/hw/musicpal.c
@@ -0,0 +1,1456 @@
+/*
+ * Marvell MV88W8618 / Freecom MusicPal emulation.
+ *
+ * Copyright (c) 2008 Jan Kiszka
+ *
+ * This code is licenced under the GNU GPL v2.
+ */
+
+#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 MP_ETH_BASE 0x80008000
+#define MP_ETH_SIZE 0x00001000
+
+#define MP_UART1_BASE 0x8000C840
+#define MP_UART2_BASE 0x8000C940
+
+#define MP_FLASHCFG_BASE 0x90006000
+#define MP_FLASHCFG_SIZE 0x00001000
+
+#define MP_AUDIO_BASE 0x90007000
+#define MP_AUDIO_SIZE 0x00001000
+
+#define MP_PIC_BASE 0x90008000
+#define MP_PIC_SIZE 0x00001000
+
+#define MP_PIT_BASE 0x90009000
+#define MP_PIT_SIZE 0x00001000
+
+#define MP_LCD_BASE 0x9000c000
+#define MP_LCD_SIZE 0x00001000
+
+#define MP_SRAM_BASE 0xC0000000
+#define MP_SRAM_SIZE 0x00020000
+
+#define MP_RAM_DEFAULT_SIZE 32*1024*1024
+#define MP_FLASH_SIZE_MAX 32*1024*1024
+
+#define MP_TIMER1_IRQ 4
+/* ... */
+#define MP_TIMER4_IRQ 7
+#define MP_EHCI_IRQ 8
+#define MP_ETH_IRQ 9
+#define MP_UART1_IRQ 11
+#define MP_UART2_IRQ 11
+#define MP_GPIO_IRQ 12
+#define MP_RTC_IRQ 28
+#define MP_AUDIO_IRQ 30
+
+static uint32_t gpio_in_state = 0xffffffff;
+static uint32_t gpio_out_state;
+static ram_addr_t sram_off;
+
+/* Address conversion helpers */
+static void *target2host_addr(uint32_t addr)
+{
+ if (addr < MP_SRAM_BASE) {
+ if (addr >= MP_RAM_DEFAULT_SIZE)
+ return NULL;
+ return (void *)(phys_ram_base + addr);
+ } else {
+ if (addr >= MP_SRAM_BASE + MP_SRAM_SIZE)
+ return NULL;
+ return (void *)(phys_ram_base + sram_off + addr - MP_SRAM_BASE);
+ }
+}
+
+static uint32_t host2target_addr(void *addr)
+{
+ if (addr < ((void *)phys_ram_base) + sram_off)
+ return (unsigned long)addr - (unsigned long)phys_ram_base;
+ else
+ return (unsigned long)addr - (unsigned long)phys_ram_base -
+ sram_off + MP_SRAM_BASE;
+}
+
+
+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 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)
+{
+ if (!i2c)
+ return;
+
+ 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)
+{
+ if (!i2c)
+ return 0;
+
+ switch (i2c->state) {
+ case RECEIVING_BIT7 ... RECEIVING_BIT0:
+ return (i2c->buffer >> 7);
+
+ case WAITING_FOR_ACK:
+ default:
+ return 0;
+ }
+}
+
+static i2c_interface *mixer_i2c;
+
+#ifdef HAS_AUDIO
+
+/* Audio register offsets */
+#define MP_AUDIO_PLAYBACK_MODE 0x00
+#define MP_AUDIO_CLOCK_DIV 0x18
+#define MP_AUDIO_IRQ_STATUS 0x20
+#define MP_AUDIO_IRQ_ENABLE 0x24
+#define MP_AUDIO_TX_START_LO 0x28
+#define MP_AUDIO_TX_THRESHOLD 0x2C
+#define MP_AUDIO_TX_STATUS 0x38
+#define MP_AUDIO_TX_START_HI 0x40
+
+/* Status register and IRQ enable bits */
+#define MP_AUDIO_TX_HALF (1 << 6)
+#define MP_AUDIO_TX_FULL (1 << 7)
+
+/* Playback mode bits */
+#define MP_AUDIO_16BIT_SAMPLE (1 << 0)
+#define MP_AUDIO_PLAYBACK_EN (1 << 7)
+#define MP_AUDIO_CLOCK_24MHZ (1 << 9)
+
+/* Wolfson 8750 I2C address */
+#define MP_WM_ADDR 0x34
+
+const char audio_name[] = "mv88w8618";
+
+typedef struct musicpal_audio_state {
+ uint32_t base;
+ qemu_irq irq;
+ uint32_t playback_mode;
+ uint32_t status;
+ uint32_t irq_enable;
+ unsigned long phys_buf;
+ void *target_buffer;
+ unsigned int threshold;
+ unsigned int play_pos;
+ unsigned int last_free;
+ uint32_t clock_div;
+ i2c_slave *wm;
+} musicpal_audio_state;
+
+static void audio_callback(void *opaque, int free_out, int free_in)
+{
+ musicpal_audio_state *s = opaque;
+ int16_t channel[2];
+ int pos, block_size;
+
+ if (!(s->playback_mode & MP_AUDIO_PLAYBACK_EN))
+ return;
+
+ if (s->playback_mode & MP_AUDIO_16BIT_SAMPLE)
+ free_out <<= 2;
+ else
+ free_out <<= 1;
+
+ block_size = s->threshold/2;
+ if (free_out - s->last_free < block_size)
+ return;
+
+ if (s->playback_mode & MP_AUDIO_16BIT_SAMPLE)
+ for (pos = 0; pos < block_size; pos += 4)
+ wm8750_dac_dat(s->wm,
+ *(uint32_t *)(s->target_buffer + s->play_pos + pos));
+ else
+ for (pos = 0; pos < block_size; pos += 2) {
+ channel[0] = cpu_to_le16(2 *
+ *(int8_t *)(s->target_buffer + s->play_pos + pos));
+ channel[1] = cpu_to_le16(2 *
+ *(int8_t *)(s->target_buffer + s->play_pos + pos + 1));
+ wm8750_dac_dat(s->wm, channel[0] | (channel[1] << 16));
+ }
+
+ s->last_free = free_out - block_size;
+
+ if (s->play_pos == 0) {
+ s->status |= MP_AUDIO_TX_HALF;
+ s->play_pos = block_size;
+ } else {
+ s->status |= MP_AUDIO_TX_FULL;
+ s->play_pos = 0;
+ }
+
+ if (s->status & s->irq_enable)
+ qemu_irq_raise(s->irq);
+}
+
+static uint32_t musicpal_audio_read(void *opaque, target_phys_addr_t offset)
+{
+ musicpal_audio_state *s = opaque;
+
+ offset -= s->base;
+ switch (offset) {
+ case MP_AUDIO_PLAYBACK_MODE:
+ return s->playback_mode;
+
+ case MP_AUDIO_CLOCK_DIV:
+ return s->clock_div;
+
+ case MP_AUDIO_IRQ_STATUS:
+ return s->status;
+
+ case MP_AUDIO_IRQ_ENABLE:
+ return s->irq_enable;
+
+ case MP_AUDIO_TX_STATUS:
+ return s->play_pos >> 2;
+
+ default:
+ return 0;
+ }
+}
+
+static void musicpal_audio_write(void *opaque, target_phys_addr_t offset,
+ uint32_t value)
+{
+ musicpal_audio_state *s = opaque;
+
+ offset -= s->base;
+ switch (offset) {
+ case MP_AUDIO_PLAYBACK_MODE:
+ if (value & MP_AUDIO_PLAYBACK_EN &&
+ !(s->playback_mode & MP_AUDIO_PLAYBACK_EN)) {
+ s->status = 0;
+ s->last_free = 0;
+ s->play_pos = 0;
+ }
+ s->playback_mode = value;
+ break;
+
+ case MP_AUDIO_CLOCK_DIV:
+ s->clock_div = value;
+ s->last_free = 0;
+ s->play_pos = 0;
+ break;
+
+ case MP_AUDIO_IRQ_STATUS:
+ s->status &= ~value;
+ break;
+
+ case MP_AUDIO_IRQ_ENABLE:
+ s->irq_enable = value;
+ if (s->status & s->irq_enable)
+ qemu_irq_raise(s->irq);
+ break;
+
+ case MP_AUDIO_TX_START_LO:
+ s->phys_buf = (s->phys_buf & 0xFFFF0000) | (value & 0xFFFF);
+ s->target_buffer = target2host_addr(s->phys_buf);
+ s->play_pos = 0;
+ s->last_free = 0;
+ break;
+
+ case MP_AUDIO_TX_THRESHOLD:
+ s->threshold = (value + 1) * 4;
+ break;
+
+ case MP_AUDIO_TX_START_HI:
+ s->phys_buf = (s->phys_buf & 0xFFFF) | (value << 16);
+ s->target_buffer = target2host_addr(s->phys_buf);
+ s->play_pos = 0;
+ s->last_free = 0;
+ break;
+ }
+}
+
+static void musicpal_audio_reset(void *opaque)
+{
+ musicpal_audio_state *s = opaque;
+
+ s->playback_mode = 0;
+ s->status = 0;
+ s->irq_enable = 0;
+}
+
+static CPUReadMemoryFunc *musicpal_audio_readfn[] = {
+ musicpal_audio_read,
+ musicpal_audio_read,
+ musicpal_audio_read
+};
+
+static CPUWriteMemoryFunc *musicpal_audio_writefn[] = {
+ musicpal_audio_write,
+ musicpal_audio_write,
+ musicpal_audio_write
+};
+
+static i2c_interface *musicpal_audio_init(uint32_t base, qemu_irq irq)
+{
+ AudioState *audio;
+ musicpal_audio_state *s;
+ i2c_interface *i2c;
+ int iomemtype;
+
+ audio = AUD_init();
+ if (!audio) {
+ AUD_log(audio_name, "No audio state\n");
+ return NULL;
+ }
+
+ s = qemu_mallocz(sizeof(musicpal_audio_state));
+ if (!s)
+ return NULL;
+ s->base = base;
+ s->irq = irq;
+
+ i2c = qemu_mallocz(sizeof(i2c_interface));
+ if (!i2c)
+ return NULL;
+ i2c->bus = i2c_init_bus();
+ i2c->current_addr = -1;
+
+ s->wm = wm8750_init(i2c->bus, audio);
+ if (!s->wm)
+ return NULL;
+ i2c_set_slave_address(s->wm, MP_WM_ADDR);
+ wm8750_data_req_set(s->wm, audio_callback, s);
+
+ iomemtype = cpu_register_io_memory(0, musicpal_audio_readfn,
+ musicpal_audio_writefn, s);
+ cpu_register_physical_memory(base, MP_AUDIO_SIZE, iomemtype);
+
+ qemu_register_reset(musicpal_audio_reset, s);
+
+ return i2c;
+}
+#else /* !HAS_AUDIO */
+static i2c_interface *musicpal_audio_init(uint32_t base, qemu_irq irq)
+{
+ return NULL;
+}
+#endif /* !HAS_AUDIO */
+
+/* Ethernet register offsets */
+#define MP_ETH_SMIR 0x010
+#define MP_ETH_PCXR 0x408
+#define MP_ETH_SDCMR 0x448
+#define MP_ETH_ICR 0x450
+#define MP_ETH_IMR 0x458
+#define MP_ETH_FRDP0 0x480
+#define MP_ETH_FRDP1 0x484
+#define MP_ETH_FRDP2 0x488
+#define MP_ETH_FRDP3 0x48C
+#define MP_ETH_CRDP0 0x4A0
+#define MP_ETH_CRDP1 0x4A4
+#define MP_ETH_CRDP2 0x4A8
+#define MP_ETH_CRDP3 0x4AC
+#define MP_ETH_CTDP0 0x4E0
+#define MP_ETH_CTDP1 0x4E4
+#define MP_ETH_CTDP2 0x4E8
+#define MP_ETH_CTDP3 0x4EC
+
+/* MII PHY access */
+#define MP_ETH_SMIR_DATA 0x0000FFFF
+#define MP_ETH_SMIR_ADDR 0x03FF0000
+#define MP_ETH_SMIR_OPCODE (1 << 26) /* Read value */
+#define MP_ETH_SMIR_RDVALID (1 << 27)
+
+/* PHY registers */
+#define MP_ETH_PHY1_BMSR 0x00210000
+#define MP_ETH_PHY1_PHYSID1 0x00410000
+#define MP_ETH_PHY1_PHYSID2 0x00610000
+
+#define MP_PHY_BMSR_LINK 0x0004
+#define MP_PHY_BMSR_AUTONEG 0x0008
+
+#define MP_PHY_88E3015 0x01410E20
+
+/* TX descriptor status */
+#define MP_ETH_TX_OWN (1 << 31)
+
+/* RX descriptor status */
+#define MP_ETH_RX_OWN (1 << 31)
+
+/* Interrupt cause/mask bits */
+#define MP_ETH_IRQ_RX_BIT 0
+#define MP_ETH_IRQ_RX (1 << MP_ETH_IRQ_RX_BIT)
+#define MP_ETH_IRQ_TXHI_BIT 2
+#define MP_ETH_IRQ_TXLO_BIT 3
+
+/* Port config bits */
+#define MP_ETH_PCXR_2BSM_BIT 28 /* 2-byte incoming suffix */
+
+/* SDMA command bits */
+#define MP_ETH_CMD_TXHI (1 << 23)
+#define MP_ETH_CMD_TXLO (1 << 22)
+
+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 smir;
+ 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;
+
+ for (i = 0; i < 4; i++) {
+ desc = s->cur_rx[i];
+ if (!desc)
+ continue;
+ do {
+ if (le32_to_cpu(desc->cmdstat) & MP_ETH_RX_OWN &&
+ 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(~MP_ETH_RX_OWN);
+ s->cur_rx[i] = target2host_addr(le32_to_cpu(desc->next));
+
+ s->icr |= MP_ETH_IRQ_RX;
+ 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]);
+ }
+}
+
+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) & MP_ETH_TX_OWN) {
+ qemu_send_packet(s->vc,
+ target2host_addr(le32_to_cpu(desc->buffer)),
+ le16_to_cpu(desc->bytes));
+ desc->cmdstat &= cpu_to_le32(~MP_ETH_TX_OWN);
+ s->icr |= 1 << (MP_ETH_IRQ_TXLO_BIT - 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 MP_ETH_SMIR:
+ if (s->smir & MP_ETH_SMIR_OPCODE) {
+ switch (s->smir & MP_ETH_SMIR_ADDR) {
+ case MP_ETH_PHY1_BMSR:
+ return MP_PHY_BMSR_LINK | MP_PHY_BMSR_AUTONEG |
+ MP_ETH_SMIR_RDVALID;
+ case MP_ETH_PHY1_PHYSID1:
+ return (MP_PHY_88E3015 >> 16) | MP_ETH_SMIR_RDVALID;
+ case MP_ETH_PHY1_PHYSID2:
+ return (MP_PHY_88E3015 & 0xFFFF) | MP_ETH_SMIR_RDVALID;
+ default:
+ return MP_ETH_SMIR_RDVALID;
+ }
+ }
+ return 0;
+
+ case MP_ETH_ICR:
+ return s->icr;
+
+ case MP_ETH_IMR:
+ return s->imr;
+
+ case MP_ETH_FRDP0 ... MP_ETH_FRDP3:
+ return host2target_addr(s->frx_queue[(offset - MP_ETH_FRDP0)/4]);
+
+ case MP_ETH_CRDP0 ... MP_ETH_CRDP3:
+ return host2target_addr(s->rx_queue[(offset - MP_ETH_CRDP0)/4]);
+
+ case MP_ETH_CTDP0 ... MP_ETH_CTDP3:
+ return host2target_addr(s->tx_queue[(offset - MP_ETH_CTDP0)/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 MP_ETH_SMIR:
+ s->smir = value;
+ break;
+
+ case MP_ETH_PCXR:
+ s->vlan_header = ((value >> MP_ETH_PCXR_2BSM_BIT) & 1) * 2;
+ break;
+
+ case MP_ETH_SDCMR:
+ if (value & MP_ETH_CMD_TXHI)
+ eth_send(s, 1);
+ if (value & MP_ETH_CMD_TXLO)
+ eth_send(s, 0);
+ if (value & (MP_ETH_CMD_TXHI | MP_ETH_CMD_TXLO) && s->icr & s->imr)
+ qemu_irq_raise(s->irq);
+ break;
+
+ case MP_ETH_ICR:
+ s->icr &= value;
+ break;
+
+ case MP_ETH_IMR:
+ s->imr = value;
+ if (s->icr & s->imr)
+ qemu_irq_raise(s->irq);
+ break;
+
+ case MP_ETH_FRDP0 ... MP_ETH_FRDP3:
+ s->frx_queue[(offset - MP_ETH_FRDP0)/4] = target2host_addr(value);
+ break;
+
+ case MP_ETH_CRDP0 ... MP_ETH_CRDP3:
+ s->rx_queue[(offset - MP_ETH_CRDP0)/4] =
+ s->cur_rx[(offset - MP_ETH_CRDP0)/4] = target2host_addr(value);
+ break;
+
+ case MP_ETH_CTDP0 ... MP_ETH_CTDP3:
+ s->tx_queue[(offset - MP_ETH_CTDP0)/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, MP_ETH_SIZE, iomemtype);
+}
+
+/* LCD register offsets */
+#define MP_LCD_IRQCTRL 0x180
+#define MP_LCD_IRQSTAT 0x184
+#define MP_LCD_SPICTRL 0x1ac
+#define MP_LCD_INST 0x1bc
+#define MP_LCD_DATA 0x1c0
+
+/* Mode magics */
+#define MP_LCD_SPI_DATA 0x00100011
+#define MP_LCD_SPI_CMD 0x00104011
+#define MP_LCD_SPI_INVALID 0x00000000
+
+/* Commmands */
+#define MP_LCD_INST_SETPAGE0 0xB0
+/* ... */
+#define MP_LCD_INST_SETPAGE7 0xB7
+
+#define MP_LCD_TEXTCOLOR 0xe0e0ff /* RRGGBB */
+
+typedef struct musicpal_lcd_state {
+ uint32_t base;
+ uint32_t mode;
+ uint32_t irqctrl;
+ 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 0x00000007: /* 0 */
+ return 0;
+
+ case 0x00020000: /* 1 */
+ return (tmp * 1) / 7;
+
+ case 0x00020001: /* 2 */
+ return (tmp * 2) / 7;
+
+ case 0x00040000: /* 3 */
+ return (tmp * 3) / 7;
+
+ case 0x00010006: /* 4 */
+ return (tmp * 4) / 7;
+
+ case 0x00020005: /* 5 */
+ return (tmp * 5) / 7;
+
+ case 0x00040003: /* 6 */
+ return (tmp * 6) / 7;
+
+ case 0x00030004: /* 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, MP_LCD_TEXTCOLOR);
+ 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 MP_LCD_IRQCTRL:
+ return s->irqctrl;
+
+ 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 MP_LCD_IRQCTRL:
+ s->irqctrl = value;
+ break;
+
+ case MP_LCD_SPICTRL:
+ if (value == MP_LCD_SPI_DATA || value == MP_LCD_SPI_CMD)
+ s->mode = value;
+ else
+ s->mode = MP_LCD_SPI_INVALID;
+ break;
+
+ case MP_LCD_INST:
+ if (value >= MP_LCD_INST_SETPAGE0 && value <= MP_LCD_INST_SETPAGE7) {
+ s->page = value - MP_LCD_INST_SETPAGE0;
+ s->page_off = 0;
+ }
+ break;
+
+ case MP_LCD_DATA:
+ if (s->mode == MP_LCD_SPI_CMD) {
+ if (value >= MP_LCD_INST_SETPAGE0 &&
+ value <= MP_LCD_INST_SETPAGE7) {
+ s->page = value - MP_LCD_INST_SETPAGE0;
+ s->page_off = 0;
+ }
+ } else if (s->mode == MP_LCD_SPI_DATA) {
+ s->video_ram[s->page*128 + s->page_off] = value;
+ s->page_off = (s->page_off + 1) & 127;
+ }
+ 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, MP_LCD_SIZE, iomemtype);
+
+ graphic_console_init(ds, lcd_refresh, NULL, NULL, NULL, s);
+ dpy_resize(ds, 128*3, 64*3);
+}
+
+/* PIC register offsets */
+#define MP_PIC_STATUS 0x00
+#define MP_PIC_ENABLE_SET 0x08
+#define MP_PIC_ENABLE_CLR 0x0C
+
+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));
+}
+
+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 MP_PIC_STATUS:
+ return s->level & s->enabled;
+
+ 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 MP_PIC_ENABLE_SET:
+ s->enabled |= value;
+ break;
+
+ case MP_PIC_ENABLE_CLR:
+ s->enabled &= ~value;
+ s->level &= ~value;
+ break;
+ }
+ mv88w8618_pic_update(s);
+}
+
+static void mv88w8618_pic_reset(void *opaque)
+{
+ mv88w8618_pic_state *s = opaque;
+
+ s->level = 0;
+ s->enabled = 0;
+}
+
+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, MP_PIC_SIZE, iomemtype);
+
+ qemu_register_reset(mv88w8618_pic_reset, s);
+
+ return qi;
+}
+
+/* PIT register offsets */
+#define MP_PIT_TIMER1_LENGTH 0x00
+/* ... */
+#define MP_PIT_TIMER4_LENGTH 0x0C
+#define MP_PIT_CONTROL 0x10
+#define MP_PIT_TIMER1_VALUE 0x14
+/* ... */
+#define MP_PIT_TIMER4_VALUE 0x20
+#define MP_BOARD_RESET 0x34
+
+/* Magic board reset value (probably some watchdog behind it) */
+#define MP_BOARD_RESET_MAGIC 0x10000
+
+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 MP_PIT_TIMER1_VALUE ... MP_PIT_TIMER4_VALUE:
+ t = s->timer[(offset-MP_PIT_TIMER1_VALUE) >> 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 MP_PIT_TIMER1_LENGTH ... MP_PIT_TIMER4_LENGTH:
+ t = s->timer[offset >> 2];
+ t->limit = value;
+ ptimer_set_limit(t->timer, t->limit, 1);
+ break;
+
+ case MP_PIT_CONTROL:
+ 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;
+
+ case MP_BOARD_RESET:
+ if (value == MP_BOARD_RESET_MAGIC)
+ qemu_system_reset_request();
+ 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;
+ /* Letting them all run at 1 MHz is likely just a pragmatic
+ * simplification. */
+ 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, MP_PIT_SIZE, iomemtype);
+}
+
+/* Flash config register offsets */
+#define MP_FLASHCFG_CFGR0 0x04
+
+typedef struct mv88w8618_flashcfg_state {
+ uint32_t base;
+ uint32_t cfgr0;
+} mv88w8618_flashcfg_state;
+
+static uint32_t mv88w8618_flashcfg_read(void *opaque,
+ target_phys_addr_t offset)
+{
+ mv88w8618_flashcfg_state *s = opaque;
+
+ offset -= s->base;
+ switch (offset) {
+ case MP_FLASHCFG_CFGR0:
+ return s->cfgr0;
+
+ default:
+ return 0;
+ }
+}
+
+static void mv88w8618_flashcfg_write(void *opaque, target_phys_addr_t offset,
+ uint32_t value)
+{
+ mv88w8618_flashcfg_state *s = opaque;
+
+ offset -= s->base;
+ switch (offset) {
+ case MP_FLASHCFG_CFGR0:
+ s->cfgr0 = value;
+ break;
+ }
+}
+
+static CPUReadMemoryFunc *mv88w8618_flashcfg_readfn[] = {
+ mv88w8618_flashcfg_read,
+ mv88w8618_flashcfg_read,
+ mv88w8618_flashcfg_read
+};
+
+static CPUWriteMemoryFunc *mv88w8618_flashcfg_writefn[] = {
+ mv88w8618_flashcfg_write,
+ mv88w8618_flashcfg_write,
+ mv88w8618_flashcfg_write
+};
+
+static void mv88w8618_flashcfg_init(uint32_t base)
+{
+ int iomemtype;
+ mv88w8618_flashcfg_state *s;
+
+ s = qemu_mallocz(sizeof(mv88w8618_flashcfg_state));
+ if (!s)
+ return;
+
+ s->base = base;
+ s->cfgr0 = 0xfffe4285; /* Default as set by U-Boot for 8 MB flash */
+ iomemtype = cpu_register_io_memory(0, mv88w8618_flashcfg_readfn,
+ mv88w8618_flashcfg_writefn, s);
+ cpu_register_physical_memory(base, MP_FLASHCFG_SIZE, iomemtype);
+}
+
+/* Various registers in the 0x80000000 domain */
+#define MP_BOARD_REVISION 0x2018
+
+#define MP_WLAN_MAGIC1 0xc11c
+#define MP_WLAN_MAGIC2 0xc124
+
+#define MP_GPIO_OE_LO 0xd008
+#define MP_GPIO_OUT_LO 0xd00c
+#define MP_GPIO_IN_LO 0xd010
+#define MP_GPIO_ISR_LO 0xd020
+#define MP_GPIO_OE_HI 0xd508
+#define MP_GPIO_OUT_HI 0xd50c
+#define MP_GPIO_IN_HI 0xd510
+#define MP_GPIO_ISR_HI 0xd520
+
+/* GPIO bits & masks */
+#define MP_GPIO_WHEEL_VOL (1 << 8)
+#define MP_GPIO_WHEEL_VOL_INV (1 << 9)
+#define MP_GPIO_WHEEL_NAV (1 << 10)
+#define MP_GPIO_WHEEL_NAV_INV (1 << 11)
+#define MP_GPIO_LCD_BRIGHTNESS 0x00070000
+#define MP_GPIO_BTN_FAVORITS (1 << 19)
+#define MP_GPIO_BTN_MENU (1 << 20)
+#define MP_GPIO_BTN_VOLUME (1 << 21)
+#define MP_GPIO_BTN_NAVIGATION (1 << 22)
+#define MP_GPIO_I2C_DATA_BIT 29
+#define MP_GPIO_I2C_DATA (1 << MP_GPIO_I2C_DATA_BIT)
+#define MP_GPIO_I2C_CLOCK_BIT 30
+
+/* LCD brightness bits in GPIO_OE_HI */
+#define MP_OE_LCD_BRIGHTNESS 0x0007
+
+static uint32_t musicpal_read(void *opaque, target_phys_addr_t offset)
+{
+ offset -= 0x80000000;
+ switch (offset) {
+ case MP_BOARD_REVISION:
+ return 0x0031;
+
+ case MP_GPIO_OE_HI: /* used for LCD brightness control */
+ return lcd_brightness & MP_OE_LCD_BRIGHTNESS;
+
+ case MP_GPIO_OUT_LO:
+ return gpio_out_state & 0xFFFF;
+ case MP_GPIO_OUT_HI:
+ return gpio_out_state >> 16;
+
+ case MP_GPIO_IN_LO:
+ return gpio_in_state & 0xFFFF;
+ case MP_GPIO_IN_HI:
+ /* Update received I2C data */
+ gpio_in_state = (gpio_in_state & ~MP_GPIO_I2C_DATA) |
+ (i2c_get_data(mixer_i2c) << MP_GPIO_I2C_DATA_BIT);
+ return gpio_in_state >> 16;
+
+ /* This is a simplification of reality */
+ case MP_GPIO_ISR_LO:
+ return ~gpio_in_state & 0xFFFF;
+ case MP_GPIO_ISR_HI:
+ return ~gpio_in_state >> 16;
+
+ /* Workaround to allow loading the binary-only wlandrv.ko crap
+ * from the original Freecom firmware. */
+ case MP_WLAN_MAGIC1:
+ return ~3;
+ case MP_WLAN_MAGIC2:
+ return -1;
+
+ default:
+ return 0;
+ }
+}
+
+static void musicpal_write(void *opaque, target_phys_addr_t offset,
+ uint32_t value)
+{
+ offset -= 0x80000000;
+ switch (offset) {
+ case MP_GPIO_OE_HI: /* used for LCD brightness control */
+ lcd_brightness = (lcd_brightness & MP_GPIO_LCD_BRIGHTNESS) |
+ (value & MP_OE_LCD_BRIGHTNESS);
+ break;
+
+ case MP_GPIO_OUT_LO:
+ gpio_out_state = (gpio_out_state & 0xFFFF0000) | (value & 0xFFFF);
+ break;
+ case MP_GPIO_OUT_HI:
+ gpio_out_state = (gpio_out_state & 0xFFFF) | (value << 16);
+ lcd_brightness = (lcd_brightness & 0xFFFF) |
+ (gpio_out_state & MP_GPIO_LCD_BRIGHTNESS);
+ i2c_state_update(mixer_i2c,
+ (gpio_out_state >> MP_GPIO_I2C_DATA_BIT) & 1,
+ (gpio_out_state >> MP_GPIO_I2C_CLOCK_BIT) & 1);
+ break;
+
+ }
+}
+
+/* Keyboard codes & masks */
+#define KEY_PRESSED 0x80
+#define KEY_CODE 0x7f
+
+#define KEYCODE_TAB 0x0f
+#define KEYCODE_ENTER 0x1c
+#define KEYCODE_F 0x21
+#define KEYCODE_M 0x32
+
+#define KEYCODE_EXTENDED 0xe0
+#define KEYCODE_UP 0x48
+#define KEYCODE_DOWN 0x50
+#define KEYCODE_LEFT 0x4b
+#define KEYCODE_RIGHT 0x4d
+
+static void musicpal_key_event(void *opaque, int keycode)
+{
+ qemu_irq irq = opaque;
+ uint32_t event = 0;
+ static int kbd_extended;
+
+ if (keycode == KEYCODE_EXTENDED) {
+ kbd_extended = 1;
+ return;
+ }
+
+ if (kbd_extended)
+ switch (keycode & KEY_CODE) {
+ case KEYCODE_UP:
+ event = MP_GPIO_WHEEL_NAV | MP_GPIO_WHEEL_NAV_INV;
+ break;
+
+ case KEYCODE_DOWN:
+ event = MP_GPIO_WHEEL_NAV;
+ break;
+
+ case KEYCODE_LEFT:
+ event = MP_GPIO_WHEEL_VOL | MP_GPIO_WHEEL_VOL_INV;
+ break;
+
+ case KEYCODE_RIGHT:
+ event = MP_GPIO_WHEEL_VOL;
+ break;
+ }
+ else
+ switch (keycode & KEY_CODE) {
+ case KEYCODE_F:
+ event = MP_GPIO_BTN_FAVORITS;
+ break;
+
+ case KEYCODE_TAB:
+ event = MP_GPIO_BTN_VOLUME;
+ break;
+
+ case KEYCODE_ENTER:
+ event = MP_GPIO_BTN_NAVIGATION;
+ break;
+
+ case KEYCODE_M:
+ event = MP_GPIO_BTN_MENU;
+ break;
+ }
+
+ if (keycode & KEY_PRESSED)
+ gpio_in_state |= event;
+ else if (gpio_in_state & event) {
+ gpio_in_state &= ~event;
+ qemu_irq_raise(irq);
+ }
+
+ kbd_extended = 0;
+}
+
+static CPUReadMemoryFunc *musicpal_readfn[] = {
+ musicpal_read,
+ musicpal_read,
+ musicpal_read,
+};
+
+static CPUWriteMemoryFunc *musicpal_writefn[] = {
+ musicpal_write,
+ musicpal_write,
+ musicpal_write,
+};
+
+static struct arm_boot_info musicpal_binfo = {
+ .loader_start = 0x0,
+ .board_id = 0x20e,
+};
+
+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);
+
+ /* For now we use a fixed - the original - RAM size */
+ cpu_register_physical_memory(0, MP_RAM_DEFAULT_SIZE,
+ qemu_ram_alloc(MP_RAM_DEFAULT_SIZE));
+
+ sram_off = qemu_ram_alloc(MP_SRAM_SIZE);
+ cpu_register_physical_memory(MP_SRAM_BASE, MP_SRAM_SIZE, sram_off);
+
+ /* Catch various stuff not handled by separate subsystems */
+ iomemtype = cpu_register_io_memory(0, musicpal_readfn,
+ musicpal_writefn, first_cpu);
+ cpu_register_physical_memory(0x80000000, 0x10000, iomemtype);
+
+ pic = mv88w8618_pic_init(MP_PIC_BASE, pic[ARM_PIC_CPU_IRQ]);
+ mv88w8618_pit_init(MP_PIT_BASE, pic, MP_TIMER1_IRQ);
+
+ if (serial_hds[0])
+ serial_mm_init(MP_UART1_BASE, 2, pic[MP_UART1_IRQ], 1825000,
+ serial_hds[0], 1);
+ if (serial_hds[1])
+ serial_mm_init(MP_UART2_BASE, 2, pic[MP_UART2_IRQ], 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 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-MP_FLASH_SIZE_MAX, qemu_ram_alloc(flash_size),
+ drives_table[index].bdrv, 0x10000,
+ (flash_size + 0xffff) >> 16,
+ MP_FLASH_SIZE_MAX / flash_size,
+ 2, 0x00BF, 0x236D, 0x0000, 0x0000,
+ 0x5555, 0x2AAA);
+ }
+ mv88w8618_flashcfg_init(MP_FLASHCFG_BASE);
+
+ musicpal_lcd_init(ds, MP_LCD_BASE);
+
+ qemu_add_kbd_event_handler(musicpal_key_event, pic[MP_GPIO_IRQ]);
+
+ /*
+ * Wait a bit to catch menu button during U-Boot start-up
+ * (to trigger emergency update).
+ */
+ sleep(1);
+
+ mv88w8618_eth_init(&nd_table[0], MP_ETH_BASE, pic[MP_ETH_IRQ]);
+
+ mixer_i2c = musicpal_audio_init(MP_AUDIO_BASE, pic[MP_AUDIO_IRQ]);
+
+ musicpal_binfo.ram_size = MP_RAM_DEFAULT_SIZE;
+ musicpal_binfo.kernel_filename = kernel_filename;
+ musicpal_binfo.kernel_cmdline = kernel_cmdline;
+ musicpal_binfo.initrd_filename = initrd_filename;
+ arm_load_kernel(first_cpu, &musicpal_binfo);
+}
+
+QEMUMachine musicpal_machine = {
+ "musicpal",
+ "Marvell 88w8618 / MusicPal (ARM926EJ-S)",
+ musicpal_init,
+ MP_RAM_DEFAULT_SIZE + MP_SRAM_SIZE + MP_FLASH_SIZE_MAX + RAMSIZE_FIXED
+};
^ permalink raw reply [flat|nested] 23+ messages in thread
* Re: [Qemu-devel] Re: [RFC][PATCH 4/4] Add support for Marvell 88w8618 / MusicPal
2008-04-20 15:52 ` Jan Kiszka
@ 2008-04-20 17:38 ` andrzej zaborowski
0 siblings, 0 replies; 23+ messages in thread
From: andrzej zaborowski @ 2008-04-20 17:38 UTC (permalink / raw)
To: qemu-devel
On 20/04/2008, Jan Kiszka <jan.kiszka@web.de> wrote:
> andrzej zaborowski wrote:
> > On 19/04/2008, Jan Kiszka <jan.kiszka@web.de> wrote:
> >> andrzej zaborowski wrote:
> >> > On 18/04/2008, Jan Kiszka <jan.kiszka@web.de> wrote:
> >> >> Andrzej, as you have written the wm8750, do you already know where which
> >> >> volume level would have to be applied (open-coded or via some
> >> >> AUD_set_volume)? I'm currently only using LOUT2VOL, and I'm a bit lazy
> >> >> to study the datasheet /wrt all the mixer details.
> >> >
> >> > My idea was to open
> >> > http://www.wolfsonmicro.com/uploads/documents/en/WM8750.pdf and on the
> >> > first page every Wolfson datasheet has its diagram of all audio paths
> >> > (of which there are always too many) and then trace with my finger the
> >> > path between the source (the I2C or I2S interfaces) and the sink (the
> >> > analog output), and then multiply all the volume values that are
> >> > applied there (both analog and digital) and pass that to host mixer
> >> > through some functions in audio/ for the given SWVoice - but we don't
> >> > have any such functions and I'm ok with using the host mixer manually.
> >> > (VirtualBox has them implemented iirc) So yes, maybe it makes sense
> >> > to multiply the samples for the moment and use only LOUTnVOL /
> >> > ROUTnVOL values as these are used by the guests we're interested in.
> >>
> >>
> >> Done, and it finally works. One of the two quirks I found in wm8750 made
> >> the switch a bit hairy. Patches will follow.
> >
> > Thanks. I pushed the patch with fixes. Regarding the wm8750_fini
> > patch, I'll #if 0 it because it's possible that a board will have this
> > chip on something hotpluggable and will need to create and destroy it
> > various times and it's easy to miss something in the clean-up.
> > Regarding the volume patch, I'll make a look-up table at one point,
>
> Don't understand yet why (are you afraid of pow, libm, or float in
> general?), but if it helps to get things merged... ;)
I think the idea (not mine) was to not depend on it if not necessary,
like in this case, pow() is a rather heavy tool for the simple issue.
>
>
> > and then merge. Also, if we have 16-bit data and 7-bit volume scale
> > maybe we're fine with scalling only the most-significant-byte and
>
>
> Hmm, wasn't endianness about finding out which byte is most-significant
> and which not? :->
Yes, the thing is that WM8750's input endianness is known and the MSB
is always the same one. The host's endianness varies and to do the
multiplication in C we need the samples to be host-endian (but audio
functions do accept guest-endian).
>
>
> > avoiding endianness headaches (or maybe not). Nevertheless the
> > MusicPal emulator should be bootable without that.
> >
> >>
> >> >
> >> >>
> >> >> >>> - 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.
> >> >> >> You can use -m 150 or similar.
> >> >> >>
> >> >> >> Please also format the code similarly to rest of Qemu.
> >> >> >
> >> >> > That would just increase ram_size, thus won't help as I need memory
> >> >> > beyond it (here for the pflash in R/W mode).
> >> >
> >> > Yes, I had not looked at how ram_size was used in the musicpal board
> >> > initialisation, sorry.
> >> >
> >> >>
> >> >> OK, I see what you mean after looking at your N800 patches: You apply a
> >> >> fixed RAM size, leaving the rest of what the user provided via -m to
> >> >> SRAM and flash. Not optimal IMHO, you may sometimes also want to play
> >> >> with the RAM size even if the real devices has a fixed amount. And it is
> >> >> far from being intuitive as well.
> >> >
> >> > Yes, although you allow the user to set also a smaller RAM than what
> >> > the virtual machine expects.
> >>
> >>
> >> That's indeed something the machine should take of (if there are such
> >> hard limits).
> >>
> >>
> >> >
> >> >> The only true solution I see right now is moving qemu_vmalloc into the
> >> >> machine initialization code. Is there anything between current
> >> >> qemu_vmalloc and machine->init that relies on phys_ram_base being valid
> >> >> (and which can't be moved after the machine init) and thus prevents this?
> >> >
> >> > I had a different idea: add a field ram_constraint in struct
> >> > QEMUMachine, which would hold the amount of RAM the machine always
> >> > needs (e.g. bios and video RAM), and the low bit could hold a flag
> >> > RAM_SIZE_FIXED for machines that have only such RAM (basically the
> >> > criteria should be whether it's possible for the guest to detect the
> >> > memory size there is on board - on machines like Spitz there's no way)
> >>
> >>
> >> IIRC, embedded boards let the boot loader "detect" this. I see valid
> >> scenarios where one wants to play with different sizes and may therefore
> >> patch U-Boot - or load the kernel directly which should make QEMU set
> >> the related ATAG field appropriately, no?
> >
> > Yes, in case of a standard firmware like Linux or U-boot - but we
> > probably don't need to provide options for everything one may want to
> > play with unless it's a valid hardware configuration (like in the PC
> > case where you can add and take away RAM sticks), at some point the
> > user needs to edit the source either way.
> >
> > Anyway almost half of the boards in qemu ignored ram_size until now
> > and risked the provided size being too low and segfaulting, so with
> > the patch I sent in another mail at least there's a check, and the
> > check is only done once for all boards so it can be removed from the
> > few boards that did it.
> >
> >>
> >> > and for such machines the -m parameter would be invalid. I'll try to
> >> > come up with a patch.
> >>
> >>
> >> I originally had the same idea but I dropped it because it would still
> >> overload -m with semantics that don't belong there. IMHO -m should only
> >> describe the main RAM size, not any additionally by QEMU required memory
> >> for establishing fixed SRAM or even for backing up flash devices. That's
> >> at least what I would expect from this switch and what the documentation
> >> suggests as well so far.
> >
> > This property is not changed by the patch (I hope).
>
> Yes, it restores the original semantic, at least as long as
> RAMSIZE_FIXED is not set. That case is still a bit suboptimal as you
> have to provide pessimistic values, e.g. the maximum flash size that can
> be used. But I can live with it I guess.
You absolutely don't have to set RAMSIZE_FIXED if you want to use -m.
In the board init you can for example decide to have fixed ram size
and flash size decided by ram_size if that's what you need. As long
as there's only one -m allowed one of the sizes has to be hardcoded.
Thanks for the rework.
--
Please do not print this email unless absolutely necessary. Spread
environmental awareness.
^ permalink raw reply [flat|nested] 23+ messages in thread
end of thread, other threads:[~2008-04-20 17:38 UTC | newest]
Thread overview: 23+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2008-04-13 10:11 [Qemu-devel] [RFC][PATCH 4/4] Add support for Marvell 88w8618 / MusicPal Jan Kiszka
2008-04-13 20:52 ` 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
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).