From b4e4af26e3daab7ae27ca3c01b459836a7770dfa Mon Sep 17 00:00:00 2001 From: Ricky Taylor Date: Fri, 8 Apr 2011 10:32:45 +0100 Subject: [PATCH] Merged cmwdotme's changes into the qemu git tree. --- Makefile.target | 3 + hw/iphone2g.c | 451 +++++++++++++++++++++++++++ hw/iphone2g.h | 62 ++++ hw/iphone2g_radio.c | 63 ++++ hw/pcf50633.c | 84 ++++++ hw/pl192.c | 589 ++++++++++++++++++++++++++++++++++++ hw/primecell.h | 5 + hw/s5l8900.c | 326 ++++++++++++++++++++ hw/s5l8900.h | 138 +++++++++ hw/s5l8900_i2c.c | 291 ++++++++++++++++++ hw/s5l8900_spi.c | 191 ++++++++++++ hw/s5l8900_uart.c | 398 ++++++++++++++++++++++++ hw/s5l8900_usb_otg.c | 705 +++++++++++++++++++++++++++++++++++++++++++ 13 files changed, 3306 insertions(+) create mode 100644 hw/iphone2g.c create mode 100644 hw/iphone2g.h create mode 100644 hw/iphone2g_radio.c create mode 100644 hw/pcf50633.c create mode 100644 hw/pl192.c create mode 100644 hw/s5l8900.c create mode 100644 hw/s5l8900.h create mode 100644 hw/s5l8900_i2c.c create mode 100644 hw/s5l8900_spi.c create mode 100644 hw/s5l8900_uart.c create mode 100644 hw/s5l8900_usb_otg.c diff --git a/Makefile.target b/Makefile.target index d5761b72f..8e28fbda0 100644 --- a/Makefile.target +++ b/Makefile.target @@ -352,6 +352,9 @@ obj-arm-y += syborg.o syborg_fb.o syborg_interrupt.o syborg_keyboard.o obj-arm-y += syborg_serial.o syborg_timer.o syborg_pointer.o syborg_rtc.o obj-arm-y += syborg_virtio.o obj-arm-y += vexpress.o +obj-arm-y += s5l8900.o iphone2g.o pcf50633.o s5l8900_uart.o s5l8900_spi.o pl192.o +obj-arm-y += s5l8900_i2c.o s5l8900_usb_otg.o + obj-sh4-y = shix.o r2d.o sh7750.o sh7750_regnames.o tc58128.o obj-sh4-y += sh_timer.o sh_serial.o sh_intc.o sh_pci.o sm501.o diff --git a/hw/iphone2g.c b/hw/iphone2g.c new file mode 100644 index 000000000..06c71d764 --- /dev/null +++ b/hw/iphone2g.c @@ -0,0 +1,451 @@ +/* + * iPhone2g support + * + * Written by cmw + * + * This code is licenced under the GPL. + */ + +#include "hw.h" +#include "arm-misc.h" +#include "sysemu.h" +#include "qemu-timer.h" +#include "devices.h" +#include "console.h" +#include "block.h" +#include "blockdev.h" +#include "boards.h" +#include "loader.h" +#include "flash.h" +#include "framebuffer.h" + +#include "iphone2g.h" +#include "s5l8900.h" + +#define VROM_BASE_ADDR 0x20000000 +#define IBOOT_BASE_ADDR 0x18000000 +#define RAM_BASE_ADDR 0x08000000 +#define RAM_HIGH_ADDR 0x80000000 +#define RAM_SIZE 0x08000000 +#define NOR_BASE_ADDR 0x24000000 + +uint32_t g_debug = (0 + //S5L8900_DEBUG_CLK + //0xffffffff | + ); +uint32_t g_dlevel = 0; +FILE *g_debug_fp = NULL; + +static target_phys_addr_t frame_base = 0; + +struct iphone2g_s { + struct s5l8900_state *cpu; +}; + +/**************************************************************************/ +/* LCD Module */ +/**************************************************************************/ + +#include "pixel_ops.h" +#define draw_line_func drawfn + +#define DEPTH 8 +#include "omap_lcd_template.h" +#define DEPTH 15 +#include "omap_lcd_template.h" +#define DEPTH 16 +#include "omap_lcd_template.h" +#define DEPTH 32 +#include "omap_lcd_template.h" + +static draw_line_func draw_line_table2[33] = { + [0 ... 32] = 0, + [8] = draw_line2_8, + [15] = draw_line2_15, + [16] = draw_line2_16, + [32] = draw_line2_32, +}, draw_line_table4[33] = { + [0 ... 32] = 0, + [8] = draw_line4_8, + [15] = draw_line4_15, + [16] = draw_line4_16, + [32] = draw_line4_32, +}, draw_line_table8[33] = { + [0 ... 32] = 0, + [8] = draw_line8_8, + [15] = draw_line8_15, + [16] = draw_line8_16, + [32] = draw_line8_32, +}, draw_line_table12[33] = { + [0 ... 32] = 0, + [8] = draw_line12_8, + [15] = draw_line12_15, + [16] = draw_line12_16, + [32] = draw_line12_32, +}, draw_line_table16[33] = { + [0 ... 32] = 0, + [8] = draw_line16_8, + [15] = draw_line16_15, + [16] = draw_line16_16, + [32] = draw_line16_32, +}; + +static void iphone2g_lcd_update_display(void *opaque) +{ + iphone2g_lcd_s *lcd = (iphone2g_lcd_s *) opaque; + draw_line_func draw_line; + int src_width, dest_width; + int height, first, last; + int width, linesize, bpp; + + (void)draw_line_table12; // Unused var. + + if (!lcd || !ds_get_bits_per_pixel(lcd->ds)) + return; + + switch (ds_get_bits_per_pixel(lcd->ds)) { + case 8: + dest_width = 1; + break; + case 15: + dest_width = 2; + break; + case 16: + dest_width = 2; + break; + case 24: + dest_width = 3; + break; + case 32: + dest_width = 4; + break; + default: + fprintf(stderr, "%s: Bad color depth\n", __FUNCTION__); + exit(1); + } + + /* Colour depth */ + switch (4) { + case 1: + draw_line = draw_line_table2[ds_get_bits_per_pixel(lcd->ds)]; + bpp = 2; + break; + + case 2: + draw_line = draw_line_table4[ds_get_bits_per_pixel(lcd->ds)]; + bpp = 4; + break; + + case 3: + draw_line = draw_line_table8[ds_get_bits_per_pixel(lcd->ds)]; + bpp = 8; + break; + + case 4 ... 7: + draw_line = draw_line_table16[ds_get_bits_per_pixel(lcd->ds)]; + bpp = 16; + break; + + default: + fprintf(stderr, "%s: Bad color depth\n", __FUNCTION__); + /* Unsupported at the moment. */ + return; + } + + /* Resolution */ + first = last = 0; + width = 320; + height = 480; + lcd->invalidate = 1; + + /* Content */ + if (!ds_get_bits_per_pixel(lcd->ds)) + return; + + src_width = width * bpp >> 3; + linesize = ds_get_linesize(lcd->ds); + + framebuffer_update_display(lcd->ds, frame_base, + width, height, + src_width, /* Length of source line, in bytes. */ + linesize, /* Bytes between adjacent horizontal output pixels. */ + dest_width, /* Bytes between adjacent vertical output pixels. */ + lcd->invalidate, + draw_line, lcd->palette, + &first, &last); + if (first >= 0) { + //fprintf(stderr, "dpy_update()\n"); + dpy_update(lcd->ds, 0, 0, width, height); + } + lcd->invalidate = 0; +} + +static void iphone2g_lcd_invalidate_display(void *opaque) { + iphone2g_lcd_s *iphone2g_lcd = opaque; + iphone2g_lcd->invalidate = 1; +} + +// Not implemented +static void iphone2g_lcd_screen_dump(void *opaque, const char *filename) { + +} + +// Not implemented +static uint32_t lcd_read(void *opaque, target_phys_addr_t offset) +{ + //iphone2g_lcd_s *s = (iphone2g_lcd_s *)opaque; + //fprintf(stderr, "%s: offset 0x%08x\n", __FUNCTION__, offset); + + return 0; +} + +// Not implemented +static void lcd_write(void *opaque, target_phys_addr_t offset, uint32_t value) +{ + //iphone2g_lcd_s *s = (iphone2g_lcd_s *)opaque; + //fprintf(stderr, "%s: offset 0x%08x value 0x%08x\n", __FUNCTION__, offset, value); +} + +static CPUReadMemoryFunc *lcd_readfn[] = { + lcd_read, + lcd_read, + lcd_read, +}; + +static CPUWriteMemoryFunc *lcd_writefn[] = { + lcd_write, + lcd_write, + lcd_write, +}; + +static iphone2g_lcd_s * iphone2g_lcd_init(target_phys_addr_t base) +{ + iphone2g_lcd_s *lcd = qemu_mallocz(sizeof(iphone2g_lcd_s)); + int io; + + io = cpu_register_io_memory(lcd_readfn, lcd_writefn, lcd, DEVICE_LITTLE_ENDIAN); + cpu_register_physical_memory(base, 0x7FF, io); + + lcd->ds = graphic_console_init(iphone2g_lcd_update_display, + iphone2g_lcd_invalidate_display, + iphone2g_lcd_screen_dump, NULL, lcd); + qemu_console_resize(lcd->ds, LCD_WIDTH, LCD_HEIGHT); + + return lcd; +} + +static uint32_t aes_read(void *opaque, target_phys_addr_t offset) +{ + struct iphone2g_aes_s *aesop = (struct iphone2g_aes_s *)opaque; + + fprintf(stderr, "%s: offset 0x%08x\n", __FUNCTION__, offset); + + switch(offset) { + case IPHONE2G_AES_STATUS: + return aesop->status; + default: + fprintf(stderr, "%s: UNMAPPED AES_ADDR @ offset 0x%08x\n", __FUNCTION__, offset); + } + + return 0; +} + +typedef struct cryptdata { + uint8_t crypt[0x80]; +} cryptdata; + +cryptdata cdata[] = +{ + {{0x93, 0x1E, 0x46, 0xB9, 0x79, 0x17, 0xd9, 0xFE, 0xA, 0x0, 0x1D, 0xAD, 0x10, 0x82, 0xA8, 0x15, 0x96, 0x4F, 0xDC, 0x24, 0x11, 0xAB, 0xCD, 0xA6, 0xDE, +0xDD, 0xE9, 0xDA, 0xCC, 0xB4, 0xE6, 0xD9, 0x5, 0x0}}, + {{0x6B, 0x84, 0x3, 0x34, 0x9E, 0x4, 0xCB, 0xDD, 0xFF, 0x69, 0x73, 0x40, 0x53, 0x60, 0xC, 0xF7, 0xC7, 0xF, 0x2C, 0x2, 0xD, 0xA3, 0x2A, 0xFD, 0xEA, 0x8E, 0xC8, 0xEC, 0x2D, 0xa, 0xF5, 0x5E, 0x5, 0x0}}, + {{0xBD, 0x9F, 0x26, 0xC7, 0xFD, 0xF3, 0xC3, 0xDF, 0xA2, 0xE7, 0x88, 0xDB, 0x48, 0x3B, 0x7C, 0x70, 0x57, 0xFF, 0xF2, 0x7F, 0x26, 0x65, 0x80, 0x4D, 0xB4, 0x2B, 0x48, 0x5F, 0x4, 0xDF, 0x31, 0xf, 0x5, 0x0}}, + {{0x8A, 0x37, 0x45, 0x45, 0xD7, 0x94, 0xCC, 0xC5, 0xD2, 0xE5, 0x5F, 0x51, 0x85, 0x12, 0xE3, 0x57, 0xA5, 0x6F, 0xFC, 0xF2, 0xBD, 0xE3, 0xF5, 0x39, 0x2, 0x14, 0x8F, 0x69, 0x49, 0x37, 0xA9, 0x1F, 0x5, 0x0}}, + {{0xF8, 0xCB, 0xEE, 0x5F, 0xB5, 0xB1, 0x2C, 0x25, 0xf, 0x5A, 0x7F, 0x45, 0xE9, 0xB8, 0x55, 0xAE, 0xFC, 0x6C, 0x69, 0x3E, 0x6E, 0x65, 0x4D, 0x69, 0x66, 0x33, 0x17, 0x67, 0xFC, 0x9E, 0x2A, 0x8, 0x5, 0x0}}, + {{0xE7, 0x25, 0x5, 0x46, 0xAC, 0x12, 0xEA, 0x24, 0x7F, 0x1D, 0xC1, 0x98, 0x72, 0x48, 0x69, 0x8F, 0xBC, 0x3A, 0x83, 0xc, 0xAB, 0xA2, 0xC8, 0xD6, 0x8E, 0xC2, 0x5E, 0xD3, 0xFD, 0x6, 0x2A, 0xE6, 0x5, 0x0}}, + {{0xC5, 0xDA, 0x96, 0xBD, 0x24, 0xBC, 0x53, 0x77, 0x61, 0x70, 0x4E, 0x84, 0x39, 0xBF, 0x18, 0x3C, 0x29, 0x2C, 0x1F, 0xD6, 0xE1, 0x66, 0x9C, 0xAD, 0x84, 0xDF, 0x4A, 0x12, 0xF2, 0x19, 0x12, 0x72, 0x5, 0x0}}, + {{0x18, 0x60, 0x8A, 0x5B, 0x1, 0x90, 0x3E, 0x77, 0xCB, 0xAE, 0xA7, 0xA8, 0xEF, 0xA6, 0xF6, 0xF0, 0xF7, 0x44, 0x6C, 0x5A, 0xd, 0x3D, 0xC6, 0xEE, 0x20, 0xB5, 0x7A, 0x11, 0xFD, 0x6F, 0x2C, 0x6F, 0x5, 0x0}}, + {{0x2F, 0x3C, 0x85, 0x5, 0x84, 0x40, 0xED, 0xA4, 0xF6, 0x6A, 0x1A, 0x78, 0x4F, 0x3F, 0x5D, 0x26, 0xD4, 0xEE, 0x80, 0x5C, 0x67, 0xF5, 0x92, 0x90, 0x37, 0x59, 0x3A, 0x1E, 0x19, 0x89, 0xB6, 0x38, 0x79, 0x58, 0x50, 0x5C, 0xEA, 0x8D, 0xED, 0x16, 0xB4, 0xA2, 0xA, 0xA7, 0x59, 0xC8, 0x29, 0x23, 0x87, 0x57, 0xC8, 0xD1, 0xD9, 0xC0, 0x98, 0x9D, 0xF5, 0xCF, 0x71, 0x9F, 0x20, 0xD8, 0x61, 0x3D, 0x11}}, + {{0x2F, 0x3C, 0x85, 0x5, 0x84, 0x40, 0xED, 0xA4, 0xF6, 0x6A, 0x1A, 0x78, 0x4F, 0x3F, 0x5D, 0x26, 0xD4, 0xEE, 0x80, 0x5C, 0x67, 0xF5, 0x92, 0x90, 0x37, 0x59, 0x3A, 0x1E, 0x19, 0x89, 0xB6, 0x38, 0x79, 0x58, 0x50, 0x5C, 0xEA, 0x8D, 0xED, 0x16, 0xB4, 0xA2, 0xA, 0xA7, 0x59, 0xC8, 0x29, 0x23, 0x87, 0x57, 0xC8, 0xD1, 0xD9, 0xC0, 0x98, 0x9D, 0xF5, 0xCF, 0x71, 0x9F, 0x20, 0xD8, 0x61, 0x3D, 0x11}}, + {{0x2F, 0x3C, 0x85, 0x5, 0x84, 0x40, 0xED, 0xA4, 0xF6, 0x6A, 0x1A, 0x78, 0x4F, 0x3F, 0x5D, 0x26, 0xD4, 0xEE, 0x80, 0x5C, 0x67, 0xF5, 0x92, 0x90, 0x37, 0x59, 0x3A, 0x1E, 0x19, 0x89, 0xB6, 0x38, 0x79, 0x58, 0x50, 0x5C, 0xEA, 0x8D, 0xED, 0x16, 0xB4, 0xA2, 0xA, 0xA7, 0x59, 0xC8, 0x29, 0x23, 0x87, 0x57, 0xC8, 0xD1, 0xD9, 0xC0, 0x98, 0x9D, 0xF5, 0xCF, 0x71, 0x9F, 0x20, 0xD8, 0x61, 0x3D, 0x11}}, + {{0x2F, 0x3C, 0x85, 0x5, 0x84, 0x40, 0xED, 0xA4, 0xF6, 0x6A, 0x1A, 0x78, 0x4F, 0x3F, 0x5D, 0x26, 0xD4, 0xEE, 0x80, 0x5C, 0x67, 0xF5, 0x92, 0x90, 0x37, 0x59, 0x3A, 0x1E, 0x19, 0x89, 0xB6, 0x38, 0x79, 0x58, 0x50, 0x5C, 0xEA, 0x8D, 0xED, 0x16, 0xB4, 0xA2, 0xA, 0xA7, 0x59, 0xC8, 0x29, 0x23, 0x87, 0x57, 0xC8, 0xD1, 0xD9, 0xC0, 0x98, 0x9D, 0xF5, 0xCF, 0x71, 0x9F, 0x20, 0xD8, 0x61, 0x3D, 0x11}} + +}; +static int ccount = 0; + +static void aes_write(void *opaque, target_phys_addr_t offset, + uint32_t value) +{ + struct iphone2g_aes_s *aesop = (struct iphone2g_aes_s *)opaque; + uint8_t inbuf[0x1000]; + uint8_t *buf; + uint32_t ctr; + + fprintf(stderr, "%s: offset 0x%08x value 0x%08x\n", __FUNCTION__, offset, value); + + switch(offset) { + case IPHONE2G_AES_GO: + fprintf(stderr, "%s: Received AES_GO lets do it\n", __FUNCTION__); + memset(aesop->ivec, 0, 16); + + memset(inbuf, 0, sizeof(inbuf)); + fprintf(stderr, "%s: AES_DECRYPT INADDR 0x%08x INSIZE 0x%08x OUTADDR 0x%08x\n", __FUNCTION__, aesop->inaddr, aesop->insize, aesop->outaddr); + + cpu_physical_memory_read((aesop->inaddr - 0x80000000), (uint8_t *)inbuf, aesop->insize); + for( ctr = 0; ctr < aesop->insize; ctr++ ) + { + fprintf(stderr, "%02x ", inbuf[ ctr ] ); + } + fprintf(stderr, "\n" ); + buf = (uint8_t *) malloc(aesop->insize); + memset(buf, 0, aesop->insize); + if(aesop->insize >= 0x20) + { + memcpy(buf, cdata[ccount].crypt, aesop->insize); + ccount++; + } else { + //AES_cbc_encrypt(inbuf, buf, aesop->insize, &aesop->decryptKey, aesop->ivec, AES_DECRYPT); + fprintf(stderr, "decrypting: "); + for( ctr = 0; ctr < aesop->insize; ctr++ ) + { + fprintf(stderr, "%02x ", buf[ ctr ] ); + } + } + cpu_physical_memory_write((aesop->outaddr - 0x80000000), buf, aesop->insize); + free(buf); + aesop->outsize = aesop->insize; + aesop->status = 0xf; + break; + case IPHONE2G_AES_INADDR: + aesop->inaddr = value; + break; + case IPHONE2G_AES_INSIZE: + aesop->insize = value; + break; + case IPHONE2G_AES_OUTSIZE: + aesop->outsize = value; + break; + case IPHONE2G_AES_OUTADDR: + aesop->outaddr = value; + break; + case IPHONE2G_AES_KEY ... ((IPHONE2G_AES_KEY + IPHONE2G_AES_KEYSIZE) - 1): + break; + case IPHONE2G_AES_IV ... ((IPHONE2G_AES_IV + IPHONE2G_AES_IVSIZE) -1 ): + break; + default: + fprintf(stderr, "%s: UNMAPPED AES_ADDR @ offset 0x%08x - 0x%08x\n", __FUNCTION__, offset, value); + } + +} + +static CPUReadMemoryFunc *aes_readfn[] = { + aes_read, + aes_read, + aes_read, +}; + +static CPUWriteMemoryFunc *aes_writefn[] = { + aes_write, + aes_write, + aes_write, +}; + +static void iphone2g_aes_init(target_phys_addr_t base) +{ + struct iphone2g_aes_s *aesop = (struct iphone2g_aes_s *) qemu_mallocz(sizeof(iphone2g_aes_s)); + int io; + + io = cpu_register_io_memory(aes_readfn, aes_writefn, aesop, DEVICE_LITTLE_ENDIAN); + cpu_register_physical_memory(base, 0xFF, io); + + //AES_set_decrypt_key(key_0x837, sizeof(key_0x837) * 8, &aesop->decryptKey); + +} + +static void iphone2g_init(ram_addr_t ram_size, + const char *boot_device, + const char *kernel_filename, const char *kernel_cmdline, + const char *initrd_filename, const char *cpu_model) +{ + s5l8900_state *cpu; + DriveInfo *dinfo; + uint32_t vrom_size, iboot_size; + ram_addr_t phys_flash, ramoff; + target_phys_addr_t vrom_base = VROM_BASE_ADDR; + target_phys_addr_t iboot_base = IBOOT_BASE_ADDR; + + struct iphone2g_s *s = (struct iphone2g_s *) qemu_mallocz(sizeof(*s)); + + g_debug_fp = stderr; + + cpu = s5l8900_init(); + + iboot_size = 0x140000; + + vrom_size = get_image_size(option_rom[1].name); + + if(vrom_size != 0) + { + cpu_register_physical_memory(vrom_base, (ram_addr_t)vrom_size,(phys_flash = qemu_ram_alloc(NULL, "iphone2g.vrom", vrom_size))); + load_image_targphys(option_rom[1].name, vrom_base, vrom_size); + frame_base = 0x0fe00000; + } else { + printf("No vrom assuming OIB\n"); + frame_base = 0x0fd00000; + } + + if(!get_image_size(option_rom[0].name)) + { + printf("A valid bootloader iboot|openiboot must be supplied with -option-rom\n"); + exit(1); + } + + cpu_register_physical_memory(iboot_base, (ram_addr_t)iboot_size,(phys_flash = qemu_ram_alloc(NULL, "iphone2g.iboot", iboot_size))); + load_image_targphys(option_rom[0].name, iboot_base, iboot_size); + + + ramoff = qemu_ram_alloc(NULL, "iphone2g.ram", RAM_SIZE); + /* Map in default ram space */ + cpu_register_physical_memory(RAM_BASE_ADDR, RAM_SIZE, ramoff | IO_MEM_RAM); + /* Also map higher ram space to default */ + cpu_register_physical_memory(RAM_HIGH_ADDR, RAM_SIZE, ramoff | IO_MEM_RAM); + /* Also also map to 0x0 as that's what OIB uses */ + cpu_register_physical_memory(0x0, RAM_SIZE, ramoff | IO_MEM_RAM); + + + dinfo = drive_get(IF_PFLASH, 0, 0); + + if (!dinfo) { + fprintf(stderr, "A NOR image must be given with the -pflash parameter\n"); + exit(1); + } + + if(!pflash_cfi02_register(NOR_BASE_ADDR, qemu_ram_alloc(NULL, "iphone2g.xor", 1024*1024), + dinfo->bdrv, 4096, 256, + 1, 2, 0x00bf, 0x273f, 0x0, 0x0, 0x555, 0x2aa, 0)) { + fprintf(stderr, "qemu: Error registering NOR flash.\n"); + exit(1); + } + + /* Init LCD */ + iphone2g_lcd_init(LCD_BASE_ADDR); + + /* Init AES hardware */ + iphone2g_aes_init(AES_BASE_ADDR); + + + cpu->env->regs[15] = IBOOT_BASE_ADDR; +} + +static QEMUMachine iphone2g_machine = { + .name = "iphone2g", + .desc = "iPhone 2G", + .init = iphone2g_init, +}; + +static void iphone2g_machine_init(void) +{ + qemu_register_machine(&iphone2g_machine); +} + +machine_init(iphone2g_machine_init); diff --git a/hw/iphone2g.h b/hw/iphone2g.h new file mode 100644 index 000000000..e980213a1 --- /dev/null +++ b/hw/iphone2g.h @@ -0,0 +1,62 @@ +#ifndef _IPHONE2G_H +#define _IPHONE2G_H + +#include +#include + +#define key_0x837 ((uint8_t[]){0x18, 0x84, 0x58, 0xA6, 0xD1, 0x50, 0x34, 0xDF, 0xE3, 0x86, 0xF2, 0x3B, 0x61, 0xD4, 0x37, 0x74}) + +#define LCD_BASE_ADDR 0x38900000 +#define LCD_WIDTH 320 +#define LCD_HEIGHT 480 + +#define AES_128_CBC_BLOCK_SIZE 64 +#define AES_BASE_ADDR 0x38C00000 + #define IPHONE2G_AES_CONTROL 0x0 + #define IPHONE2G_AES_GO 0x4 + #define IPHONE2G_AES_UNKREG0 0x8 + #define IPHONE2G_AES_STATUS 0xC + #define IPHONE2G_AES_UNKREG1 0x10 + #define IPHONE2G_AES_KEYLEN 0x14 + #define IPHONE2G_AES_INSIZE 0x18 + #define IPHONE2G_AES_INADDR 0x20 + #define IPHONE2G_AES_OUTSIZE 0x24 + #define IPHONE2G_AES_OUTADDR 0x28 + #define IPHONE2G_AES_AUXSIZE 0x2C + #define IPHONE2G_AES_AUXADDR 0x30 + #define IPHONE2G_AES_SIZE3 0x34 + #define IPHONE2G_AES_KEY 0x4C + #define IPHONE2G_AES_TYPE 0x6C + #define IPHONE2G_AES_IV 0x74 + #define IPHONE2G_AES_KEYSIZE 0x20 + #define IPHONE2G_AES_IVSIZE 0x10 + + + +typedef struct iphone2g_aes_s +{ + AES_KEY decryptKey; + uint8_t ivec[16]; + uint32_t insize; + uint32_t inaddr; + uint32_t outsize; + uint32_t outaddr; + uint32_t auxaddr; + uint32_t keytype; + uint32_t status; + uint32_t ctrl; + uint32_t unkreg0; + uint32_t unkreg1; + uint32_t keylen; +} iphone2g_aes_s; + + +typedef struct iphone2g_lcd_s { + DisplayState *ds; + uint16_t palette[256]; + int invalidate; + uint32_t lcd_ctrl; + qemu_irq irq; +} iphone2g_lcd_s; + +#endif diff --git a/hw/iphone2g_radio.c b/hw/iphone2g_radio.c new file mode 100644 index 000000000..c19832166 --- /dev/null +++ b/hw/iphone2g_radio.c @@ -0,0 +1,63 @@ +/* + * iPhone2g Radio Emulation + * + */ + +#include "hw.h" +#include "qemu-char.h" +#include "pc.h" +#include "sysemu.h" + +#include "iphone2g.h" + +typedef struct iphone2g_radio { + +} iphone2g_radio_s; + +static void serial_init_core(SerialState *s) +{ + if (!s->chr) { + fprintf(stderr, "Can't create radio device, empty char device\n"); + exit(1); + } + + qemu_register_reset(radio_reset, s); + + qemu_chr_add_handlers(s->chr, serial_can_receive1, serial_receive1, + serial_event, s); +} + +static void iphone2g_radio_init() +{ + DeviceState *dev = qdev_create(NULL, "s5l8900.uart"); + char str[] = "s5l8900.uart.00"; + S5L8900UartState *s = FROM_SYSBUS(S5L8900UartState, dev); + + s->base = base; + + if (!chr) { + fprintf(stderr, "openning char device"); + snprintf(str, strlen(str) + 1, "s5l8900.uart.%02d", instance % 100); + chr = qemu_chr_open(str, "null", NULL); + } + qdev_prop_set_chr(dev, "chr", chr); + qdev_prop_set_uint32(dev, "queue-size", queue_size); + qdev_prop_set_uint32(dev, "instance", instance); + qdev_init_nofail(dev); + sysbus_mmio_map(sysbus_from_qdev(dev), 0, base); + sysbus_connect_irq(sysbus_from_qdev(dev), 0, irq); + return dev; +} + +static ISADeviceInfo serial_isa_info = { + .qdev.name = "iphone2g_radio", + .qdev.size = sizeof(iphone2g_radio_s), + .init = iphone2g_radio_initfn, +}; + +static void radio_register_devices(void) +{ +} + +device_init(radio_register_devices) + diff --git a/hw/pcf50633.c b/hw/pcf50633.c new file mode 100644 index 000000000..9db5d92bc --- /dev/null +++ b/hw/pcf50633.c @@ -0,0 +1,84 @@ +/* + * PCF50633 emulation for S5L8900 + */ + +#include "sysemu.h" +#include "sysbus.h" +#include "smbus.h" +#include "s5l8900.h" +#include "qemu-timer.h" + + +typedef struct pcf50633State { + SMBusDevice smbusdev; + +} pcf50633State; + +static void pcf50633_reset(void *opaque) +{ + //pcf50633State *s = (pcf50633State *)opaque; + +} + +static void pcf50633_write_data(SMBusDevice *dev, uint8_t cmd, + uint8_t *buf, int len) +{ + //pcf50633State *s = (pcf50633State *)dev; + + fprintf(stderr, "%s: cmd 0x%08x len 0x%08x\n", __func__, cmd, len); + + switch (cmd) { + default: + // hw_error("pcf50633: bad write offset 0x%x\n", cmd); + break; + } + +} + +static uint8_t pcf50633_read_data(SMBusDevice *dev, uint8_t cmd, int n) +{ + //pcf50633State *s = (pcf50633State *)dev; + + fprintf(stderr, "%s: cmd 0x%08x n 0x%08x\n", __func__, cmd, n); + switch (cmd) { + default: + //hw_error("pcf50633: bad read offset 0x%x\n", cmd); + break; + } + + return 0; +} + +DeviceState *pcf50633_init(i2c_bus *bus, int addr) +{ + DeviceState *dev = qdev_create((BusState *)bus, "pcf50633"); + + fprintf(stderr, "Registering pcf50633\n"); + qdev_init_nofail(dev); + i2c_set_slave_address((i2c_slave *)dev, addr); + return dev; +} + +static int pcf50633_init1(SMBusDevice *dev) +{ + pcf50633State *s = (pcf50633State *) dev; + pcf50633_reset(s); + qemu_register_reset(pcf50633_reset, s); + + return 0; +} + +static SMBusDeviceInfo pcf50633_info = { + .i2c.qdev.name = "pcf50633", + .i2c.qdev.size = sizeof(pcf50633State), + .init = pcf50633_init1, + .write_data = pcf50633_write_data, + .read_data = pcf50633_read_data +}; + +static void s5l8900_pmu_register_devices(void) +{ + smbus_register_device(&pcf50633_info); +} + +device_init(s5l8900_pmu_register_devices) diff --git a/hw/pl192.c b/hw/pl192.c new file mode 100644 index 000000000..a39c2433b --- /dev/null +++ b/hw/pl192.c @@ -0,0 +1,589 @@ +/* + * ARM PrimeCell PL192 Vector Interrupt Controller + * + * Copyright (c) 2009 Samsung Electronics. + * Contributed by Kirill Batuzov + */ + +#include "sysbus.h" +#include "primecell.h" + + +#define PL192_INT_SOURCES 32 +#define PL192_DAISY_IRQ PL192_INT_SOURCES +#define PL192_NO_IRQ PL192_INT_SOURCES+1 +#define PL192_PRIO_LEVELS 16 + +#define PL192_IRQSTATUS 0x00 +#define PL192_FIQSTATUS 0x04 +#define PL192_RAWINTR 0x08 +#define PL192_INTSELECT 0x0C +#define PL192_INTENABLE 0x10 +#define PL192_INTENCLEAR 0x14 +#define PL192_SOFTINT 0x18 +#define PL192_SOFTINTCLEAR 0x1C +#define PL192_PROTECTION 0x20 +#define PL192_SWPRIORITYMASK 0x24 +#define PL192_PRIORITYDAISY 0x28 +#define PL192_VECTADDR 0xF00 +#define PL192_IOMEM_SIZE 0x1000 + +#define PL190_ITCR 0x300 +#define PL190_VECTADDR 0x30 +#define PL190_DEFVECTADDR 0x34 + +#define PL192_IOMEM_SIZE 0x1000 + + +typedef struct pl192_state_s { + SysBusDevice busdev; + uint32_t instance; + + /* Control registers */ + uint32_t irq_status; + uint32_t fiq_status; + uint32_t rawintr; + uint32_t intselect; + uint32_t intenable; + uint32_t softint; + uint32_t protection; + uint32_t sw_priority_mask; + uint32_t vect_addr[PL192_INT_SOURCES]; + uint32_t vect_priority[PL192_INT_SOURCES]; + uint32_t address; + + /* Currently processed interrupt and + highest priority interrupt */ + uint32_t current; + uint32_t current_highest; + + /* Priority masking logic */ + int32_t stack_i; + uint32_t priority_stack[PL192_PRIO_LEVELS+1]; + uint8_t irq_stack[PL192_PRIO_LEVELS+1]; + uint32_t priority; + + /* Daisy-chain interface */ + uint32_t daisy_vectaddr; + uint32_t daisy_priority; + struct pl192_state_s *daisy_callback; + uint8_t daisy_input; + + /* Parent interrupts */ + qemu_irq irq; + qemu_irq fiq; + + /* Next controller in chain */ + struct pl192_state_s *daisy; +} pl192_state; + +const unsigned char pl192_id[] = +{ 0x92, 0x11, 0x04, 0x00, 0x0D, 0xF0, 0x05, 0xB1 }; + + +static void pl192_update(pl192_state *); + +static void pl192_raise(pl192_state *s, int is_fiq) +{ + if (is_fiq) { + if (s->fiq) { + /* Raise parent FIQ */ + qemu_irq_raise(s->fiq); + } else { + if (s->daisy) { + /* FIQ is directly propagated through daisy chain */ + pl192_raise(s->daisy, is_fiq); + } else { + hw_error("pl192: cannot raise FIQ. This usually means that " + "initialization was done incorrectly.\n"); + } + } + } else { + if (s->irq) { + /* Raise parent IRQ */ + qemu_irq_raise(s->irq); + } else { + if (s->daisy) { + /* Setup daisy input of the next chained contorller and force + it to update it's state */ + s->daisy->daisy_vectaddr = s->address; + s->daisy->daisy_callback = s; + s->daisy->daisy_input = 1; + pl192_update(s->daisy); + } else { + hw_error("pl192: cannot raise IRQ. This usually means that " + "initialization was done incorrectly.\n"); + } + } + } +} + +static void pl192_lower(pl192_state *s, int is_fiq) +{ + /* Lower parrent interrupt if there is one */ + if (is_fiq && s->fiq) { + qemu_irq_lower(s->fiq); + } + if (!is_fiq && s->irq) { + qemu_irq_lower(s->irq); + } + /* Propagate to the previous controller in chain if needed */ + if (s->daisy) { + if (!is_fiq) { + s->daisy->daisy_input = 0; + pl192_update(s->daisy); + } else { + pl192_lower(s->daisy, is_fiq); + } + } +} + +/* Find interrupt of the highest priority */ +static uint32_t pl192_priority_sorter(pl192_state *s) +{ + int i; + uint32_t prio_irq[PL192_PRIO_LEVELS]; + + for (i = 0; i < PL192_PRIO_LEVELS; i++) { + prio_irq[i] = PL192_NO_IRQ; + } + if (s->daisy_input) { + prio_irq[s->daisy_priority] = PL192_DAISY_IRQ; + } + for (i = PL192_INT_SOURCES - 1; i >= 0; i--) { + if (s->irq_status & (1 << i)) { + prio_irq[s->vect_priority[i]] = i; + } + } + for (i = 0; i < PL192_PRIO_LEVELS; i++) { + if ((s->sw_priority_mask & (1 << i)) && + prio_irq[i] <= PL192_DAISY_IRQ) { + return prio_irq[i]; + } + } + return PL192_NO_IRQ; +} + +static void pl192_update(pl192_state *s) +{ + /* TODO: does SOFTINT affects IRQ_STATUS??? */ + s->irq_status = (s->rawintr | s->softint) & s->intenable & ~s->intselect; + s->fiq_status = (s->rawintr | s->softint) & s->intenable & s->intselect; + if (s->fiq_status) { + pl192_raise(s, 1); + } else { + pl192_lower(s, 1); + } + if (s->irq_status || s->daisy_input) { + s->current_highest = pl192_priority_sorter(s); + if (s->current_highest < PL192_INT_SOURCES) { + s->address = s->vect_addr[s->current_highest]; + } else { + s->address = s->daisy_vectaddr; + } + if (s->current_highest != s->current) { + if (s->current_highest < PL192_INT_SOURCES) { + if (s->vect_priority[s->current_highest] >= s->priority) { + return ; + } + } + if (s->current_highest == PL192_DAISY_IRQ) { + if (s->daisy_priority >= s->priority) { + return ; + } + } + if (s->current_highest <= PL192_DAISY_IRQ) { + pl192_raise(s, 0); + } else { + pl192_lower(s, 0); + } + } + } else { + s->current_highest = PL192_NO_IRQ; + pl192_lower(s, 0); + } +} + +/* Set priority level when an interrupt have been acknoledged by CPU. + Also save interrupt id and priority to stack so it can be restored + lately. */ +static inline void pl192_mask_priority(pl192_state *s) +{ + if (s->stack_i >= PL192_INT_SOURCES) { + hw_error("pl192: internal error\n"); + } + s->stack_i++; + if (s->current == PL192_DAISY_IRQ) { + s->priority = s->daisy_priority; + } else { + s->priority = s->vect_priority[s->current]; + } + s->priority_stack[s->stack_i] = s->priority; + s->irq_stack[s->stack_i] = s->current; +} + +/* Set priority level when interrupt have been successfully processed by CPU. + Also restore previous interrupt id and priority level. */ +static inline void pl192_unmask_priority(pl192_state *s) +{ + if (s->stack_i < 1) { + hw_error("pl192: internal error\n"); + } + s->stack_i--; + s->priority = s->priority_stack[s->stack_i]; + s->current = s->irq_stack[s->stack_i]; +} + +/* IRQ was acknoledged by CPU. Update controller state accordingly */ +static uint32_t pl192_irq_ack(pl192_state *s) +{ + int is_daisy = (s->current_highest == PL192_DAISY_IRQ); + uint32_t res = s->address; + + s->current = s->current_highest; + pl192_mask_priority(s); + if (is_daisy) { + pl192_mask_priority(s->daisy_callback); + } + pl192_update(s); + return res; +} + +/* IRQ was processed by CPU. Update controller state accrodingly */ +static void pl192_irq_fin(pl192_state *s) +{ + int is_daisy = (s->current == PL192_DAISY_IRQ); + + pl192_unmask_priority(s); + if (is_daisy) { + pl192_unmask_priority(s->daisy_callback); + } + pl192_update(s); + if (s->current == PL192_NO_IRQ) { + pl192_lower(s, 0); + } +} + +static uint32_t pl192_read(void *opaque, target_phys_addr_t offset) +{ + pl192_state *s = (pl192_state *) opaque; + + if (offset & 3) { + hw_error("pl192: bad read offset " TARGET_FMT_plx "\n", offset); + return 0; + } + + if (offset >= 0xfe0 && offset < 0x1000) { + return pl192_id[(offset - 0xfe0) >> 2]; + } + if (offset >= 0x100 && offset < 0x180) { + return s->vect_addr[(offset - 0x100) >> 2]; + } + if (offset >= 0x200 && offset < 0x280) { + return s->vect_priority[(offset - 0x200) >> 2]; + } + + switch (offset) { + case PL192_IRQSTATUS: + return s->irq_status; + case PL192_FIQSTATUS: + return s->fiq_status; + case PL192_RAWINTR: + return s->rawintr; + case PL192_INTSELECT: + return s->intselect; + case PL192_INTENABLE: + return s->intenable; + case PL192_SOFTINT: + return s->softint; + case PL192_PROTECTION: + return s->protection; + case PL192_SWPRIORITYMASK: + return s->sw_priority_mask; + case PL192_PRIORITYDAISY: + return s->daisy_priority; + case PL192_INTENCLEAR: + return 0; + case PL192_SOFTINTCLEAR: + hw_error("pl192: attempt to read write-only register (offset = " + TARGET_FMT_plx ")\n", offset); + case PL192_VECTADDR: + return pl192_irq_ack(s); + /* Workaround for kernel code using PL190 */ + case PL190_ITCR: + case PL190_VECTADDR: + case PL190_DEFVECTADDR: + return 0; + default: + hw_error("pl192: bad read offset " TARGET_FMT_plx "\n", offset); + return 0; + } +} + +static void pl192_write(void *opaque, target_phys_addr_t offset, + uint32_t value) +{ + pl192_state *s = (pl192_state *) opaque; + + if (offset & 3) { + hw_error("pl192: bad write offset " TARGET_FMT_plx "\n", offset); + } + + if (offset >= 0xfe0 && offset < 0x1000) { + hw_error("pl192: attempt to write to a read-only register (offset = " + TARGET_FMT_plx ")\n", offset); + } + if (offset >= 0x100 && offset < 0x180) { + s->vect_addr[(offset - 0x100) >> 2] = value; + pl192_update(s); + return; + } + if (offset >= 0x200 && offset < 0x280) { + s->vect_priority[(offset - 0x200) >> 2] = value & 0xf; + pl192_update(s); + return; + } + + switch (offset) { + case PL192_IRQSTATUS: + /* This is a readonly register, but linux tries to write to it + anyway. Ignore the write. */ + return; + case PL192_FIQSTATUS: + case PL192_RAWINTR: + hw_error("pl192: attempt to write to a read-only register (offset = " + TARGET_FMT_plx ")\n", offset); + break; + case PL192_INTSELECT: + s->intselect = value; + break; + case PL192_INTENABLE: + s->intenable |= value; + break; + case PL192_INTENCLEAR: + s->intenable &= ~value; + break; + case PL192_SOFTINT: + s->softint |= value; + break; + case PL192_SOFTINTCLEAR: + s->softint &= ~value; + break; + case PL192_PROTECTION: + /* TODO: implement protection */ + s->protection = value & 1; + break; + case PL192_SWPRIORITYMASK: + s->sw_priority_mask = value & 0xffff; + break; + case PL192_PRIORITYDAISY: + s->daisy_priority = value & 0xf; + break; + case PL192_VECTADDR: + pl192_irq_fin(s); + return; + case PL190_ITCR: + case PL190_VECTADDR: + case PL190_DEFVECTADDR: + /* NB: This thing is not present here, but linux wants to write it */ + /* Ignore written value */ + return; + default: + hw_error("pl192: bad write offset " TARGET_FMT_plx "\n", offset); + return; + } + + pl192_update(s); +} + +static void pl192_irq_handler(void *opaque, int irq, int level) +{ + pl192_state *s = (pl192_state *) opaque; + + if (level) { + s->rawintr |= 1 << irq; + } else { + s->rawintr &= ~(1 << irq); + } + pl192_update(opaque); +} + +static void pl192_reset(DeviceState *d) +{ + pl192_state *s = FROM_SYSBUS(pl192_state, sysbus_from_qdev(d)); + int i; + + for (i = 0; i < PL192_INT_SOURCES; i++) { + s->vect_priority[i] = 0xf; + } + s->sw_priority_mask = 0xffff; + s->daisy_priority = 0xf; + s->current = PL192_NO_IRQ; + s->current_highest = PL192_NO_IRQ; + s->stack_i = 0; + s->priority_stack[0] = 0x10; + s->irq_stack[0] = PL192_NO_IRQ; + s->priority = 0x10; +} + +static CPUReadMemoryFunc * const pl192_readfn[] = { + pl192_read, + pl192_read, + pl192_read +}; + +static CPUWriteMemoryFunc * const pl192_writefn[] = { + pl192_write, + pl192_write, + pl192_write +}; + +static void pl192_save(QEMUFile *f, void *opaque) +{ + pl192_state *s = (pl192_state *) opaque; + int i; + + qemu_put_be32s(f, &s->irq_status); + qemu_put_be32s(f, &s->fiq_status); + qemu_put_be32s(f, &s->rawintr); + qemu_put_be32s(f, &s->intselect); + qemu_put_be32s(f, &s->intenable); + qemu_put_be32s(f, &s->softint); + qemu_put_be32s(f, &s->protection); + qemu_put_be32s(f, &s->sw_priority_mask); + + for (i = 0; i < PL192_INT_SOURCES; i++) { + qemu_put_be32s(f, &s->vect_addr[i]); + qemu_put_be32s(f, &s->vect_priority[i]); + } + + qemu_put_be32s(f, &s->address); + qemu_put_be32s(f, &s->current); + qemu_put_be32s(f, &s->current_highest); + qemu_put_sbe32s(f, &s->stack_i); + + for (i = 0; i <= PL192_PRIO_LEVELS; i++) { + qemu_put_be32s(f, &s->priority_stack[i]); + qemu_put_8s (f, &s->irq_stack[i]); + } + + qemu_put_be32s(f, &s->priority); + qemu_put_be32s(f, &s->daisy_vectaddr); + qemu_put_be32s(f, &s->daisy_priority); + qemu_put_8s (f, &s->daisy_input); +} + +static int pl192_load(QEMUFile *f, void *opaque, int version_id) +{ + pl192_state *s = (pl192_state *) opaque; + int i; + + if (version_id != 1) { + return -EINVAL; + } + + qemu_get_be32s(f, &s->irq_status); + qemu_get_be32s(f, &s->fiq_status); + qemu_get_be32s(f, &s->rawintr); + qemu_get_be32s(f, &s->intselect); + qemu_get_be32s(f, &s->intenable); + qemu_get_be32s(f, &s->softint); + qemu_get_be32s(f, &s->protection); + qemu_get_be32s(f, &s->sw_priority_mask); + + for (i = 0; i < PL192_INT_SOURCES; i++) { + qemu_get_be32s(f, &s->vect_addr[i]); + qemu_get_be32s(f, &s->vect_priority[i]); + } + + qemu_get_be32s(f, &s->address); + qemu_get_be32s(f, &s->current); + qemu_get_be32s(f, &s->current_highest); + qemu_get_sbe32s(f, &s->stack_i); + + for (i = 0; i <= PL192_PRIO_LEVELS; i++) { + qemu_get_be32s(f, &s->priority_stack[i]); + qemu_get_8s (f, &s->irq_stack[i]); + } + + qemu_get_be32s(f, &s->priority); + qemu_get_be32s(f, &s->daisy_vectaddr); + qemu_get_be32s(f, &s->daisy_priority); + qemu_get_8s (f, &s->daisy_input); + + return 0; +} + +DeviceState *pl192_init(target_phys_addr_t base, int instance, ...) +{ + va_list va; + qemu_irq irq; + int n; + + DeviceState *dev = qdev_create(NULL, "pl192"); + SysBusDevice *s = sysbus_from_qdev(dev); + qdev_prop_set_uint32(dev, "instance", instance); + qdev_init_nofail(dev); + sysbus_mmio_map(s, 0, base); + + va_start(va, instance); + n = 0; + while (1) { + irq = va_arg(va, qemu_irq); + if (!irq) + break; + sysbus_connect_irq(s, n, irq); + n++; + } + return dev; +} + +static int pl192_init1(SysBusDevice *dev) +{ + pl192_state *s = FROM_SYSBUS(pl192_state, dev); + int iomemtype; + + sysbus_init_irq(dev, &s->irq); + sysbus_init_irq(dev, &s->fiq); + + /* Allocate IRQs */ + qdev_init_gpio_in(&dev->qdev, pl192_irq_handler, PL192_INT_SOURCES); + + /* Map Interrupt Controller registers to memory */ + iomemtype = cpu_register_io_memory(pl192_readfn, pl192_writefn, s, DEVICE_LITTLE_ENDIAN); + sysbus_init_mmio(dev, PL192_IOMEM_SIZE, iomemtype); + + /* TODO: Interrupt Controller coprocessor??? */ + pl192_reset(&s->busdev.qdev); + + register_savevm(&dev->qdev, "pl192", s->instance, 1, + pl192_save, pl192_load, s); + + return 0; +} + +static SysBusDeviceInfo pl192_info = { + .init = pl192_init1, + .qdev.name = "pl192", + .qdev.size = sizeof(pl192_state), + .qdev.reset = pl192_reset, + .qdev.props = (Property[]) { + DEFINE_PROP_UINT32("instance", pl192_state, instance, 0), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static void pl192_register_devices(void) +{ + sysbus_register_withprop(&pl192_info); +} + +void pl192_chain(void *first, void *next) +{ + pl192_state *s1 = FROM_SYSBUS(pl192_state, (SysBusDevice *)first); + pl192_state *s2 = FROM_SYSBUS(pl192_state, (SysBusDevice *)next); + + s2->daisy = s1; +} + +device_init(pl192_register_devices) diff --git a/hw/primecell.h b/hw/primecell.h index de7d6f2df..7e564ccf8 100644 --- a/hw/primecell.h +++ b/hw/primecell.h @@ -8,6 +8,11 @@ /* pl080.c */ void *pl080_init(uint32_t base, qemu_irq irq, int nchannels); +/* pl192.c */ +DeviceState *pl192_init(target_phys_addr_t base, + int instance, ...); +void pl192_chain(void *first, void *next); + /* arm_sysctl.c */ void arm_sysctl_init(uint32_t base, uint32_t sys_id, uint32_t proc_id); diff --git a/hw/s5l8900.c b/hw/s5l8900.c new file mode 100644 index 000000000..e11a2e5cd --- /dev/null +++ b/hw/s5l8900.c @@ -0,0 +1,326 @@ +/* + * Samsung S5L8900 processor support + * + * Written by cmw + * + * Uses code from Samsung S5pc1xx port + * + * This code is licenced under the GPL. + */ + +#include "hw.h" +#include "arm-misc.h" +#include "sysemu.h" +#include "qemu-char.h" +#include "qemu-timer.h" +#include "sysbus.h" +#include "primecell.h" +#include "devices.h" +#include "console.h" +#include "block.h" +#include "boards.h" +#include "s5l8900.h" +#include "net.h" +#include "i2c.h" + +typedef struct s5l8900_clk1_s +{ + uint32_t clk1_config0; + uint32_t clk1_config1; + uint32_t clk1_config2; + uint32_t clk1_pll1con; + uint32_t clk1_pll2con; + uint32_t clk1_pll3con; + uint32_t clk1_plllock; + uint32_t clk1_pllmode; + +} s5l8900_clk1_s; + + +typedef struct s5l8900_timer_s +{ + uint32_t ticks_high; + uint32_t ticks_low; + +} s5l8900_timer_s; + +static uint32_t s5l8900_timer1_read(void *opaque, target_phys_addr_t addr) +{ + s5l8900_timer_s *s = (struct s5l8900_timer_s *) opaque; + uint64_t ticks; + + S5L8900_DEBUG(S5L8900_DEBUG_CLK, S5L8900_DLVL_ERR, "%s: offset = 0x%02x\n", __FUNCTION__, (int)addr); + + switch (addr) { + case TIMER_TICKSHIGH: // needs to be fixed so that read from low first works as well + ticks = qemu_get_clock_ns(vm_clock); + s->ticks_high = (ticks >> 32); + s->ticks_low = (ticks & 0xFFFFFFFF); + return s->ticks_high; + case TIMER_TICKSLOW: + return s->ticks_low; + default: + S5L8900_DEBUG(S5L8900_DEBUG_CLK, S5L8900_DLVL_ERR, "%s: UNMAPPED offset = 0x%02x\n", __FUNCTION__, (int)addr); + } + return 0; +} + +static void s5l8900_timer1_write(void *opaque, target_phys_addr_t addr, uint32_t value) +{ + + S5L8900_DEBUG(S5L8900_DEBUG_CLK, S5L8900_DLVL_ERR, "%s: offset = 0x%02x\n", __FUNCTION__, (int)addr); + +} + +static CPUReadMemoryFunc *s5l8900_timer1_readfn[] = { + s5l8900_timer1_read, + s5l8900_timer1_read, + s5l8900_timer1_read, +}; + +static CPUWriteMemoryFunc *s5l8900_timer1_writefn[] = { + s5l8900_timer1_write, + s5l8900_timer1_write, + s5l8900_timer1_write, +}; + +static void s5l8900_timer_init(target_phys_addr_t base) +{ + struct s5l8900_timer_s *timer1 = (struct s5l8900_timer_s *) qemu_mallocz(sizeof(struct s5l8900_timer_s)); + + int iomemtype = cpu_register_io_memory(s5l8900_timer1_readfn, + s5l8900_timer1_writefn, timer1, DEVICE_LITTLE_ENDIAN); + S5L8900_OPAQUE("TIMER1", timer1); + timer1->ticks_high = 0; + timer1->ticks_low = 0; + cpu_register_physical_memory(base, 0xFF, iomemtype); +} + +static uint32_t s5l8900_clk1_read(void *opaque, target_phys_addr_t addr) +{ + s5l8900_clk1_s *s = (struct s5l8900_clk1_s *) opaque; + + S5L8900_DEBUG(S5L8900_DEBUG_CLK, S5L8900_DLVL_ERR, "%s: offset = 0x%02x\n", __FUNCTION__, (int)addr); + + switch (addr) { + case CLOCK1_CONFIG0: + return s->clk1_config0; + case CLOCK1_CONFIG1: + return s->clk1_config1; + case CLOCK1_CONFIG2: + return s->clk1_config2; + case CLOCK1_PLLLOCK: + return 1; + case CLOCK1_PLLMODE: + return s->clk1_pllmode; + + default: + S5L8900_DEBUG(S5L8900_DEBUG_CLK, S5L8900_DLVL_ERR, "%s: UNMAPPED offset = 0x%02x\n", __FUNCTION__, (int)addr); + } + return 0; +} + +static void s5l8900_clk1_write(void *opaque, target_phys_addr_t addr, uint32_t value) +{ + + S5L8900_DEBUG(S5L8900_DEBUG_CLK, S5L8900_DLVL_ERR, "%s: offset = 0x%02x\n", __FUNCTION__, (int)addr); + +} + +static CPUReadMemoryFunc *s5l8900_clk1_readfn[] = { + s5l8900_clk1_read, + s5l8900_clk1_read, + s5l8900_clk1_read, +}; + +static CPUWriteMemoryFunc *s5l8900_clk1_writefn[] = { + s5l8900_clk1_write, + s5l8900_clk1_write, + s5l8900_clk1_write, +}; + + +static uint32_t s5l8900_miu_read(void *opaque, target_phys_addr_t addr) +{ + S5L8900_DEBUG(S5L8900_DEBUG_MIU, S5L8900_DLVL_ERR, "%s: offset = 0x%02x\n", __FUNCTION__, (int)addr); + + switch (addr) { + case POWER_ID: + return (3 << 24); //for older iboots + //return (5 << 0x18); // new iboots + default: + S5L8900_DEBUG(S5L8900_DEBUG_MIU, S5L8900_DLVL_ERR, "%s: UNMAPPED offset = 0x%02x\n", __FUNCTION__, (int)addr); + } + return 0; +} + +static void s5l8900_miu_write(void *opaque, target_phys_addr_t addr, uint32_t value) +{ + + S5L8900_DEBUG(S5L8900_DEBUG_MIU, S5L8900_DLVL_ERR, "%s: offset = 0x%02x\n", __FUNCTION__, (int)addr); + +} + +static CPUReadMemoryFunc *s5l8900_miu_readfn[] = { + s5l8900_miu_read, + s5l8900_miu_read, + s5l8900_miu_read, +}; + +static CPUWriteMemoryFunc *s5l8900_miu_writefn[] = { + s5l8900_miu_write, + s5l8900_miu_write, + s5l8900_miu_write, +}; + +static void s5l8900_miu_init(target_phys_addr_t base) +{ + int iomemtype = cpu_register_io_memory(s5l8900_miu_readfn, + s5l8900_miu_writefn, NULL, DEVICE_LITTLE_ENDIAN); + cpu_register_physical_memory(base, 0x50, iomemtype); +} + + +static void s5l8900_clk_init(target_phys_addr_t base) +{ + struct s5l8900_clk1_s *clk1 = (struct s5l8900_clk1_s *) qemu_mallocz(sizeof(struct s5l8900_clk1_s)); + + int iomemtype = cpu_register_io_memory(s5l8900_clk1_readfn, + s5l8900_clk1_writefn, clk1, DEVICE_LITTLE_ENDIAN); + S5L8900_OPAQUE("clk1", clk1); + + cpu_register_physical_memory(base, 0xFF, iomemtype); +} + +static uint32_t s5l8900_chipid_read(void *opaque, target_phys_addr_t addr) +{ + + S5L8900_DEBUG(S5L8900_DEBUG_CHIPID, S5L8900_DLVL_WARN, "%s: offset = 0x%02x\n", __FUNCTION__, (int)addr); + + switch(addr) { + case 0x04: + return 0xfffffffc; + default: + S5L8900_DEBUG(S5L8900_DEBUG_CHIPID, S5L8900_DLVL_ERR, "%s: UNMAPPED offset = 0x%02x\n", __FUNCTION__, (int)addr); + } + + return 0; +} + +static CPUReadMemoryFunc *s5l8900_chipid_readfn[] = { + s5l8900_chipid_read, + s5l8900_chipid_read, + s5l8900_chipid_read, +}; + +static CPUWriteMemoryFunc *s5l8900_chipid_writefn[] = { + NULL, + NULL, + NULL, +}; + +static void s5l8900_chipid_init(target_phys_addr_t base) +{ + + int iomemtype = cpu_register_io_memory(s5l8900_chipid_readfn, + s5l8900_chipid_writefn, NULL, DEVICE_LITTLE_ENDIAN); + cpu_register_physical_memory(base, 0xF, iomemtype); + +} + +static inline qemu_irq s5l8900_get_irq(struct s5l8900_state_s *s, int n) +{ + return s->irq[n / S5L8900_VIC_SIZE][n % S5L8900_VIC_SIZE]; +} + +s5l8900_state *s5l8900_init(void) +{ + + s5l8900_state *s = (s5l8900_state *)qemu_mallocz(sizeof(s5l8900_state)); + i2c_bus *i2c; + + + qemu_irq *cpu_irq; + DeviceState *dev, *dev_prev; + int i,j; + + s->env = cpu_init("arm1136"); + if(!s->env) { + fprintf(stderr, "Unable to find CPU definition\n"); + exit(1); + } + + cpu_irq = arm_pic_init_cpu(s->env); + s->irq = qemu_mallocz(S5L8900_VIC_N * sizeof(qemu_irq *)); + dev = pl192_init(S5L8900_VIC_BASE, 0, + cpu_irq[ARM_PIC_CPU_IRQ], + cpu_irq[ARM_PIC_CPU_FIQ], NULL); + s->irq[0] = qemu_mallocz(S5L8900_VIC_SIZE * sizeof(qemu_irq)); + for (i = 0; i < S5L8900_VIC_SIZE; i++) + s->irq[0][i] = qdev_get_gpio_in(dev, i); + for (j = 1; j < S5L8900_VIC_N; j++) { + dev_prev = dev; + dev = pl192_init(S5L8900_VIC_BASE + S5L8900_VIC_SHIFT * j, j, NULL); + + s->irq[j] = qemu_mallocz(S5L8900_VIC_SIZE * sizeof(qemu_irq)); + for (i = 0; i < S5L8900_VIC_SIZE; i++) + s->irq[j][i] = qdev_get_gpio_in(dev, i); + pl192_chain(sysbus_from_qdev(dev_prev), sysbus_from_qdev(dev)); + } + + /* Chip info */ + s5l8900_chipid_init(S5L8900_CHIPID); + + /* CLKs */ + s5l8900_clk_init(CLOCK1); + + /* MIU */ + s5l8900_miu_init(MIU_BASE); + + /* Sytem Timer */ + s5l8900_timer_init(S5L8900_TIMER1); + + /* Uart */ + s5l8900_uart_init(S5L8900_UART0_BASE, 0, 256, s5l8900_get_irq(s, S5L8900_IRQ_UART0), serial_hds[0]); + + /* Uart + Radio */ + s5l8900_uart_init(S5L8900_UART1_BASE, 0, 256, s5l8900_get_irq(s, S5L8900_IRQ_UART0 /* XXX: fix irq for radio */), NULL); + + /* I2C */ + dev = sysbus_create_simple("s5l8900.i2c", S5l8900_I2C0_BASE, + s5l8900_get_irq(s, S5L8900_IRQ_I2C_0)); + i2c = (i2c_bus *)qdev_get_child_bus(dev, "i2c"); + + dev = sysbus_create_simple("s5l8900.i2c", S5l8900_I2C1_BASE, + s5l8900_get_irq(s, S5L8900_IRQ_I2C_1)); + i2c = (i2c_bus *)qdev_get_child_bus(dev, "i2c"); + + + /* PWU */ + pcf50633_init(i2c, PCF50633_ADDR_GET); + pcf50633_init(i2c, PCF50633_ADDR_SET); + + + /* SPI */ + set_spi_base(0); + sysbus_create_simple("s5l8900.spi", + S5L8900_SPI0_BASE, + s5l8900_get_irq(s, S5L8900_SPI0_IRQ)); + + set_spi_base(1); + sysbus_create_simple("s5l8900.spi", + S5L8900_SPI1_BASE, + s5l8900_get_irq(s, S5L8900_SPI1_IRQ)); + + set_spi_base(2); + sysbus_create_simple("s5l8900.spi", + S5L8900_SPI2_BASE, + s5l8900_get_irq(s, S5L8900_SPI2_IRQ)); + + /* USB-OTG */ + s5l8900_usb_otg_init(&nd_table[0], + S5L8900_USB_OTG_BASE, + s5l8900_get_irq(s, S5L8900_IRQ_OTG)); + + return s; +} diff --git a/hw/s5l8900.h b/hw/s5l8900.h new file mode 100644 index 000000000..3b1ebd933 --- /dev/null +++ b/hw/s5l8900.h @@ -0,0 +1,138 @@ +#ifndef S5L8900_H +#define S5L8900_H + +#include "qemu-timer.h" + +// Devices + +// PMU +#define PCF50633_ADDR_GET 0xe6 +#define PCF50633_ADDR_SET 0xe7 + +// MIU +#define MIU_BASE 0x39A00000 +#define POWER_ID 0x44 + +// Chip ID +#define S5L8900_CHIPID 0x3e500000 + +// UART +#define S5L8900_UART0_BASE 0x3cc00000 +#define S5L8900_UART1_BASE 0x3cc04000 +#define S5L8900_IRQ_UART0 24 + +// TIMERS +#define S5L8900_IRQ_TIMER0 7 +#define S5L8900_TIMER1 0x3E200000 +#define TIMER_TICKSHIGH 0x80 +#define TIMER_TICKSLOW 0x84 + +// VIC +#define S5l8900_I2C0_BASE 0x3C600000 +#define S5l8900_I2C1_BASE 0x3c900000 +#define S5L8900_IRQ_I2C_0 15 +#define S5L8900_IRQ_I2C_1 0 +#define S5L8900_VIC_N 2 +#define S5L8900_VIC_SIZE 32 +#define S5L8900_VIC_BASE 0x38E00000 +#define S5L8900_VIC_SHIFT 0x00001000 +#define S5L8900_VIC1_BASE 0x38E01000 + +#define VICIRQSTATUS 0x000 +#define VICRAWINTR 0x8 +#define VICINTSELECT 0xC +#define VICINTENABLE 0x10 +#define VICINTENCLEAR 0x14 +#define VICSWPRIORITYMASK 0x24 +#define VICVECTADDRS 0x100 +#define VICADDRESS 0xF00 +#define VICPERIPHID0 0xFE0 +#define VICPERIPHID1 0xFE4 +#define VICPERIPHID2 0xFE8 +#define VICPERIPHID3 0xFEC + +// USB +#define S5L8900_USB_PHY_BASE 0x3C400000 +#define S5L8900_USB_OTG_BASE 0x38400000 +#define S5L8900_IRQ_OTG 0x13 + +// SPI +#define S5L8900_SPI0_BASE 0x3C300000 +#define S5L8900_SPI1_BASE 0x3CE00000 +#define S5L8900_SPI2_BASE 0x3D200000 + +#define S5L8900_SPI0_IRQ 0x9 +#define S5L8900_SPI1_IRQ 0xA +#define S5L8900_SPI2_IRQ 0xB + +#define S5L8900_SPI_CONTROL 0x0 +#define S5L8900_SPI_SETUP 0x4 +#define S5L8900_SPI_STATUS 0x8 +#define S5L8900_SPI_PIN 0xC +#define S5L8900_SPI_TXDATA 0x10 +#define S5L8900_SPI_RXDATA 0x20 +#define S5L8900_SPI_CLKDIVIDER 0x30 +#define S5L8900_SPI_SPCNT 0x34 +#define S5L8900_SPI_SPIDD 0x38 + +// Clock +#define CLOCK0 0x38100000 +#define CLOCK1 0x3C500000 +#define CLOCK1_CONFIG0 0x0 +#define CLOCK1_CONFIG1 0x4 +#define CLOCK1_CONFIG2 0x8 +#define CLOCK1_PLL0CON 0x20 +#define CLOCK1_PLL1CON 0x24 +#define CLOCK1_PLL2CON 0x28 +#define CLOCK1_PLL3CON 0x2C +#define CLOCK1_PLL0LCNT 0x30 +#define CLOCK1_PLL1LCNT 0x34 +#define CLOCK1_PLL2LCNT 0x38 +#define CLOCK1_PLL3LCNT 0x3C +#define CLOCK1_PLLLOCK 0x40 +#define CLOCK1_PLLMODE 0x44 +#define CLOCK1_CL2_GATES 0x48 +#define CLOCK1_CL3_GATES 0x4C + + +// DEBUG + +#define S5L8900_DLVL_ERR (1) +#define S5L8900_DLVL_WARN (2) +#define S5L8900_DLVL_INFO (3) +#define S5L8900_DLVL_INFO2 (4) + +extern uint32_t g_debug; +extern uint32_t g_dlevel; +extern FILE *g_debug_fp; + +#define S5L8900_OPAQUE(name, opaque) fprintf(stderr, name " is at %p\n", opaque) + +#define S5L8900_DEBUG_CLK (0x00000001) +#define S5L8900_DEBUG_MIU (0x00000002) +#define S5L8900_DEBUG_CHIPID (0x00000003) + +#define S5L8900_DEBUG(module, level, msg ...) \ + do { \ + if ((module) & g_debug || (level) <= g_dlevel) { \ + fprintf(g_debug_fp, "%lld : ", qemu_get_clock_ms(vm_clock)); \ + fprintf(g_debug_fp, msg); \ + } \ + } while (0) + +typedef struct s5l8900_state_s { + CPUState *env; + qemu_irq **irq; + +} s5l8900_state; + +s5l8900_state *s5l8900_init(void); + + +DeviceState *s5l8900_uart_init(target_phys_addr_t base, int instance, + int queue_size, qemu_irq irq, + CharDriverState *chr); +DeviceState *pcf50633_init(i2c_bus *bus, int addr); +void s5l8900_usb_otg_init(NICInfo *nd, target_phys_addr_t base, qemu_irq irq); +void set_spi_base(uint32_t base); +#endif diff --git a/hw/s5l8900_i2c.c b/hw/s5l8900_i2c.c new file mode 100644 index 000000000..8a7f3739b --- /dev/null +++ b/hw/s5l8900_i2c.c @@ -0,0 +1,291 @@ +/* + * I2C controller for S5l8900 + * + * Hacked to bits by cmw + * Based on worked by + * Copyright (c) 2009 Samsung Electronics. + * Contributed by Vladimir Monakhov + * Alexey Merkulov + * + * Based on SMDK6400 I2C (hw/smdk6400/smdk_i2c.c) + */ + +#include "i2c.h" +#include "sysbus.h" + + +#define I2CCON 0x00 /* I2C Control register */ +#define I2CSTAT 0x04 /* I2C Status register */ +#define I2CADD 0x08 /* I2C Slave Address register */ +#define I2CDS 0x0c /* I2C Data Shift register */ +#define I2CLC 0x10 /* I2C Line Control register */ + +#define IICREG20 0x20 + +#define SR_MODE 0x0 /* Slave Receive Mode */ +#define ST_MODE 0x1 /* Slave Transmit Mode */ +#define MR_MODE 0x2 /* Master Receive Mode */ +#define MT_MODE 0x3 /* Master Transmit Mode */ + + +#define S5L8900_IICCON_ACKEN (1<<7) +#define S5L8900_IICCON_TXDIV_16 (0<<6) +#define S5L8900_IICCON_TXDIV_512 (1<<6) +#define S5L8900_IICCON_IRQEN (1<<5) +#define S5L8900_IICCON_IRQPEND (1<<4) + +#define S5L8900_IICSTAT_START (1<<5) +#define S5L8900_IICSTAT_BUSBUSY (1<<5) +#define S5L8900_IICSTAT_TXRXEN (1<<4) +#define S5L8900_IICSTAT_ARBITR (1<<3) +#define S5L8900_IICSTAT_ASSLAVE (1<<2) +#define S5L8900_IICSTAT_ADDR0 (1<<1) +#define S5L8900_IICSTAT_LASTBIT (1<<0) + +#define S5L8900_I2C_REG_MEM_SIZE 0x1000 + + +/* I2C Interface */ +typedef struct S5L8900I2CState { + SysBusDevice busdev; + + i2c_bus *bus; + qemu_irq irq; + + uint8_t control; + uint8_t status; + uint8_t address; + uint8_t datashift; + uint8_t line_ctrl; + uint32_t iicreg20; + + uint8_t ibmr; + uint8_t data; +} S5L8900I2CState; + + +static void s5l8900_i2c_update(S5L8900I2CState *s) +{ + uint16_t level; + level = (s->status & S5L8900_IICSTAT_START) && + (s->control & S5L8900_IICCON_IRQEN); + + if (s->control & S5L8900_IICCON_IRQPEND) + level = 0; + //qemu_set_irq(s->irq, !!level); +} + +static int s5l8900_i2c_receive(S5L8900I2CState *s) +{ + int r; + s->iicreg20 = 0x2000; + r = i2c_recv(s->bus); + s5l8900_i2c_update(s); + return r; +} + +static int s5l8900_i2c_send(S5L8900I2CState *s, uint8_t data) +{ + if (!(s->status & S5L8900_IICSTAT_LASTBIT)) { + /*s->status |= 1 << 7;*/ + s->iicreg20 = 0x100; + s->data = data; + i2c_send(s->bus, s->data); + } + s5l8900_i2c_update(s); + return 1; +} + +/* I2C read function */ +static uint32_t s5l8900_i2c_read(void *opaque, target_phys_addr_t offset) +{ + S5L8900I2CState *s = (S5L8900I2CState *)opaque; + + //fprintf(stderr, "s5l8900_i2c_read(): offset = 0x%08x\n", offset); + + + switch (offset) { + case I2CCON: + return s->control; + case I2CSTAT: + return s->status; + case I2CADD: + return s->address; + case I2CDS: + s->data = s5l8900_i2c_receive(s); + /* XXX: Need to fix */ + //fprintf(stderr, "%s: returning I2CDS 0x%08x\n", __func__, s->data); + return 0x0;//0x20; //s->data; + case I2CLC: + return s->line_ctrl; + case IICREG20: + { + uint32_t tmp_reg20 = s->iicreg20; + s->iicreg20 &= ~0x100; + s->iicreg20 &= ~0x2000; + return tmp_reg20; + } + default: + hw_error("s5l8900.i2c: bad read offset 0x" TARGET_FMT_plx "\n", + offset); + } + return 0; +} + +/* I2C write function */ +static void s5l8900_i2c_write(void *opaque, target_phys_addr_t offset, + uint32_t value) +{ + S5L8900I2CState *s = (S5L8900I2CState *)opaque; + int mode; + + //fprintf(stderr, "s5l8900_i2c_write: offset = 0x%08x, val = 0x%08x\n", offset, value); + + //qemu_irq_lower(s->irq); + + switch (offset) { + case I2CCON: + s->control = value & 0xff; + if(value & 7) + s->iicreg20 = 0x100; + if((value & 0x10) && (s->status == 0x90)) + s->iicreg20 = 0x2000; + + if (value & S5L8900_IICCON_IRQEN) + s5l8900_i2c_update(s); + break; + + case I2CSTAT: + s->status = value & 0xff; + mode = (s->status >> 6) & 0x3; + if (value & S5L8900_IICSTAT_TXRXEN) { + /* IIC-bus data output enable/disable bit */ + switch(mode) { + case SR_MODE: + s->data = s5l8900_i2c_receive(s); + break; + case ST_MODE: + s->data = s5l8900_i2c_receive(s); + break; + case MR_MODE: + if (value & (1 << 5)) { + /* START condition */ + s->status &= ~S5L8900_IICSTAT_LASTBIT; + + i2c_start_transfer(s->bus, s->data >> 1, s->data & 1); + } else { + i2c_end_transfer(s->bus); + s->status |= S5L8900_IICSTAT_TXRXEN; + } + break; + case MT_MODE: + if (value & (1 << 5)) { + /* START condition */ + s->status &= ~S5L8900_IICSTAT_LASTBIT; + + s->iicreg20 |= 0x100; + //fprintf(stderr, "%s: Starting transfer to addr 0x%08x\n", __func__, s->data); + i2c_start_transfer(s->bus, s->data, s->data & 1); + } else { + i2c_end_transfer(s->bus); + s->status |= S5L8900_IICSTAT_TXRXEN; + } + break; + default: + break; + } + } + s5l8900_i2c_update(s); + break; + + case I2CADD: + s->address = value & 0xff; + break; + + case I2CDS: + s5l8900_i2c_send(s, value & 0xff); + break; + + case I2CLC: + s->line_ctrl = value & 0xff; + break; + + case IICREG20: + //s->iicreg20 = value; + break; + default: + hw_error("s5l8900.i2c: bad write offset 0x" TARGET_FMT_plx "\n", + offset); + } +} + +static CPUReadMemoryFunc * const s5l8900_i2c_readfn[] = { + s5l8900_i2c_read, + s5l8900_i2c_read, + s5l8900_i2c_read +}; + +static CPUWriteMemoryFunc * const s5l8900_i2c_writefn[] = { + s5l8900_i2c_write, + s5l8900_i2c_write, + s5l8900_i2c_write +}; + +static void s5l8900_i2c_save(QEMUFile *f, void *opaque) +{ + S5L8900I2CState *s = (S5L8900I2CState *)opaque; + + qemu_put_8s(f, &s->control); + qemu_put_8s(f, &s->status); + qemu_put_8s(f, &s->address); + qemu_put_8s(f, &s->datashift); + qemu_put_8s(f, &s->line_ctrl); + qemu_put_8s(f, &s->ibmr); + qemu_put_8s(f, &s->data); +} + +static int s5l8900_i2c_load(QEMUFile *f, void *opaque, int version_id) +{ + S5L8900I2CState *s = (S5L8900I2CState *)opaque; + + if (version_id != 1) { + return -EINVAL; + } + + qemu_get_8s(f, &s->control); + qemu_get_8s(f, &s->status); + qemu_get_8s(f, &s->address); + qemu_get_8s(f, &s->datashift); + qemu_get_8s(f, &s->line_ctrl); + qemu_get_8s(f, &s->ibmr); + qemu_get_8s(f, &s->data); + + return 0; +} + +/* I2C init */ +static int s5l8900_i2c_init(SysBusDevice *dev) +{ + int iomemtype; + S5L8900I2CState *s = FROM_SYSBUS(S5L8900I2CState, dev); + + sysbus_init_irq(dev, &s->irq); + s->bus = i2c_init_bus(&dev->qdev, "i2c"); + + iomemtype = + cpu_register_io_memory(s5l8900_i2c_readfn, s5l8900_i2c_writefn, s, DEVICE_LITTLE_ENDIAN); + sysbus_init_mmio(dev, S5L8900_I2C_REG_MEM_SIZE, iomemtype); + + register_savevm(&dev->qdev, "s5l8900.i2c", -1, 1, + s5l8900_i2c_save, s5l8900_i2c_load, s); + + return 0; +} + +static void s5l8900_i2c_register(void) +{ + sysbus_register_dev("s5l8900.i2c", sizeof(S5L8900I2CState), + s5l8900_i2c_init); +} + +device_init(s5l8900_i2c_register) diff --git a/hw/s5l8900_spi.c b/hw/s5l8900_spi.c new file mode 100644 index 000000000..e629c4a13 --- /dev/null +++ b/hw/s5l8900_spi.c @@ -0,0 +1,191 @@ +/* + * S5L8900 SPI Emulation + * + * by cmw + */ + +#include "sysbus.h" +#include "s5l8900.h" + +#define S5L8900_WDT_REG_MEM_SIZE 0x30 + +#define SPI_CONTROL 0 +#define SPI_SETUP 0x4 +#define SPI_STATUS 0x8 +#define SPI_PIN 0xc +#define SPI_TXDATA 0x10 +#define SPI_RXDATA 0x20 +#define SPI_CLKDIV 0x30 +#define SPI_CNT 0x34 +#define SPI_IDD 0x38 + + +typedef struct S5L8900SPIState { + SysBusDevice busdev; + + uint32_t cmd; + uint32_t base; + uint32_t ctrl; + uint32_t setup; + uint32_t status; + uint32_t pin; + uint32_t tx_data; + uint32_t rx_data; + uint32_t clkdiv; + uint32_t cnt; + uint32_t idd; + + qemu_irq irq; +} S5L8900SPIState; + + +static uint32_t s5l8900_spi_mm_read(void *opaque, target_phys_addr_t offset) +{ + S5L8900SPIState *s = (S5L8900SPIState *)opaque; + + //fprintf(stderr, "%s: base 0x%08x offset 0x%08x\n", __func__, s->base, offset); + + switch (offset) { + case SPI_CONTROL: + return s->ctrl; + case SPI_SETUP: + return s->setup; + case SPI_STATUS: + return s->status; + case SPI_PIN: + return s->pin; + case SPI_TXDATA: + return s->tx_data; + case SPI_RXDATA: + //fprintf(stderr, "%s: s->cmd 0x%08x\n", __func__, s->cmd); + switch(s->cmd) { + case 0x95: + return 1; + case 0xDA: + return 0x71; + case 0xDB: + return 0xC2; + case 0xDC: + return 0x00; + default: + return 0; + } + return s->rx_data; + case SPI_CLKDIV: + return s->clkdiv; + case SPI_CNT: + return s->cnt; + case SPI_IDD: + return s->idd; + default: + hw_error("s5l8900_spi: bad read offset 0x" TARGET_FMT_plx "\n", + offset); + } +} + +static void s5l8900_spi_mm_write(void *opaque, target_phys_addr_t offset, + uint32_t val) +{ + S5L8900SPIState *s = (S5L8900SPIState *)opaque; + + //fprintf(stderr, "%s: base 0x%08x offset 0x%08x value 0x%08x\n", __func__, s->base, offset, val); + + switch (offset) { + case SPI_CONTROL: + if(val & 0x1) { + s->status |= 0xff2; + s->cmd = s->tx_data; + qemu_irq_raise(s->irq); + } + break; + case SPI_SETUP: + s->setup = val; + break; + case SPI_STATUS: + qemu_irq_lower(s->irq); + s->status = 0; + break; + case SPI_PIN: + s->pin = val; + break; + case SPI_TXDATA: + s->tx_data = val; + break; + case SPI_RXDATA: + s->rx_data = val; + break; + case SPI_CLKDIV: + s->clkdiv = val; + break; + case SPI_CNT: + s->cnt = val; + break; + case SPI_IDD: + s->idd = val; + break; + default: + hw_error("s5l8900_spi: bad write offset 0x" TARGET_FMT_plx "\n", + offset); + } +} + +CPUReadMemoryFunc * const s5l8900_spi_readfn[] = { + s5l8900_spi_mm_read, + s5l8900_spi_mm_read, + s5l8900_spi_mm_read +}; + +CPUWriteMemoryFunc * const s5l8900_spi_writefn[] = { + s5l8900_spi_mm_write, + s5l8900_spi_mm_write, + s5l8900_spi_mm_write +}; + +static void s5l8900_spi_reset(void *opaque) +{ + S5L8900SPIState *s = (S5L8900SPIState *)opaque; + + s->cmd = 0; + s->ctrl = 0; + s->setup = 0; + s->status = 0; + s->pin = 0; + s->tx_data = 0; + s->rx_data = 0; + s->clkdiv = 0; + s->cnt = 0; + s->idd = 0; +} + +static uint32_t base_addr = 0; + +void set_spi_base(uint32_t base) +{ + base_addr = base; +} + +static int s5l8900_spi_init(SysBusDevice *dev) +{ + int iomemtype; + S5L8900SPIState *s = FROM_SYSBUS(S5L8900SPIState, dev); + + sysbus_init_irq(dev, &s->irq); + + iomemtype = cpu_register_io_memory(s5l8900_spi_readfn, s5l8900_spi_writefn, s, DEVICE_LITTLE_ENDIAN); + sysbus_init_mmio(dev, S5L8900_WDT_REG_MEM_SIZE, iomemtype); + + s->base = base_addr; + s5l8900_spi_reset(s); + + qemu_register_reset(s5l8900_spi_reset, s); + + return 0; +} + +static void s5l8900_spi_register_devices(void) +{ + sysbus_register_dev("s5l8900.spi", sizeof(S5L8900SPIState), + s5l8900_spi_init); +} + +device_init(s5l8900_spi_register_devices) diff --git a/hw/s5l8900_uart.c b/hw/s5l8900_uart.c new file mode 100644 index 000000000..07bba4d53 --- /dev/null +++ b/hw/s5l8900_uart.c @@ -0,0 +1,398 @@ +/* + * S5L8900 UART Emulation + * + * Copyright (c) 2009 Samsung Electronics. + * Contributed by Kirill Batuzov + * Hacked to bits by cmw for s5l8900 support + */ + +#include "sysbus.h" +#include "qemu-char.h" +#include "s5l8900.h" + + +#define QUEUE_SIZE 257 + +#define INT_RXD (1 << 0) +#define INT_ERROR (1 << 1) +#define INT_TXD (1 << 2) +#define INT_MODEM (1 << 3) + +#define TRSTATUS_TRANSMITTER_READY (1 << 2) +#define TRSTATUS_BUFFER_EMPTY (1 << 1) +#define TRSTATUS_DATA_READY (1 << 0) + +#define UFSTAT_RX_FIFO_FULL (1 << 8) + +#define UFCON_FIFO_ENABLED (1 << 0) +#define UFCON_TX_LEVEL_SHIFT 8 +#define UFCON_TX_LEVEL (7 << UFCON_TX_LEVEL_SHIFT) + +#define UFSTAT_TX_COUNT_SHIT 16 +#define UFSTAT_TX_COUNT (0xFF << UFSTAT_TX_COUNT_SHIT) + +#define QI(x) ((x + 1) % QUEUE_SIZE) +#define QD(x) ((x - 1 + QUEUE_SIZE) % QUEUE_SIZE) + +#define S5L8900_UART_REG_MEM_SIZE 0x3C + +typedef struct UartQueue { + uint8_t queue[QUEUE_SIZE]; + uint32_t s, t; + uint32_t size; +} UartQueue; + +typedef struct S5L8900UartState { + SysBusDevice busdev; + + UartQueue rx; + + uint32_t base; + uint32_t ulcon; + uint32_t ucon; + uint32_t ufcon; + uint32_t umcon; + uint32_t utrstat; + uint32_t uerstat; + uint32_t ufstat; + uint32_t umstat; + uint32_t utxh; + uint32_t urxh; + uint32_t ubrdiv; + uint32_t udivslot; + uint32_t uintp; + uint32_t uintsp; + uint32_t uintm; + + CharDriverState *chr; + qemu_irq irq; + uint32_t instance; +} S5L8900UartState; + + +static inline int queue_elem_count(const UartQueue *s) +{ + if (s->t >= s->s) { + return s->t - s->s; + } else { + return QUEUE_SIZE - s->s + s->t; + } +} + +static inline int queue_empty_count(const UartQueue *s) +{ + return s->size - queue_elem_count(s) - 1; +} + +static inline int queue_empty(const UartQueue *s) +{ + return (queue_elem_count(s) == 0); +} + +static inline void queue_push(UartQueue *s, uint8_t x) +{ + s->queue[s->t] = x; + s->t = QI(s->t); +} + +static inline uint8_t queue_get(UartQueue *s) +{ + uint8_t ret; + + ret = s->queue[s->s]; + s->s = QI(s->s); + return ret; +} + +static inline void queue_reset(UartQueue *s) +{ + s->s = 0; + s->t = 0; +} + +static void s5l8900_uart_update(S5L8900UartState *s) +{ + if (s->ufcon && UFCON_FIFO_ENABLED) { + if (((s->ufstat && UFSTAT_TX_COUNT) >> UFSTAT_TX_COUNT_SHIT) <= + ((s->ufcon && UFCON_TX_LEVEL) >> UFCON_TX_LEVEL_SHIFT) * 2 ) { + s->uintsp |= INT_TXD; + } + } + + s->uintp = s->uintsp & ~s->uintm; + if (s->uintp) { + qemu_irq_raise(s->irq); + } else { + qemu_irq_lower(s->irq); + } +} + +static uint32_t s5l8900_uart_mm_read(void *opaque, target_phys_addr_t offset) +{ + uint32_t res; + S5L8900UartState *s = (S5L8900UartState *)opaque; + + //fprintf(stderr, "s5l8900_uart_mm_read(): offset = 0x%08x\n", offset); + + switch (offset) { + case 0x00: + return s->ulcon; + case 0x04: + return s->ucon; + case 0x08: + return s->ufcon; + case 0x0C: + return s->umcon; + case 0x10: + return s->utrstat; + case 0x14: + res = s->uerstat; + s->uerstat = 0; + return res; + case 0x18: + s->ufstat = queue_elem_count(&s->rx) & 0xff; + if (queue_empty_count(&s->rx) == 0) { + s->ufstat |= UFSTAT_RX_FIFO_FULL; + } + return s->ufstat; + case 0x20: + return s->utxh; + case 0x1C: + return 0x1; //s->umstat; + case 0x24: + if (s->ufcon & 1) { + if (! queue_empty(&s->rx)) { + res = queue_get(&s->rx); + if (queue_empty(&s->rx)) { + s->utrstat &= ~TRSTATUS_DATA_READY; + } else { + s->utrstat |= TRSTATUS_DATA_READY; + } + } else { + s->uintsp |= INT_ERROR; + s5l8900_uart_update(s); + res = 0; + } + } else { + s->utrstat &= ~TRSTATUS_DATA_READY; + res = s->urxh; + } + return res; + case 0x28: + return s->ubrdiv; + case 0x2C: + return s->udivslot; + case 0x30: + return s->uintp; + case 0x34: + return s->uintsp; + case 0x38: + return s->uintm; + default: + hw_error("s5l8900.uart: bad read offset 0x" TARGET_FMT_plx "\n", + offset); + } +} + +static void s5l8900_uart_mm_write(void *opaque, target_phys_addr_t offset, + uint32_t val) +{ + uint8_t ch; + S5L8900UartState *s = (S5L8900UartState *)opaque; + + //fprintf(stderr, "s5l8900_uart_mm_write(): offset = 0x%08x, val = 0x%08x\n", offset, val); + + switch (offset) { + case 0x00: + s->ulcon = val; + break; + case 0x04: + s->ucon = val; + break; + case 0x08: + s->ufcon = val; + if (val & 2) { + queue_reset(&s->rx); + } + s->ufcon &= ~6; + break; + case 0x10: // wtf is it doing? + case 0x14: + break; + case 0x0C: + s->umcon = val; + break; + case 0x20: + //if (s->chr && !(s->base & 0x4000)) { + s->utrstat &= ~(TRSTATUS_TRANSMITTER_READY | TRSTATUS_BUFFER_EMPTY); + ch = (uint8_t)val; + qemu_chr_write(s->chr, &ch, 1); + s->utrstat |= TRSTATUS_TRANSMITTER_READY | TRSTATUS_BUFFER_EMPTY; + s->uintsp |= INT_TXD; + //} else if (s->base & 0x4000) { + // Radio should be using chr_add_handlers + //fprintf(stderr, "%c\n", val); + //s->umstat + //} + break; + case 0x28: + s->ubrdiv = val; + break; + case 0x2C: + s->udivslot = val; + break; + case 0x30: + s->uintp &= ~val; + s->uintsp &= ~val; /* TODO: does this really work in this way??? */ + break; + case 0x34: + s->uintsp = val; + break; + case 0x38: + s->uintm = val; + break; + default: + hw_error("s5l8900.uart: bad write offset 0x" TARGET_FMT_plx "\n", + offset); + } + s5l8900_uart_update(s); +} + +CPUReadMemoryFunc * const s5l8900_uart_readfn[] = { + s5l8900_uart_mm_read, + s5l8900_uart_mm_read, + s5l8900_uart_mm_read +}; + +CPUWriteMemoryFunc * const s5l8900_uart_writefn[] = { + s5l8900_uart_mm_write, + s5l8900_uart_mm_write, + s5l8900_uart_mm_write +}; + +static int s5l8900_uart_can_receive(void *opaque) +{ + S5L8900UartState *s = (S5L8900UartState *)opaque; + + return queue_empty_count(&s->rx); +} + +static void s5l8900_uart_trigger_level(S5L8900UartState *s) +{ + /* TODO: fix this */ + if (! queue_empty(&s->rx)) { + s->uintsp |= INT_RXD; + } +} + +static void s5l8900_uart_receive(void *opaque, const uint8_t *buf, int size) +{ + int i; + S5L8900UartState *s = (S5L8900UartState *)opaque; + if (s->ufcon & 1) { + if (queue_empty_count(&s->rx) < size) { + for (i = 0; i < queue_empty_count(&s->rx); i++) { + queue_push(&s->rx, buf[i]); + } + s->uintp |= INT_ERROR; + s->utrstat |= TRSTATUS_DATA_READY; + } else { + for (i = 0; i < size; i++) { + queue_push(&s->rx, buf[i]); + } + s->utrstat |= TRSTATUS_DATA_READY; + } + s5l8900_uart_trigger_level(s); + } else { + s->urxh = buf[0]; + s->uintsp |= INT_RXD; + s->utrstat |= TRSTATUS_DATA_READY; + } + s5l8900_uart_update(s); +} + +static void s5l8900_uart_event(void *opaque, int event) +{ + /* TODO: implement this */ +} + +static void s5l8900_uart_reset(DeviceState *d) +{ + S5L8900UartState *s = + FROM_SYSBUS(S5L8900UartState, sysbus_from_qdev(d)); + + s->ulcon = 0; + s->ucon = 0; + s->ufcon = 0; + s->umcon = 0; + s->utrstat = 0x6; + s->uerstat = 0; + s->ufstat = 0; + s->umstat = 0; + s->ubrdiv = 0; + s->udivslot = 0; + s->uintp = 0; + s->uintsp = 0; + s->uintm = 0; + queue_reset(&s->rx); +} + +DeviceState *s5l8900_uart_init(target_phys_addr_t base, int instance, + int queue_size, qemu_irq irq, + CharDriverState *chr) +{ + DeviceState *dev = qdev_create(NULL, "s5l8900.uart"); + char str[] = "s5l8900.uart.00"; + + if (!chr) { + fprintf(stderr, "openning char device"); + snprintf(str, strlen(str) + 1, "s5l8900.uart.%02d", instance % 100); + chr = qemu_chr_open(str, "null", NULL); + } + qdev_prop_set_chr(dev, "chr", chr); + qdev_prop_set_uint32(dev, "queue-size", queue_size); + qdev_prop_set_uint32(dev, "instance", instance); + qdev_init_nofail(dev); + sysbus_mmio_map(sysbus_from_qdev(dev), 0, base); + sysbus_connect_irq(sysbus_from_qdev(dev), 0, irq); + return dev; +} + +static int s5l8900_uart_init1(SysBusDevice *dev) +{ + int iomemtype; + S5L8900UartState *s = FROM_SYSBUS(S5L8900UartState, dev); + + s5l8900_uart_reset(&s->busdev.qdev); + + sysbus_init_irq(dev, &s->irq); + + qemu_chr_add_handlers(s->chr, s5l8900_uart_can_receive, + s5l8900_uart_receive, s5l8900_uart_event, s); + + iomemtype = + cpu_register_io_memory(s5l8900_uart_readfn, s5l8900_uart_writefn, s, DEVICE_LITTLE_ENDIAN); + sysbus_init_mmio(dev, S5L8900_UART_REG_MEM_SIZE, iomemtype); + + return 0; +} + +static SysBusDeviceInfo s5l8900_uart_info = { + .init = s5l8900_uart_init1, + .qdev.name = "s5l8900.uart", + .qdev.size = sizeof(S5L8900UartState), + .qdev.reset = s5l8900_uart_reset, + .qdev.props = (Property[]) { + DEFINE_PROP_UINT32("instance", S5L8900UartState, instance, 0), + DEFINE_PROP_UINT32("queue-size", S5L8900UartState, rx.size, 16), + DEFINE_PROP_CHR("chr", S5L8900UartState, chr), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static void s5l8900_uart_register(void) +{ + sysbus_register_withprop(&s5l8900_uart_info); +} + +device_init(s5l8900_uart_register) diff --git a/hw/s5l8900_usb_otg.c b/hw/s5l8900_usb_otg.c new file mode 100644 index 000000000..cffbadf5b --- /dev/null +++ b/hw/s5l8900_usb_otg.c @@ -0,0 +1,705 @@ +/* + * S5L8900 OTG USB + * + * -COMPLETELY BROKEN- + * Copyright (c) 2009 Samsung Electronics. + * Contributed by Kirill Batuzov + */ + +#include "sysbus.h" +#include "qemu-common.h" +#include "qemu-timer.h" +#include "usb.h" +#include "net.h" +#include "irq.h" +#include "hw.h" +#include "s5l8900.h" + + +/* Interrupts */ +#define USB_INT_MODEMIS (1 << 1) /* Mode Mismatch Interrupt */ +#define USB_INT_OTGINT (1 << 2) /* OTG Interrupt */ +#define USB_INT_SOF (1 << 3) /* Start of (micro) Frame */ +#define USB_INT_RXFLVL (1 << 4) /* RxFIFO Non-Empty */ +#define USB_INT_NPTXFEMP (1 << 5) /* Non-periodic TxFIFO Empty */ +#define USB_INT_GINNAKEFF (1 << 6) /* Global IN Non-periodic NAK Effective */ +#define USB_INT_GOUTNAKEFF (1 << 7) /* Global OUT NAK Effective */ +#define USB_INT_ERLYSUSP (1 << 10) /* Early Suspend */ +#define USB_INT_USBSUSP (1 << 11) /* USB Suspend */ +#define USB_INT_USBRST (1 << 12) /* USB Reset */ +#define USB_INT_ENUMDONE (1 << 13) /* Enumeration Done */ +#define USB_INT_ISOUTDROP (1 << 14) /* Isochronous OUT Packet Dropped */ +#define USB_INT_EOPF (1 << 15) /* End of Periodic Frame */ +#define USB_INT_IEPINT (1 << 18) /* IN Endpoints Interrupt */ +#define USB_INT_OEPINT (1 << 19) /* OUT Endpoints Interrupt */ +#define USB_INT_INCOMPLISOIN \ + (1 << 20) /* Incomplete Isochronous IN Transfer */ +#define USB_INT_INCOMPLISOOUT \ + (1 << 21) /* Incomplete Isochronous OUT Transfer */ +#define USB_INT_FETSUSP (1 << 22) /* Data Fetch Suspended */ +//#define USB_INT_PRTINT (1 << 24) /* Host Port Interrupt */ +//#define USB_INT_HCHINT (1 << 25) /* Host Channels Interrupt */ +#define USB_INT_PTXFEMP (1 << 26) /* Periodic TxFIFO Empty */ +#define USB_INT_CONIDSTSCHNG \ + (1 << 28) /* Connector ID Status Change */ +#define USB_INT_DISCONINT (1 << 29) /* Disconnect Detected Interrupt */ +#define USB_INT_SESSREQINT (1 << 30) /* New Session Detected Interrupt */ +#define USB_INT_WKUPINT (1 << 31) /* Resume Interrupt */ + +#define EP_INT_XFERCOMPL (1 << 0) /* Transfer complete */ +#define EP_INT_EPDISABLED (1 << 1) /* Endpoint disabled */ +#define EP_INT_AHBERR (1 << 2) /* AHB error */ +#define EP_INT_SETUP (1 << 3) /* [OUT] Setup phase done */ +#define EP_INT_TIMEOUT (1 << 3) /* [IN] Timeout */ +#define EP_INT_OUTTKNEPDIS (1 << 4) /* [OUT] Token Received When EP Disabled */ +#define EP_INT_INTKNFIFOEMP (1 << 4) /* [IN] Token Received When FIFO is Empty */ +#define EP_INT_STSPHSERCVD (1 << 5) /* [OUT] Status Phase Received For Control Write */ +#define EP_INT_INTTKNEPMIS (1 << 5) /* [IN] Token Received With EP Missmatch */ +#define EP_INT_BACK2BACK (1 << 6) /* [OUT] Back-to-Back SETUP Packets Receive */ +#define EP_INT_INEPNAKEFF (1 << 6) /* [IN] Endpoint NAK Effective */ +#define EP_INT_TXFEMP (1 << 7) /* Transmit FIFO Empty */ +#define EP_INT_OUTPKTERR (1 << 8) /* [OUT] Packet Error */ +#define EP_INT_TXFIFOUNDRN (1 << 8) /* [IN] FIFO Underrun */ +#define EP_INT_BNAINTR (1 << 9) /* Buffer not Available */ + + +#define OTG_EP_DIR_IN 0x80 +#define OTG_EP_DIR_OUT 0 + +#define OTG_EP_ENABLE (1U << 31) +#define OTG_EP_DISABLE (1 << 30) + +#define OTG_EP_COUNT 16 + + +typedef enum { + OTG_STATE_START = 0, + OTG_STATE_RESET, + OTG_STATE_SPEEDDETECT, + OTG_STATE_SETCONFIG_S, + OTG_STATE_SETCONFIG_W, + OTG_STATE_SETCONFIG_D, + OTG_STATE_SETIFACE_S, + OTG_STATE_SETIFACE_W, + OTG_STATE_SETIFACE_D, + OTG_STATE_OPERATIONAL +} OtgLogicalState; + +typedef struct S5L8900UsbOtgEndPoint { + uint32_t n; + uint32_t ctrl; + uint32_t interrupt; + uint32_t transfer_size; + uint32_t dma_addr; + uint32_t dma_buf; + uint32_t in_fifo_size; + + uint8_t dir; + + struct S5L8900UsbOtgState *parent; +} S5L8900UsbOtgEndPoint; + +typedef struct S5L8900UsbOtgState { + SysBusDevice busdev; + + struct S5L8900PhyState { + uint32_t power; + uint32_t clock; + uint32_t reset; + uint32_t tune0; + uint32_t tune1; + } phy; + + uint32_t gotg_ctl; + uint32_t gotg_int; + uint32_t gahb_cfg; + uint32_t gusb_cfg; + uint32_t grst_ctl; + uint32_t gint_sts; + uint32_t gint_msk; + uint32_t grx_stsr; + uint32_t grx_stsp; + uint32_t grx_fsiz; + uint32_t gnptx_fsiz; + uint32_t gnptx_sts; + uint32_t hnptx_fsiz; + uint32_t daint_sts; + uint32_t daint_msk; + uint32_t diep_msk; + uint32_t doep_msk; + + S5L8900UsbOtgEndPoint ep_in[OTG_EP_COUNT]; + S5L8900UsbOtgEndPoint ep_out[OTG_EP_COUNT]; + + OtgLogicalState state; + + NICState *nic; + NICConf conf; + qemu_irq irq; + uint8_t buf[1600]; + uint32_t buf_size; + uint8_t buf_full; +} S5L8900UsbOtgState; + + +static const uint8_t otg_setup_packet[] = { + 0x80, 0x06, 0x00, 0x01, 0x00, 0x00, 0x40, 0x00 +}; +static const uint8_t otg_setup_iface[] = { + 0x01, 0x0B, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00 +}; +static const uint8_t otg_setup_config[] = { + 0x00, 0x09, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00 +}; + +static void s5l8900_usb_otg_update_irq(S5L8900UsbOtgState *s) +{ + if (s->gint_sts & s->gint_msk) { + fprintf(stderr,"%s: qemu_irq_raise()\n", __func__); + //qemu_irq_raise(s->irq); + } else { + fprintf(stderr,"%s: qemu_irq_lower()\n", __func__); + qemu_irq_lower(s->irq); + } +} + +static void s5l8900_usb_otg_initial_reset(DeviceState *d) +{ + S5L8900UsbOtgState *s = + FROM_SYSBUS(S5L8900UsbOtgState, sysbus_from_qdev(d)); + int i; + + s->phy.power = 0x000001F9; + s->phy.clock = 0x00000000; + s->phy.reset = 0x00000009; /* TODO: I believe it should be 0 */ + s->phy.tune0 = 0x000919B3; + s->phy.tune1 = 0x000919B3; + + s->gotg_ctl = 0x00010000; + s->gotg_int = 0x00000000; + s->gahb_cfg = 0x00000000; + s->gusb_cfg = 0x00001408; + s->grst_ctl = 0x80000000; + s->gint_sts = 0x04000020; + s->gint_msk = 0x00000000; + + for (i = 0; i < OTG_EP_COUNT; i++) { + s->ep_in[i].parent = s; + s->ep_in[i].dir = OTG_EP_DIR_IN; + s->ep_in[i].n = i; + s->ep_out[i].parent = s; + s->ep_out[i].dir = OTG_EP_DIR_OUT; + s->ep_out[i].n = i; + } + + s->state = OTG_STATE_START; + s->buf_full = 0; +} + +static void s5l8900_usb_otg_reset(S5L8900UsbOtgState *s) +{ + s5l8900_usb_otg_initial_reset(&s->busdev.qdev); + s->state = OTG_STATE_RESET; + s->gotg_ctl += 0x000C0000; + s->gint_sts |= USB_INT_USBRST; +} + +static uint32_t s5l8900_usb_otg_phy_mm_read(void *opaque, + target_phys_addr_t offset) +{ + S5L8900UsbOtgState *s = (S5L8900UsbOtgState *)opaque; + + fprintf(stderr, "%s: offset 0x%08x\n", __func__, offset); + + + switch (offset) { + case 0x00: + return s->phy.power; + case 0x04: + return s->phy.clock; + case 0x08: + return s->phy.reset; + default: + hw_error("s5l8900.usb_otg: bad read offset 0x" TARGET_FMT_plx "\n", + offset); + } +} + +static void s5l8900_usb_otg_phy_mm_write(void *opaque, target_phys_addr_t offset, + uint32_t val) +{ + S5L8900UsbOtgState *s = (S5L8900UsbOtgState *)opaque; + + fprintf(stderr, "%s: offset 0x%08x val 0x%08x\n", __func__, offset, val); + + switch (offset) { + case 0x00: + s->phy.power = val; + break; + case 0x04: + s->phy.clock = val; + break; + case 0x08: + /* TODO: actually reset USB OTG */ + if (val & 0x1f) { + s5l8900_usb_otg_reset(s); + s->gint_sts |= USB_INT_USBRST; + s5l8900_usb_otg_update_irq(s); + } + break; + default: + hw_error("s5l8900.usb_otg: bad write offset 0x" TARGET_FMT_plx "\n", + offset); + } +} + +static CPUReadMemoryFunc * const s5l8900_usb_otg_phy_mm_readfn[] = { + s5l8900_usb_otg_phy_mm_read, + s5l8900_usb_otg_phy_mm_read, + s5l8900_usb_otg_phy_mm_read, +}; + +static CPUWriteMemoryFunc * const s5l8900_usb_otg_phy_mm_writefn[] = { + s5l8900_usb_otg_phy_mm_write, + s5l8900_usb_otg_phy_mm_write, + s5l8900_usb_otg_phy_mm_write, +}; + +static void s5l8900_usb_otg_ep_update_irq(S5L8900UsbOtgEndPoint *s) +{ + + fprintf(stderr, "%s: \n", __func__); + + if (s->interrupt) { + if (s->dir == OTG_EP_DIR_IN) { + s->parent->daint_sts |= (1 << s->n); + if (s->parent->daint_sts & s->parent->daint_msk & 0xffff) { + s->parent->gint_sts |= USB_INT_IEPINT; + } else { + s->parent->gint_sts &= ~USB_INT_IEPINT; + } + } else { + s->parent->daint_sts |= (1 << (s->n + 16)); + if (s->parent->daint_sts & s->parent->daint_msk & 0xffff0000) { + s->parent->gint_sts |= USB_INT_OEPINT; + } else { + s->parent->gint_sts &= ~USB_INT_OEPINT; + } + } + } else { + if (s->dir == OTG_EP_DIR_IN) { + s->parent->daint_sts &= ~(1 << s->n); + if (s->parent->daint_sts & s->parent->daint_msk & 0xffff) { + s->parent->gint_sts |= USB_INT_IEPINT; + } else { + s->parent->gint_sts &= ~USB_INT_IEPINT; + } + } else { + s->parent->daint_sts &= ~(1 << (s->n + 16)); + if (s->parent->daint_sts & s->parent->daint_msk & 0xffff0000) { + s->parent->gint_sts |= USB_INT_OEPINT; + } else { + s->parent->gint_sts &= ~USB_INT_OEPINT; + } + } + } + s5l8900_usb_otg_update_irq(s->parent); +} + +static void s5l8900_usb_otg_act(S5L8900UsbOtgState *s) +{ + fprintf(stderr, "%s: \n", __func__); + + switch (s->state) { + case OTG_STATE_START: + case OTG_STATE_RESET: + case OTG_STATE_SPEEDDETECT: + break; + case OTG_STATE_SETCONFIG_S: + if (s->ep_out[0].ctrl & OTG_EP_ENABLE) { + s->state = OTG_STATE_SETCONFIG_W; + cpu_physical_memory_write(s->ep_out[0].dma_addr, + otg_setup_config, 8); + s->ep_out[0].ctrl &= ~OTG_EP_ENABLE; + s->ep_out[0].interrupt |= EP_INT_SETUP|EP_INT_XFERCOMPL; + s5l8900_usb_otg_ep_update_irq(&s->ep_out[0]); + } + break; + case OTG_STATE_SETIFACE_S: + if (s->ep_out[0].ctrl & OTG_EP_ENABLE) { + s->state = OTG_STATE_SETIFACE_W; + cpu_physical_memory_write(s->ep_out[0].dma_addr, + otg_setup_iface, 8); + s->ep_out[0].ctrl &= ~OTG_EP_ENABLE; + s->ep_out[0].interrupt |= EP_INT_SETUP|EP_INT_XFERCOMPL; + s5l8900_usb_otg_ep_update_irq(&s->ep_out[0]); + } + break; + default: + break; + } +} + +static void s5l8900_usb_otg_data_tx(S5L8900UsbOtgEndPoint *s) +{ + uint8_t buf[1600]; + uint32_t size = s->transfer_size & 0x7ffff; + + fprintf(stderr, "%s: ", __func__); + + cpu_physical_memory_read(s->dma_addr, buf, size); + qemu_send_packet(&s->parent->nic->nc, buf, size); + s->interrupt |= EP_INT_XFERCOMPL|EP_INT_TXFEMP; + s5l8900_usb_otg_ep_update_irq(s); +} + +static void s5l8900_usb_otg_data_rx(S5L8900UsbOtgEndPoint *s) +{ + uint32_t size = s->parent->buf_size; + + fprintf(stderr, "%s: \n", __func__); + + if (s->parent->buf_size > (s->transfer_size & 0x7ffff)) { + s->parent->buf_full = 0; + /* Packet dropped */ + return ; + } + cpu_physical_memory_write(s->dma_addr, s->parent->buf, size); + s->dma_buf = s->dma_addr + size; + s->transfer_size -= size; + s->ctrl &= ~OTG_EP_ENABLE; + s->interrupt |= EP_INT_XFERCOMPL; + s->parent->buf_full = 0; + s5l8900_usb_otg_ep_update_irq(s); +} + +static uint32_t s5l8900_usb_otg_ep_read(S5L8900UsbOtgEndPoint *s, + target_phys_addr_t addr) +{ + fprintf(stderr, "%s: \n", __func__); + + switch (addr) { + case 0x00: + return s->ctrl; + case 0x08: + return s->interrupt; + case 0x10: + return s->transfer_size; + case 0x14: + return s->dma_addr; + case 0x1C: + return s->dma_buf; + default: + hw_error("s5l8900.usb_otg: bad write offset 0x" TARGET_FMT_plx "\n", + addr); + } +} + +static uint32_t s5l8900_usb_otg_ep_write(S5L8900UsbOtgEndPoint *s, + target_phys_addr_t addr, uint32_t val) +{ + fprintf(stderr, "%s: addr 0x%08x val 0x%08x\n", __func__, addr, val); + + switch (addr) { + case 0x00: + if ((val & OTG_EP_DISABLE) && (s->ctrl & OTG_EP_ENABLE)) { + s->ctrl &= ~OTG_EP_ENABLE; + val &= ~OTG_EP_DISABLE; + s->interrupt |= EP_INT_EPDISABLED; + s5l8900_usb_otg_ep_update_irq(s); + } + val &= ~OTG_EP_DISABLE; + if (val & OTG_EP_ENABLE) { + s5l8900_usb_otg_act(s->parent); + if (s->n == 0 && s->dir == OTG_EP_DIR_IN) { + s->interrupt |= EP_INT_XFERCOMPL|EP_INT_TXFEMP; + if (s->parent->state == OTG_STATE_SETCONFIG_W) { + s->parent->state = OTG_STATE_SETIFACE_S; + } else if (s->parent->state == OTG_STATE_SETIFACE_W) { + s->parent->state = OTG_STATE_OPERATIONAL; + } + s5l8900_usb_otg_ep_update_irq(s); + } + if (s->n != 0 && s->dir == OTG_EP_DIR_IN) { + s5l8900_usb_otg_data_tx(s); + val &= ~OTG_EP_ENABLE; + } + if (s->n != 0 && s->dir == OTG_EP_DIR_OUT && + s->parent->buf_full == 1) { + s5l8900_usb_otg_data_rx(s); + val &= ~OTG_EP_ENABLE; + } + } + val &= ~(0xC << 24); /* TODO: handle NAK? */ + s->ctrl = val; + /* TODO: handle control */ + break; + case 0x08: + s->interrupt &= ~val; + s5l8900_usb_otg_ep_update_irq(s); + break; + case 0x10: + s->transfer_size = val; + break; + case 0x14: + s->dma_addr = val; + break; + default: + hw_error("s5l8900.usb_otg: bad write offset 0x" TARGET_FMT_plx "\n", + addr); + } + return 0; +} + +static uint32_t s5l8900_usb_otg_read(void *opaque, target_phys_addr_t addr) +{ + S5L8900UsbOtgState *s = (S5L8900UsbOtgState *)opaque; + +/* + if (addr >= 0x100000 && addr < 0x100028) { + return s5l8900_usb_otg_phy_mm_read(opaque, addr - 0x100000); + } +*/ + + fprintf(stderr, "%s: offset 0x%08x\n", __func__, addr); + + switch (addr) { + case 0x00: + return s->gotg_ctl; + case 0x04: + return s->gotg_int; + case 0x08: + return s->gahb_cfg; + case 0x0C: + return s->gusb_cfg; + case 0x10: + return s->grst_ctl; + case 0x14: + return s->gint_sts; + case 0x18: + return s->gint_msk; + case 0x1C: + return s->grx_stsr; + case 0x20: + return s->grx_stsp; + case 0x24: + return s->grx_fsiz; + case 0x28: + return s->gnptx_fsiz; + case 0x2C: + return s->gnptx_sts; + case 0x30: + return s->hnptx_fsiz; + case 0x100 ... 0x13C: + return s->ep_in[(addr - 0x100) >> 2].in_fifo_size; + case 0x810: + return s->diep_msk; + case 0x814: + return s->doep_msk; + case 0x818: + return s->daint_sts; + case 0x81C: + return s->daint_msk; + case 0x900 ... 0xAFC: + addr -= 0x900; + return s5l8900_usb_otg_ep_read(&s->ep_in[addr >> 5], addr & 0x1f); + case 0xB00 ... 0xCFC: + addr -= 0xB00; + return s5l8900_usb_otg_ep_read(&s->ep_out[addr >> 5], addr & 0x1f); + } + return 0; +} + +static void s5l8900_usb_otg_write(void *opaque, target_phys_addr_t addr, + uint32_t val) +{ + int i; + S5L8900UsbOtgState *s = (S5L8900UsbOtgState *)opaque; + + /* + if (addr >= 0x100000 && addr < 0x100028) { + s5l8900_usb_otg_phy_mm_write(opaque, addr - 0x100000, val); + return; + } + */ + + fprintf(stderr, "%s: offset 0x%08x val 0x%08x\n", __func__, addr, val); + + switch (addr) { + case 0x00: + s->gotg_ctl = val; + break; + case 0x04: + s->gotg_int &= ~val; + s5l8900_usb_otg_update_irq(s); + break; + case 0x08: + s->gahb_cfg = val; + break; + case 0x0C: + s->gusb_cfg = val; + break; + case 0x10: + if (val & 1) { + s5l8900_usb_otg_reset(s); + s->gint_sts |= USB_INT_USBRST; + s->state = OTG_STATE_RESET; + s5l8900_usb_otg_update_irq(s); + } else if (val & 0x0f) { + s->gint_sts |= USB_INT_USBRST; + s->state = OTG_STATE_RESET; + s5l8900_usb_otg_update_irq(s); + } + //s->grst_ctl = val & (~0x3f); + s5l8900_usb_otg_update_irq(s); + break; + case 0x14: + val &= ~(7 << 24); + val &= ~(3 << 18); + val &= ~(0xf << 4); + val &= ~0x5; + s->gint_sts &= ~val; + if (val == 0x1000) { + s->gint_sts |= USB_INT_ENUMDONE; + s->state = OTG_STATE_SPEEDDETECT; + s5l8900_usb_otg_act(s); + } + if (val == USB_INT_ENUMDONE) { + s->state = OTG_STATE_SETCONFIG_S; + s5l8900_usb_otg_act(s); + } + s5l8900_usb_otg_update_irq(s); + break; + case 0x18: + s->gint_msk = val; + break; + case 0x24: + s->grx_fsiz = val; + break; + case 0x28: + s->gnptx_fsiz = val; + break; + case 0x30: + s->hnptx_fsiz = val; + break; + case 0x100 ... 0x13C: + s->ep_in[(addr - 0x100 ) >> 2].in_fifo_size = val; + break; + case 0x810: + s->diep_msk = val; + for (i = 0; i < OTG_EP_COUNT; i++) { + s5l8900_usb_otg_ep_update_irq(&s->ep_in[i]); + } + break; + case 0x814: + s->doep_msk = val; + for (i = 0; i < OTG_EP_COUNT; i++) { + s5l8900_usb_otg_ep_update_irq(&s->ep_out[i]); + } + break; + case 0x81C: + s->daint_msk = val; + if (s->daint_sts & 0xffff & s->daint_msk) { + s->gint_sts |= USB_INT_IEPINT; + } else { + s->gint_sts &= ~USB_INT_IEPINT; + } + if ((s->daint_sts & s->daint_msk) >> 16) { + s->gint_sts |= USB_INT_OEPINT; + } else { + s->gint_sts &= ~USB_INT_OEPINT; + } + if(s->daint_msk == 0xFFFFFFFF) + break; + s5l8900_usb_otg_update_irq(s); + break; + case 0x900 ... 0xAFC: + addr -= 0x900; + s5l8900_usb_otg_ep_write(&s->ep_in[addr >> 5], addr & 0x1f, val); + break; + case 0xB00 ... 0xCFC: + addr -= 0xB00; + s5l8900_usb_otg_ep_write(&s->ep_out[addr >> 5], addr & 0x1f, val); + break; + } +} + +static CPUReadMemoryFunc * const s5l8900_usb_otg_readfn[] = { + s5l8900_usb_otg_read, + s5l8900_usb_otg_read, + s5l8900_usb_otg_read +}; + +static CPUWriteMemoryFunc * const s5l8900_usb_otg_writefn[] = { + s5l8900_usb_otg_write, + s5l8900_usb_otg_write, + s5l8900_usb_otg_write +}; + +static int s5l8900_usb_otg_init1(SysBusDevice *dev) +{ + int iomemtype; + S5L8900UsbOtgState *s = FROM_SYSBUS(S5L8900UsbOtgState, dev); + + iomemtype = + cpu_register_io_memory(s5l8900_usb_otg_readfn, + s5l8900_usb_otg_writefn, s, DEVICE_LITTLE_ENDIAN); + sysbus_init_mmio(dev, 0x100000, iomemtype); + sysbus_init_irq(dev, &s->irq); + + + /* Remap OTG PHY regs */ + iomemtype = cpu_register_io_memory(s5l8900_usb_otg_phy_mm_readfn, + s5l8900_usb_otg_phy_mm_writefn, s, DEVICE_LITTLE_ENDIAN); + cpu_register_physical_memory(0x3c400000, 0x40, iomemtype); + + /* + qemu_macaddr_default_if_unset(&s->conf.macaddr); + */ + s5l8900_usb_otg_initial_reset(&s->busdev.qdev); + + /* + s->nic = qemu_new_nic(&net_s5l8900_usb_otg_info, &s->conf, + dev->qdev.info->name, dev->qdev.id, s); + qemu_format_nic_info_str(&s->nic->nc, s->conf.macaddr.a); + + register_savevm(&dev->qdev, "s5l8900.usb.otg", -1, 1, + s5l8900_usb_otg_save, s5l8900_usb_otg_load, s); + */ + + return 0; +} + +static SysBusDeviceInfo s5l8900_usb_otg_info = { + .init = s5l8900_usb_otg_init1, + .qdev.name = "s5l8900.usb.otg", + .qdev.size = sizeof(S5L8900UsbOtgState), + .qdev.reset = s5l8900_usb_otg_initial_reset, + .qdev.props = (Property[]) { + DEFINE_NIC_PROPERTIES(S5L8900UsbOtgState, conf), + DEFINE_PROP_END_OF_LIST(), + } +}; + +static void s5l8900_usb_otg_register_devices(void) +{ + sysbus_register_withprop(&s5l8900_usb_otg_info); +} + +/* Legacy helper function. Should go away when machine config files are + implemented. */ +void s5l8900_usb_otg_init(NICInfo *nd, target_phys_addr_t base, qemu_irq irq) +{ + DeviceState *dev; + SysBusDevice *s; + + //qemu_check_nic_model(nd, "s5l8900-usb-otg"); + dev = qdev_create(NULL, "s5l8900.usb.otg"); + //qdev_set_nic_properties(dev, nd); + qdev_init_nofail(dev); + s = sysbus_from_qdev(dev); + sysbus_mmio_map(s, 0, base); + sysbus_connect_irq(s, 0, irq); +} + +device_init(s5l8900_usb_otg_register_devices)