diff -r ffa851df0825 -r 2fb8b9db1c86 symbian-qemu-0.9.1-12/qemu-symbian-svp/hw/musicpal.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/symbian-qemu-0.9.1-12/qemu-symbian-svp/hw/musicpal.c Fri Jul 31 15:01:17 2009 +0100 @@ -0,0 +1,1491 @@ +/* + * 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 "gui.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_isr; +static uint32_t gpio_out_state; +static ram_addr_t sram_off; + +/* Address conversion helpers */ +static void *target2host_addr(uint32_t addr) +{ + /* FIXME: This is broken if it spans multiple RAM regions. */ + if (addr < MP_SRAM_BASE) { + if (addr >= MP_RAM_DEFAULT_SIZE) + return NULL; + return host_ram_addr(addr); + } else { + if (addr >= MP_SRAM_BASE + MP_SRAM_SIZE) + return NULL; + return host_ram_addr(sram_off + addr - MP_SRAM_BASE); + } +} + +static uint32_t host2target_addr(void *addr) +{ + /* FIXME: This is broken if it spans multiple RAM regions. */ + ram_addr_t offset = ram_offset_from_host(addr); + if (offset < sram_off) + return offset; + else + return offset - 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) +#define MP_AUDIO_MONO (1 << 14) + +/* Wolfson 8750 I2C address */ +#define MP_WM_ADDR 0x34 + +static const char audio_name[] = "mv88w8618"; + +typedef struct musicpal_audio_state { + qemu_irq irq; + uint32_t playback_mode; + uint32_t status; + uint32_t irq_enable; + unsigned long phys_buf; + int8_t *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 *codec_buffer; + int8_t *mem_buffer; + int pos, block_size; + + if (!(s->playback_mode & MP_AUDIO_PLAYBACK_EN)) + return; + + if (s->playback_mode & MP_AUDIO_16BIT_SAMPLE) + free_out <<= 1; + + if (!(s->playback_mode & MP_AUDIO_MONO)) + free_out <<= 1; + + block_size = s->threshold/2; + if (free_out - s->last_free < block_size) + return; + + mem_buffer = s->target_buffer + s->play_pos; + if (s->playback_mode & MP_AUDIO_16BIT_SAMPLE) { + if (s->playback_mode & MP_AUDIO_MONO) { + codec_buffer = wm8750_dac_buffer(s->wm, block_size >> 1); + for (pos = 0; pos < block_size; pos += 2) { + *codec_buffer++ = *(int16_t *)mem_buffer; + *codec_buffer++ = *(int16_t *)mem_buffer; + mem_buffer += 2; + } + } else + memcpy(wm8750_dac_buffer(s->wm, block_size >> 2), + (uint32_t *)mem_buffer, block_size); + } else { + if (s->playback_mode & MP_AUDIO_MONO) { + codec_buffer = wm8750_dac_buffer(s->wm, block_size); + for (pos = 0; pos < block_size; pos++) { + *codec_buffer++ = cpu_to_le16(256 * *mem_buffer); + *codec_buffer++ = cpu_to_le16(256 * *mem_buffer++); + } + } else { + codec_buffer = wm8750_dac_buffer(s->wm, block_size >> 1); + for (pos = 0; pos < block_size; pos += 2) { + *codec_buffer++ = cpu_to_le16(256 * *mem_buffer++); + *codec_buffer++ = cpu_to_le16(256 * *mem_buffer++); + } + } + } + wm8750_dac_commit(s->wm); + + 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 void musicpal_audio_clock_update(musicpal_audio_state *s) +{ + int rate; + + if (s->playback_mode & MP_AUDIO_CLOCK_24MHZ) + rate = 24576000 / 64; /* 24.576MHz */ + else + rate = 11289600 / 64; /* 11.2896MHz */ + + rate /= ((s->clock_div >> 8) & 0xff) + 1; + + wm8750_set_bclk_in(s->wm, rate); +} + +static uint32_t musicpal_audio_read(void *opaque, target_phys_addr_t offset) +{ + musicpal_audio_state *s = opaque; + + 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; + + 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; + musicpal_audio_clock_update(s); + break; + + case MP_AUDIO_CLOCK_DIV: + s->clock_div = value; + s->last_free = 0; + s->play_pos = 0; + musicpal_audio_clock_update(s); + 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->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 { + 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; + + 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; + + 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->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 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; + } +} + +#define SET_LCD_PIXEL(depth, type) \ +static inline void glue(set_lcd_pixel, depth) \ + (musicpal_lcd_state *s, int x, int y, type col) \ +{ \ + int dx, dy; \ + type *pixel = &((type *) ds_get_data(s->ds))[(y * 128 * 3 + x) * 3]; \ +\ + for (dy = 0; dy < 3; dy++, pixel += 127 * 3) \ + for (dx = 0; dx < 3; dx++, pixel++) \ + *pixel = col; \ +} +SET_LCD_PIXEL(8, uint8_t) +SET_LCD_PIXEL(16, uint16_t) +SET_LCD_PIXEL(32, uint32_t) + +#include "pixel_ops.h" + +static void lcd_refresh(void *opaque) +{ + musicpal_lcd_state *s = opaque; + int x, y, col; + + switch (ds_get_bits_per_pixel(s->ds)) { + case 0: + return; +#define LCD_REFRESH(depth, func) \ + case depth: \ + col = func(scale_lcd_color((MP_LCD_TEXTCOLOR >> 16) & 0xff), \ + scale_lcd_color((MP_LCD_TEXTCOLOR >> 8) & 0xff), \ + scale_lcd_color(MP_LCD_TEXTCOLOR & 0xff)); \ + for (x = 0; x < 128; x++) \ + for (y = 0; y < 64; y++) \ + if (s->video_ram[x + (y/8)*128] & (1 << (y % 8))) \ + glue(set_lcd_pixel, depth)(s, x, y, col); \ + else \ + glue(set_lcd_pixel, depth)(s, x, y, 0); \ + break; + LCD_REFRESH(8, rgb_to_pixel8) + LCD_REFRESH(16, rgb_to_pixel16) + LCD_REFRESH(32, (ds_get_bgr(s->ds) ? rgb_to_pixel32bgr : rgb_to_pixel32)) + default: + cpu_abort(cpu_single_env, "unsupported colour depth %i\n", + ds_get_bits_per_pixel(s->ds)); + } + + dpy_update(s->ds, 0, 0, 128*3, 64*3); +} + +static void lcd_invalidate(void *opaque) +{ +} + +static uint32_t musicpal_lcd_read(void *opaque, target_phys_addr_t offset) +{ + musicpal_lcd_state *s = opaque; + + 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; + + 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; + iomemtype = cpu_register_io_memory(0, musicpal_lcd_readfn, + musicpal_lcd_writefn, s); + cpu_register_physical_memory(base, MP_LCD_SIZE, iomemtype); + + s->ds = gui_get_graphic_console(NULL, lcd_refresh, lcd_invalidate, + NULL, s); + gui_resize_vt(s->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 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; + + 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; + + 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->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; +} 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; + + 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; + + 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; + + /* 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 cfgr0; +} mv88w8618_flashcfg_state; + +static uint32_t mv88w8618_flashcfg_read(void *opaque, + target_phys_addr_t offset) +{ + mv88w8618_flashcfg_state *s = opaque; + + 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; + + 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->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) +{ + 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; + + case MP_GPIO_ISR_LO: + return gpio_isr & 0xFFFF; + case MP_GPIO_ISR_HI: + return gpio_isr >> 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) +{ + 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_RELEASED 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; + } + /* Do not repeat already pressed buttons */ + if (!(keycode & KEY_RELEASED) && !(gpio_in_state & event)) + event = 0; + } + + if (event) { + if (keycode & KEY_RELEASED) { + gpio_in_state |= event; + } else { + gpio_in_state &= ~event; + gpio_isr = 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(ram_addr_t 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, env); + 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); + + gui_register_dev_key_callback(musicpal_key_event, pic[MP_GPIO_IRQ]); + + 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(env, &musicpal_binfo); +} + +QEMUMachine musicpal_machine = { + .name = "musicpal", + .desc = "Marvell 88w8618 / MusicPal (ARM926EJ-S)", + .init = musicpal_init, + .ram_require = MP_RAM_DEFAULT_SIZE + MP_SRAM_SIZE + + MP_FLASH_SIZE_MAX + RAMSIZE_FIXED, +};