From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mailman by lists.gnu.org with tmda-scanned (Exim 4.43) id 1JkzBY-0005wn-MH for qemu-devel@nongnu.org; Sun, 13 Apr 2008 06:11:44 -0400 Received: from exim by lists.gnu.org with spam-scanned (Exim 4.43) id 1JkzBX-0005uf-8T for qemu-devel@nongnu.org; Sun, 13 Apr 2008 06:11:44 -0400 Received: from [199.232.76.173] (helo=monty-python.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1JkzBX-0005uJ-1A for qemu-devel@nongnu.org; Sun, 13 Apr 2008 06:11:43 -0400 Received: from fmmailgate02.web.de ([217.72.192.227]) by monty-python.gnu.org with esmtp (Exim 4.60) (envelope-from ) id 1JkzBV-00045i-FR for qemu-devel@nongnu.org; Sun, 13 Apr 2008 06:11:42 -0400 Received: from smtp05.web.de (fmsmtp05.dlan.cinetic.de [172.20.4.166]) by fmmailgate02.web.de (Postfix) with ESMTP id 15446D8AC87D for ; Sun, 13 Apr 2008 12:11:41 +0200 (CEST) Received: from [88.65.41.105] (helo=[192.168.1.198]) by smtp05.web.de with asmtp (TLSv1:AES256-SHA:256) (WEB.DE 4.109 #226) id 1JkzBU-0001LK-00 for qemu-devel@nongnu.org; Sun, 13 Apr 2008 12:11:40 +0200 Message-ID: <4801DC59.1010403@web.de> Date: Sun, 13 Apr 2008 12:11:37 +0200 From: Jan Kiszka MIME-Version: 1.0 Content-Type: text/plain; charset=ISO-8859-15 Sender: jan.kiszka@web.de Content-Transfer-Encoding: quoted-printable Subject: [Qemu-devel] [RFC][PATCH 4/4] Add support for Marvell 88w8618 / MusicPal Reply-To: qemu-devel@nongnu.org List-Id: qemu-devel.nongnu.org List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , To: qemu-devel@nongnu.org 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=D764 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 --- Makefile.target | 1=20 hw/boards.h | 3=20 hw/musicpal.c | 1266 +++++++++++++++++++++++++++++++++++++++++++++++++= +++++++ sysemu.h | 2=20 vl.c | 1=20 5 files changed, 1272 insertions(+), 1 deletion(-) Index: b/Makefile.target =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D --- a/Makefile.target +++ b/Makefile.target @@ -612,6 +612,7 @@ OBJS+=3D spitz.o ide.o serial.o nand.o ecc OBJS+=3D omap1.o omap_lcdc.o omap_dma.o omap_clk.o omap_mmc.o omap_i2c.o OBJS+=3D palm.o tsc210x.o OBJS+=3D mst_fpga.o mainstone.o +OBJS+=3D musicpal.o pflash_cfi02.o CPPFLAGS +=3D -DHAS_AUDIO endif ifeq ($(TARGET_BASE_ARCH), sh4) Index: b/hw/boards.h =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D --- a/hw/boards.h +++ b/hw/boards.h @@ -101,4 +101,7 @@ extern QEMUMachine dummy_m68k_machine; /* mainstone.c */ extern QEMUMachine mainstone2_machine; =20 +/* musicpal.c */ +extern QEMUMachine musicpal_machine; + #endif Index: b/vl.c =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D --- 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 =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D --- a/sysemu.h +++ b/sysemu.h @@ -109,7 +109,7 @@ extern unsigned int nb_prom_envs; #endif =20 /* 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 =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D --- /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 + +#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 =3D 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 >=3D ram_size) + return NULL; + return (void *)(phys_ram_base + addr); + } else { + if (addr >=3D 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[] =3D "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 i= nt length) +{ + unsigned int pos; + double val; + + if (s->mute) { + memset(s->mixer_buffer, 0, length); + return; + } + + if (s->playback_mode & 1) + for (pos =3D 0; pos < length; pos +=3D 2) { + val =3D *(int16_t *)(s->target_buffer + s->play_pos + pos); + val =3D val * pow(10.0, s->volume/20.0); + *(int16_t *)(s->mixer_buffer + pos) =3D val; + } + else + for (pos =3D 0; pos < length; pos +=3D 1) { + val =3D *(int8_t *)(s->target_buffer + s->play_pos + pos); + val =3D val * pow(10.0, s->volume/20.0); + *(int8_t *)(s->mixer_buffer + pos) =3D val; + } +} + +static void sound_callback(void *opaque, int free) +{ + mv88w8618_sound_state *s =3D opaque; + uint32_t old_status =3D s->status; + int64_t now =3D qemu_get_clock(rt_clock); + unsigned int n; + + if (now - s->last_callback < (1000 * s->threshold/2) / (44100*2)) + return; + s->last_callback =3D now; + + while (free > 0) { + n =3D audio_MIN(s->threshold - s->play_pos, (unsigned int)free); + sound_fill_mixer_buffer(s, n); + n =3D AUD_write(s->voice, s->mixer_buffer, n); + if (!n) + break; + + s->play_pos +=3D n; + free -=3D n; + + if (s->play_pos >=3D s->threshold/2) + s->status |=3D 1 << 6; + if (s->play_pos =3D=3D s->threshold) { + s->status |=3D 1 << 7; + s->play_pos =3D 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 =3D mute; +} + +static void mv88w8618_sound_set_volume(mv88w8618_sound_state *s, int vol= ume) +{ + s->volume =3D volume; +} + +static uint32_t mv88w8618_sound_read(void *opaque, target_phys_addr_t of= fset) +{ + mv88w8618_sound_state *s =3D opaque; + + offset -=3D 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 offse= t, + uint32_t value) +{ + mv88w8618_sound_state *s =3D opaque; + + offset -=3D s->base; + switch (offset) { + case 0x00: /* Playback Mode */ + s->playback_mode =3D value; + if (value & (1 << 7)) { + audsettings_t as =3D {0, 2, 0, 0}; + + if (value & (1 << 9)) + as.freq =3D (24576000 / 64) / (s->clock_div + 1); + else + as.freq =3D (11289600 / 64) / (s->clock_div + 1); + if (value & 1) + as.fmt =3D AUD_FMT_S16; + else + as.fmt =3D AUD_FMT_S8; + + s->voice =3D 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 =3D 0; + s->status =3D 0; + } else if (s->voice) + AUD_set_active_out(s->voice, 0); + break; + + case 0x18: /* Clock Divider */ + s->clock_div =3D (value >> 8) & 0xFF; + break; + + case 0x20: /* Interrupt Status */ + s->status &=3D ~value; + break; + + case 0x24: /* Interrupt Enable */ + s->irq_enable =3D value; + if (s->status & s->irq_enable) + qemu_irq_raise(s->irq); + break; + + case 0x28: /* Tx Start Low */ + s->phys_buf =3D (s->phys_buf & 0xFFFF0000) | (value & 0xFFFF); + s->target_buffer =3D target2host_addr(s->phys_buf); + s->play_pos =3D 0; + break; + + case 0x2C: /* Tx Threshold */ + s->threshold =3D (value + 1) * 4; + break; + + case 0x40: /* Tx Start High */ + s->phys_buf =3D (s->phys_buf & 0xFFFF) | (value << 16); + s->target_buffer =3D target2host_addr(s->phys_buf); + s->play_pos =3D 0; + break; + } + +// printf("addr 0x%08lx, value 0x%08x\n", offset, value); +} + +static CPUReadMemoryFunc *mv88w8618_sound_readfn[] =3D { + mv88w8618_sound_read, + mv88w8618_sound_read, + mv88w8618_sound_read +}; + +static CPUWriteMemoryFunc *mv88w8618_sound_writefn[] =3D { + mv88w8618_sound_write, + mv88w8618_sound_write, + mv88w8618_sound_write +}; + +static mv88w8618_sound_state *mv88w8618_sound_init(uint32_t base, qemu_i= rq irq) +{ + AudioState *audio; + mv88w8618_sound_state *s; + int iomemtype; + + s =3D qemu_mallocz(sizeof(mv88w8618_sound_state)); + if (!s) + return NULL; + s->base =3D base; + s->irq =3D irq; + + audio =3D AUD_init(); + if (!audio) { + AUD_log(sound_name, "No audio state\n"); + return NULL; + } + AUD_register_card(audio, sound_name, &s->card); + + iomemtype =3D 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_i= rq 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 =3D opaque; + mv88w8618_rx_desc *desc; + int i; + +// printf("Receiving %d bytes\n", size); + for (i =3D 0; i < 4; i++) { + desc =3D s->cur_rx[i]; + if (!desc) + continue; + do { + if (le32_to_cpu(desc->cmdstat) & (1 << 31) && + le16_to_cpu(desc->buffer_size) >=3D size) { + memcpy(target2host_addr(le32_to_cpu(desc->buffer) + s->vlan_header), + buf, size); + desc->bytes =3D cpu_to_le16(size + s->vlan_header); + desc->cmdstat =3D cpu_to_le32(le32_to_cpu(desc->cmdstat) & ~(1 << 31= )); + s->cur_rx[i] =3D target2host_addr(le32_to_cpu(desc->next)); + + s->icr |=3D 1; + if (s->icr & s->imr) + qemu_irq_raise(s->irq); + return; + } + desc =3D target2host_addr(le32_to_cpu(desc->next)); + } while (desc !=3D s->rx_queue[i]); + } + printf(">>>Dropping packet!\n"); +} + +static void eth_send(mv88w8618_eth_state *s, int queue_index) +{ + mv88w8618_tx_desc *desc =3D 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 =3D cpu_to_le32(le32_to_cpu(desc->cmdstat) & ~(1 << 31)= ); + s->icr |=3D 1 << (3 - queue_index); + } + desc =3D target2host_addr(le32_to_cpu(desc->next)); + } while (desc !=3D s->tx_queue[queue_index]); +} + +static uint32_t mv88w8618_eth_read(void *opaque, target_phys_addr_t offs= et) +{ + mv88w8618_eth_state *s =3D opaque; + + offset -=3D 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 =3D opaque; + + offset -=3D s->base; + switch (offset) { + case 0x408: /* pcxr */ + s->vlan_header =3D (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 &=3D value; + break; + + case 0x458: /* imr */ + s->imr =3D value; + if (s->icr & s->imr) + qemu_irq_raise(s->irq); + break; + + case 0x480 ... 0x48c: /* frdp[0..3] */ + s->frx_queue[(offset - 0x480)/4] =3D target2host_addr(value); + break; + + case 0x4a0 ... 0x4ac: /* crdp[0..3] */ + s->rx_queue[(offset - 0x4a0)/4] =3D s->cur_rx[(offset - 0x4a0)/4] =3D + target2host_addr(value); + break; + + case 0x4e0 ... 0x4e4: /* ctdp[0..2] */ + s->tx_queue[(offset - 0x4e0)/4] =3D target2host_addr(value); + break; + } +} + +static CPUReadMemoryFunc *mv88w8618_eth_readfn[] =3D { + mv88w8618_eth_read, + mv88w8618_eth_read, + mv88w8618_eth_read +}; + +static CPUWriteMemoryFunc *mv88w8618_eth_writefn[] =3D { + 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 =3D qemu_mallocz(sizeof(mv88w8618_eth_state)); + if (!s) + return; + s->base =3D base; + s->irq =3D irq; + s->vc =3D qemu_new_vlan_client(nd->vlan, eth_receive, eth_can_receive, = s); + iomemtype =3D 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 =3D 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 =3D 0; dy < 3; dy++) + for (dx =3D 0; dx < 3; dx++) { + s->ds->data[(x*3 + dx + (y*3 + dy) * 128*3) * 4 + 0] =3D + scale_lcd_color(col); + s->ds->data[(x*3 + dx + (y*3 + dy) * 128*3) * 4 + 1] =3D + scale_lcd_color(col >> 8); + s->ds->data[(x*3 + dx + (y*3 + dy) * 128*3) * 4 + 2] =3D + scale_lcd_color(col >> 16); + } +} + +static void lcd_refresh(void *opaque) +{ + musicpal_lcd_state *s =3D opaque; + int x, y; + + for (x =3D 0; x < 128; x++) + for (y =3D 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 offse= t) +{ + musicpal_lcd_state *s =3D opaque; + + offset -=3D 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 =3D opaque; + + offset -=3D s->base; + switch (offset) { + case 0x1ac: + if (value =3D=3D 0x00100011) + s->mode =3D DATA; + else if (value =3D=3D 0x00104011) + s->mode =3D CMD; + else + s->mode =3D NONE; + break; + + case 0x1bc: + if (value >=3D 0xB0 && value <=3D 0xB7) { + s->page =3D value - 0xB0; + s->page_off =3D 0; + } + break; + + case 0x1c0: + if (s->mode =3D=3D CMD) { + if (value >=3D 0xB0 && value <=3D 0xB7) { + s->page =3D value - 0xB0; + s->page_off =3D 0; + } + } else if (s->mode =3D=3D DATA) { + s->video_ram[s->page*128 + s->page_off] =3D value; + s->page_off++; + } + break; + } +} + +static CPUReadMemoryFunc *musicpal_lcd_readfn[] =3D { + musicpal_lcd_read, + musicpal_lcd_read, + musicpal_lcd_read +}; + +static CPUWriteMemoryFunc *musicpal_lcd_writefn[] =3D { + 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 =3D qemu_mallocz(sizeof(musicpal_lcd_state)); + if (!s) + return; + s->base =3D base; + s->ds =3D ds; + iomemtype =3D 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 =3D opaque; + + if (level) + s->level |=3D 1 << irq; + else + s->level &=3D ~(1 << irq); + mv88w8618_pic_update(s); +} + +static uint32_t mv88w8618_pic_read(void *opaque, target_phys_addr_t offs= et) +{ + mv88w8618_pic_state *s =3D opaque; + + offset -=3D 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 =3D opaque; + + offset -=3D s->base; + switch (offset) { + case 0x08: + s->enabled |=3D value; + break; + + case 0x0C: + s->enabled &=3D ~value; + s->level &=3D ~value; + break; + } + mv88w8618_pic_update(s); +} + +static CPUReadMemoryFunc *mv88w8618_pic_readfn[] =3D { + mv88w8618_pic_read, + mv88w8618_pic_read, + mv88w8618_pic_read +}; + +static CPUWriteMemoryFunc *mv88w8618_pic_writefn[] =3D { + 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 =3D qemu_mallocz(sizeof(mv88w8618_pic_state)); + if (!s) + return NULL; + qi =3D qemu_allocate_irqs(mv88w8618_pic_set_irq, s, 32); + s->base =3D base; + s->parent_irq =3D parent_irq; + iomemtype =3D 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 =3D opaque; + + qemu_irq_raise(s->irq); +} + +static void *mv88w8618_timer_init(uint32_t freq, qemu_irq irq) +{ + mv88w8618_timer_state *s; + QEMUBH *bh; + + s =3D qemu_mallocz(sizeof(mv88w8618_timer_state)); + s->irq =3D irq; + s->freq =3D freq; + + bh =3D qemu_bh_new(mv88w8618_timer_tick, s); + s->timer =3D ptimer_init(bh); + + return s; +} + +static uint32_t mv88w8618_pit_read(void *opaque, target_phys_addr_t offs= et) +{ + mv88w8618_pit_state *s =3D opaque; + mv88w8618_timer_state *t; + + offset -=3D s->base; + switch (offset) { + case 0x14 ... 0x20: + t =3D 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 =3D opaque; + mv88w8618_timer_state *t; + int i; + + offset -=3D s->base; + switch (offset) { + case 0x00 ... 0x0c: + t =3D s->timer[offset >> 2]; + t->limit =3D value; + ptimer_set_limit(t->timer, t->limit, 1); + break; + + case 0x10: + for (i =3D 0; i < 4; i++) { + if (value & 0xf) { + t =3D s->timer[i]; + ptimer_set_limit(t->timer, t->limit, 0); + ptimer_set_freq(t->timer, t->freq); + ptimer_run(t->timer, 0); + } + value >>=3D 4; + } + break; + } +} + +static CPUReadMemoryFunc *mv88w8618_pit_readfn[] =3D { + mv88w8618_pit_read, + mv88w8618_pit_read, + mv88w8618_pit_read +}; + +static CPUWriteMemoryFunc *mv88w8618_pit_writefn[] =3D { + 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 =3D qemu_mallocz(sizeof(mv88w8618_pit_state)); + if (!s) + return; + + s->base =3D base; + s->timer[0] =3D mv88w8618_timer_init(1000000, pic[irq]); + s->timer[1] =3D mv88w8618_timer_init(1000000, pic[irq + 1]); + s->timer[2] =3D mv88w8618_timer_init(1000000, pic[irq + 2]); + s->timer[3] =3D mv88w8618_timer_init(1000000, pic[irq + 3]); + + iomemtype =3D cpu_register_io_memory(0, mv88w8618_pit_readfn, + mv88w8618_pit_writefn, s); + cpu_register_physical_memory(base, 0x00000034, iomemtype); +} + + +typedef enum i2c_state { + STOPPED =3D 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 >=3D 0) + i2c_end_transfer(i2c->bus); + i2c->current_addr =3D -1; + i2c->state =3D STOPPED; +} + +static void i2c_state_update(i2c_interface *i2c, int data, int clock) +{ + switch (i2c->state) { + case STOPPED: + if (data =3D=3D 0 && i2c->last_data =3D=3D 1 && clock =3D=3D 1) + i2c->state =3D INITIALIZING; + break; + + case INITIALIZING: + if (clock =3D=3D 0 && i2c->last_clock =3D=3D 1 && data =3D=3D 0) + i2c->state =3D SENDING_BIT7; + else + i2c_enter_stop(i2c); + break; + + case SENDING_BIT7 ... SENDING_BIT0: + if (clock =3D=3D 0 && i2c->last_clock =3D=3D 1) { + i2c->buffer =3D (i2c->buffer << 1) | data; + i2c->state++; /* will end up in WAITING_FOR_ACK */ + } else if (data =3D=3D 1 && i2c->last_data =3D=3D 0 && clock =3D=3D 1) + i2c_enter_stop(i2c); + break; + + case WAITING_FOR_ACK: + if (clock =3D=3D 0 && i2c->last_clock =3D=3D 1) { + if (i2c->current_addr < 0) { + i2c->current_addr =3D 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 =3D RECEIVING_BIT7; + i2c->buffer =3D i2c_recv(i2c->bus); + } else + i2c->state =3D SENDING_BIT7; + } else if (data =3D=3D 1 && i2c->last_data =3D=3D 0 && clock =3D=3D 1) + i2c_enter_stop(i2c); + break; + + case RECEIVING_BIT7 ... RECEIVING_BIT0: + if (clock =3D=3D 0 && i2c->last_clock =3D=3D 1) { + i2c->state++; /* will end up in SENDING_ACK */ + i2c->buffer <<=3D 1; + } else if (data =3D=3D 1 && i2c->last_data =3D=3D 0 && clock =3D=3D 1) + i2c_enter_stop(i2c); + break; + + case SENDING_ACK: + if (clock =3D=3D 0 && i2c->last_clock =3D=3D 1) { + i2c->state =3D RECEIVING_BIT7; + if (data =3D=3D 0) + i2c->buffer =3D i2c_recv(i2c->bus); + else + i2c_nack(i2c->bus); + } else if (data =3D=3D 1 && i2c->last_data =3D=3D 0 && clock =3D=3D 1) + i2c_enter_stop(i2c); + break; + } + + i2c->last_data =3D data; + i2c->last_clock =3D 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_RE= G_1 } mode; + uint8_t buffer; + int volume; +} wm8750_state; + +static void wm8750_i2c_event(i2c_slave *dev, enum i2c_event event) +{ + wm8750_state *s =3D (wm8750_state *)dev; + + switch (event) { + case I2C_START_SEND: + if (s->mode =3D=3D STANDBY) + s->mode =3D WRITING_REG_0; + break; + + case I2C_START_RECV: + if (s->mode =3D=3D STANDBY) + s->mode =3D READING_REG_0; + break; + + case I2C_FINISH: + s->mode =3D STANDBY; + break; + + default: + break; + } +} + +static int wm8750_i2c_recv(i2c_slave *dev) +{ +// wm8750_state *s =3D (wm8750_state *)dev; + +// printf("%s()\n", __FUNCTION__); + return 0; +} + +static int wm8750_i2c_send(i2c_slave *dev, uint8_t data) +{ + wm8750_state *s =3D (wm8750_state *)dev; + int vol; + + switch (s->mode) { + case WRITING_REG_0: + case READING_REG_0: + s->buffer =3D 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 =3D ((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 =3D qemu_mallocz(sizeof(i2c_interface)); + if (!i2c) + return NULL; + i2c->bus =3D i2c_init_bus(); + i2c->current_addr =3D -1; + + s =3D (wm8750_state *)i2c_slave_init(i2c->bus, 0x34, sizeof(wm8750_stat= e)); + if (!s) + return NULL; + s->slave.event =3D wm8750_i2c_event; + s->slave.recv =3D wm8750_i2c_recv; + s->slave.send =3D 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 =3D (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 =3D (gpio_out_state & 0xFFFF0000) | (value & 0xFFFF); + break; + + case 0x8000d50c: /* GPIO_OUT Hi / LCD Brightness */ + gpio_out_state =3D (gpio_out_state & 0xFFFF) | (value << 16); + lcd_brightness =3D (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 =3D (lcd_brightness & 0xFFFF) | ((value << 16) & 0x0007= 0000); + break; + + case 0x90009034: /* Reset */ + if (value & 0x10000) + qemu_system_reset_request(); + break; + } +} + +static CPUReadMemoryFunc *musicpal_readfn[] =3D { + musicpal_read, + musicpal_read, + musicpal_read, +}; + +static CPUWriteMemoryFunc *musicpal_writefn[] =3D { + musicpal_write, + musicpal_write, + musicpal_write, +}; + + +static void musicpal_key_event(void *opaque, int keycode) +{ + qemu_irq irq =3D opaque; + uint32_t event =3D 0; + static int kbd_escape; + + if (keycode =3D=3D 0xe0) { + kbd_escape =3D 1; + return; + } + + if (kbd_escape) + switch (keycode & 0x7F) { + case 0x48: /* Nav - */ + event =3D 0x00000c00; + break; + + case 0x50: /* Nav + */ + event =3D 0x00000400; + break; + + case 0x4b: /* Vol - */ + event =3D 0x00000300; + break; + + case 0x4d: /* Vol + */ + event =3D 0x00000100; + break; + } + else + switch (keycode & 0x7F) { + case 0x21: /* Fav */ + event =3D 0x00080000; + break; + + case 0x0f: /* Vol */ + event =3D 0x00200000; + break; + + case 0x1c: /* Nav */ + event =3D 0x00400000; + break; + + case 0x32: /* Menu */ + event =3D 0x00100000; + break; + + case 0x17: /* IR? */ + event =3D 0x04000000; + break; + } + + if (keycode & 0x80) + gpio_in_state |=3D event; + else if (gpio_in_state & event) { + gpio_in_state &=3D ~event; + qemu_irq_raise(irq); + } + + kbd_escape =3D 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 =3D "arm926"; + + env =3D cpu_init(cpu_model); + if (!env) { + fprintf(stderr, "Unable to find CPU definition\n"); + exit(1); + } + pic =3D arm_pic_init_cpu(env); + + cpu_register_physical_memory(0, ram_size, qemu_ram_alloc(ram_size)); + + sram_off =3D qemu_ram_alloc(SRAM_SIZE); + cpu_register_physical_memory(0xC0000000, SRAM_SIZE, sram_off); + + pic =3D mv88w8618_pic_init(0x90008000, pic[ARM_PIC_CPU_IRQ]); + mv88w8618_pit_init(0x90009000, pic, 4); + + iomemtype =3D cpu_register_io_memory(0, musicpal_readfn, musicpal_write= fn, 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 =3D drive_get_index(IF_PFLASH, 0, 0); + if (index !=3D -1) { + flash_size =3D bdrv_getlength(drives_table[index].bdrv); + if (flash_size !=3D 8*1024*1024 && flash_size !=3D 16*1024*1024 && + flash_size !=3D 32*1024*1024) { + fprintf(stderr, "Invalid flash image size\n"); + exit(1); + } + + /* + * The original U-Boot accesses the flash at 0xFE000000 (-32 MB) inste= ad of + * 0xFF800000 (if there is 8 MB flash). So remap flash access if the i= mage + * 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 =3D mv88w8618_sound_init(0x90007000, pic[30]); + mixer_i2c =3D musicpal_mixer_init(); + + arm_load_kernel(first_cpu, ram_size, kernel_filename, kernel_cmdline, + initrd_filename, 0x20e, 0x0); +} + +QEMUMachine musicpal_machine =3D { + "musicpal", + "Marvell 88w8618 / MusicPal (ARM926EJ-S)", + musicpal_init +};