diff options
author | Peter Maydell <peter.maydell@linaro.org> | 2012-07-11 11:25:46 +0000 |
---|---|---|
committer | Peter Maydell <peter.maydell@linaro.org> | 2012-07-11 11:25:46 +0000 |
commit | be3b9cfbc4240e984cd774c37b307aea843b780b (patch) | |
tree | 41a1c89dee5f78ddc3ce5dc78dcf3f02bd2dcf58 | |
parent | c3d6b993bd16ec53ae04d288cd9623dca6ad4275 (diff) |
hw/omap_usb: Implement OMAP3 USB controller
-rw-r--r-- | hw/arm/Makefile.objs | 1 | ||||
-rw-r--r-- | hw/omap_usb.c | 506 |
2 files changed, 507 insertions, 0 deletions
diff --git a/hw/arm/Makefile.objs b/hw/arm/Makefile.objs index 236786eb5a..ad670fd29d 100644 --- a/hw/arm/Makefile.objs +++ b/hw/arm/Makefile.objs @@ -26,6 +26,7 @@ obj-y += omap1.o omap_lcdc.o omap_dma.o omap_clk.o omap_mmc.o omap_i2c.o \ omap_gpio.o omap_intc.o omap_uart.o obj-y += omap2.o omap_dss.o soc_dma.o omap_gptimer.o omap_synctimer.o \ omap_gpmc.o omap_sdrc.o omap_spi.o omap_tap.o omap_l4.o +obj-y += omap_usb.o obj-y += omap_sx1.o palm.o tsc210x.o obj-y += nseries.o blizzard.o onenand.o cbus.o tusb6010.o usb/hcd-musb.o obj-y += mst_fpga.o mainstone.o diff --git a/hw/omap_usb.c b/hw/omap_usb.c new file mode 100644 index 0000000000..7621a3c0f7 --- /dev/null +++ b/hw/omap_usb.c @@ -0,0 +1,506 @@ +/* + * TI OMAP3 High-Speed USB Host and OTG Controller emulation. + * + * Copyright (C) 2009 Nokia Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 or + * (at your option) any later version of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +#include "qemu-common.h" +#include "qemu-timer.h" +#include "usb.h" +#include "omap.h" +#include "irq.h" +#include "devices.h" +#include "hw.h" +#include "sysbus.h" + +//#define OMAP3_HSUSB_DEBUG + +#ifdef OMAP3_HSUSB_DEBUG +#define TRACE(fmt,...) fprintf(stderr, "%s: " fmt "\n", __FUNCTION__, ##__VA_ARGS__) +#else +#define TRACE(...) +#endif + +/* usb-musb.c */ +extern CPUReadMemoryFunc *musb_read[]; +extern CPUWriteMemoryFunc *musb_write[]; + +typedef struct omap3_hsusb_otg_s { + SysBusDevice busdev; + MemoryRegion iomem; + qemu_irq mc_irq; + qemu_irq dma_irq; + qemu_irq stdby_irq; + MUSBState *musb; + + uint8_t rev; + uint16_t sysconfig; + uint8_t interfsel; + uint8_t simenable; + uint8_t forcestdby; +} OMAP3HSUSBOTGState; + +static const VMStateDescription vmstate_omap3_hsusb_otg = { + .name = "omap3_hsusb_otg", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT16(sysconfig, OMAP3HSUSBOTGState), + VMSTATE_UINT8(interfsel, OMAP3HSUSBOTGState), + VMSTATE_UINT8(simenable, OMAP3HSUSBOTGState), + VMSTATE_UINT8(forcestdby, OMAP3HSUSBOTGState), + VMSTATE_END_OF_LIST() + } +}; + +static void omap3_hsusb_otg_stdby_update(OMAP3HSUSBOTGState *s) +{ + if (s->stdby_irq) { + qemu_set_irq(s->stdby_irq, s->forcestdby); + } +} + +static void omap3_hsusb_otg_reset(DeviceState *dev) +{ + OMAP3HSUSBOTGState *s = FROM_SYSBUS(OMAP3HSUSBOTGState, + sysbus_from_qdev(dev)); + s->rev = 0x33; + s->sysconfig = 0; + s->interfsel = 0x1; + s->simenable = 0; + s->forcestdby = 1; + musb_reset(s->musb); + omap3_hsusb_otg_stdby_update(s); +} + +static uint32_t omap3_hsusb_otg_read(int access, + void *opaque, + target_phys_addr_t addr) +{ + OMAP3HSUSBOTGState *s = opaque; + + if (addr < 0x200) + return musb_read[access](s->musb, addr); + if (addr < 0x400) + return musb_read[access](s->musb, 0x20 + ((addr >> 3) & 0x3c)); + switch (addr) { + case 0x400: /* OTG_REVISION */ + TRACE("OTG_REVISION: 0x%08x", s->rev); + return s->rev; + case 0x404: /* OTG_SYSCONFIG */ + TRACE("OTG_SYSCONFIG: 0x%08x", s->sysconfig); + return s->sysconfig; + case 0x408: /* OTG_SYSSTATUS */ + TRACE("OTG_SYSSTATUS: 0x00000001"); + return 1; /* reset finished */ + case 0x40c: /* OTG_INTERFSEL */ + TRACE("OTG_INTERFSEL: 0x%08x", s->interfsel); + return s->interfsel; + case 0x410: /* OTG_SIMENABLE */ + TRACE("OTG_SIMENABLE: 0x%08x", s->simenable); + return s->simenable; + case 0x414: /* OTG_FORCESTDBY */ + TRACE("OTG_FORCESTDBY: 0x%08x", s->forcestdby); + return s->forcestdby; + default: + break; + } + OMAP_BAD_REG(addr); + return 0; +} + +static void omap3_hsusb_otg_write(int access, + void *opaque, + target_phys_addr_t addr, + uint32_t value) +{ + OMAP3HSUSBOTGState *s = opaque; + + if (addr < 0x200) + musb_write[access](s->musb, addr, value); + else if (addr < 0x400) + musb_write[access](s->musb, 0x20 + ((addr >> 3) & 0x3c), value); + else switch (addr) { + case 0x400: /* OTG_REVISION */ + case 0x408: /* OTG_SYSSTATUS */ + OMAP_RO_REG(addr); + break; + case 0x404: /* OTG_SYSCONFIG */ + TRACE("OTG_SYSCONFIG = 0x%08x", value); + if (value & 2) /* SOFTRESET */ + omap3_hsusb_otg_reset(&s->busdev.qdev); + s->sysconfig = value & 0x301f; + break; + case 0x40c: /* OTG_INTERFSEL */ + TRACE("OTG_INTERFSEL = 0x%08x", value); + s->interfsel = value & 0x3; + break; + case 0x410: /* OTG_SIMENABLE */ + TRACE("OTG_SIMENABLE = 0x%08x", value); + cpu_abort(cpu_single_env, + "%s: USB simulation mode not supported\n", + __FUNCTION__); + break; + case 0x414: /* OTG_FORCESTDBY */ + TRACE("OTG_FORCESTDBY = 0x%08x", value); + s->forcestdby = value & 1; + omap3_hsusb_otg_stdby_update(s); + break; + default: + OMAP_BAD_REG(addr); + break; + } +} + +static uint32_t omap3_hsusb_otg_readb(void *opaque, target_phys_addr_t addr) +{ + return omap3_hsusb_otg_read(0, opaque, addr); +} + +static uint32_t omap3_hsusb_otg_readh(void *opaque, target_phys_addr_t addr) +{ + return omap3_hsusb_otg_read(1, opaque, addr); +} + +static uint32_t omap3_hsusb_otg_readw(void *opaque, target_phys_addr_t addr) +{ + return omap3_hsusb_otg_read(2, opaque, addr); +} + +static void omap3_hsusb_otg_writeb(void *opaque, target_phys_addr_t addr, + uint32_t value) +{ + omap3_hsusb_otg_write(0, opaque, addr, value); +} + +static void omap3_hsusb_otg_writeh(void *opaque, target_phys_addr_t addr, + uint32_t value) +{ + omap3_hsusb_otg_write(1, opaque, addr, value); +} + +static void omap3_hsusb_otg_writew(void *opaque, target_phys_addr_t addr, + uint32_t value) +{ + omap3_hsusb_otg_write(2, opaque, addr, value); +} + +static const MemoryRegionOps omap3_hsusb_otg_ops = { + .old_mmio = { + .read = { + omap3_hsusb_otg_readb, + omap3_hsusb_otg_readh, + omap3_hsusb_otg_readw, + }, + .write = { + omap3_hsusb_otg_writeb, + omap3_hsusb_otg_writeh, + omap3_hsusb_otg_writew, + }, + }, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void omap3_hsusb_musb_core_intr(void *opaque, int source, int level) +{ + OMAP3HSUSBOTGState *s = opaque; + /*TRACE("intr 0x%08x, 0x%08x, 0x%08x", source, level, musb_core_intr_get(s->musb));*/ + qemu_set_irq(s->mc_irq, level); +} + +static int omap3_hsusb_otg_init(SysBusDevice *dev) +{ + OMAP3HSUSBOTGState *s = FROM_SYSBUS(OMAP3HSUSBOTGState, dev); + sysbus_init_irq(dev, &s->mc_irq); + sysbus_init_irq(dev, &s->dma_irq); + sysbus_init_irq(dev, &s->stdby_irq); + memory_region_init_io(&s->iomem, &omap3_hsusb_otg_ops, s, + "omap3_hsusb_otg", 0x1000); + sysbus_init_mmio(dev, &s->iomem); + qdev_init_gpio_in(&dev->qdev, omap3_hsusb_musb_core_intr, musb_irq_max); + s->musb = musb_init(&dev->qdev, 0); + vmstate_register(&dev->qdev, -1, &vmstate_omap3_hsusb_otg, s); + return 0; +} + +static void omap3_hsusb_otg_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + k->init = omap3_hsusb_otg_init; + dc->reset = omap3_hsusb_otg_reset; +} + +static TypeInfo omap3_hsusb_otg_info = { + .name = "omap3_hsusb_otg", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(OMAP3HSUSBOTGState), + .class_init = omap3_hsusb_otg_class_init, +}; + +typedef struct omap3_hsusb_host_s { + SysBusDevice busdev; + MemoryRegion tll_iomem; + MemoryRegion host_iomem; + MemoryRegion ehci_iomem; + qemu_irq ehci_irq; + qemu_irq tll_irq; + + uint32_t uhh_sysconfig; + uint32_t uhh_hostconfig; + uint32_t uhh_debug_csr; + uint32_t tll_sysconfig; + uint32_t insnreg05_ulpi; +} OMAP3HSUSBHostState; + +static const VMStateDescription vmstate_omap3_hsusb_host = { + .name = "omap3_hsusb_host", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(uhh_sysconfig, OMAP3HSUSBHostState), + VMSTATE_UINT32(uhh_hostconfig, OMAP3HSUSBHostState), + VMSTATE_UINT32(uhh_debug_csr, OMAP3HSUSBHostState), + VMSTATE_UINT32(tll_sysconfig, OMAP3HSUSBHostState), + VMSTATE_UINT32(insnreg05_ulpi, OMAP3HSUSBHostState), + VMSTATE_END_OF_LIST() + } +}; + +static void omap3_hsusb_host_reset(DeviceState *dev) +{ + OMAP3HSUSBHostState *s = FROM_SYSBUS(OMAP3HSUSBHostState, + sysbus_from_qdev(dev)); + s->uhh_sysconfig = 1; + s->uhh_hostconfig = 0x700; + s->uhh_debug_csr = 0x20; + /* TODO: perform OHCI & EHCI reset */ + s->tll_sysconfig = 1; +} + +static uint32_t omap3_hsusb_host_read(void *opaque, target_phys_addr_t addr) +{ + OMAP3HSUSBHostState *s = opaque; + TRACE(OMAP_FMT_plx, addr); + + switch (addr) { + case 0x00: /* UHH_REVISION */ + return 0x10; + case 0x10: /* UHH_SYSCONFIG */ + return s->uhh_sysconfig; + case 0x14: /* UHH_SYSSTATUS */ + return 0x7; /* EHCI_RESETDONE | OHCI_RESETDONE | RESETDONE */ + case 0x40: /* UHH_HOSTCONFIG */ + return s->uhh_hostconfig; + case 0x44: /* UHH_DEBUG_CSR */ + return s->uhh_debug_csr; + default: + break; + } + OMAP_BAD_REG(addr); + return 0; +} + +static void omap3_hsusb_host_write(void *opaque, target_phys_addr_t addr, + uint32_t value) +{ + OMAP3HSUSBHostState *s = opaque; + TRACE(OMAP_FMT_plx " = 0x%08x", addr, value); + + switch (addr) { + case 0x00: /* UHH_REVISION */ + case 0x14: /* UHH_SYSSTATUS */ + OMAP_RO_REG(addr); + break; + case 0x10: /* UHH_SYSCONFIG */ + s->uhh_sysconfig = value & 0x311d; + if (value & 2) { /* SOFTRESET */ + omap3_hsusb_host_reset(&s->busdev.qdev); + } + break; + case 0x40: /* UHH_HOSTCONFIG */ + s->uhh_hostconfig = value & 0x1f3d; + break; + case 0x44: /* UHH_DEBUG_CSR */ + s->uhh_debug_csr = value & 0xf00ff; + break; + default: + OMAP_BAD_REG(addr); + break; + } +} + +static const MemoryRegionOps omap3_hsusb_host_ops = { + .old_mmio = { + .read = { + omap_badwidth_read32, + omap_badwidth_read32, + omap3_hsusb_host_read, + }, + .write = { + omap_badwidth_write32, + omap_badwidth_write32, + omap3_hsusb_host_write, + }, + }, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static uint32_t omap3_hsusb_ehci_read(void *opaque, target_phys_addr_t addr) +{ + OMAP3HSUSBHostState *s = opaque; + TRACE(OMAP_FMT_plx, addr); + switch (addr) { + case 0xa4: /* INSNREG05_ULPI */ + return s->insnreg05_ulpi; + default: + break; + } + return 0; +} + +static void omap3_hsusb_ehci_write(void *opaque, target_phys_addr_t addr, + uint32_t value) +{ + OMAP3HSUSBHostState *s = opaque; + TRACE(OMAP_FMT_plx " = 0x%08x", addr, value); + + switch (addr) { + case 0xa4: /* INSNREG05_ULPI */ + s->insnreg05_ulpi = value & 0xF0000000; + default: + break; + } +} + +static const MemoryRegionOps omap3_hsusb_ehci_ops = { + .old_mmio = { + .read = { + omap_badwidth_read32, + omap_badwidth_read32, + omap3_hsusb_ehci_read, + }, + .write = { + omap_badwidth_write32, + omap_badwidth_write32, + omap3_hsusb_ehci_write, + }, + }, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static uint32_t omap3_hsusb_tll_read(void *opaque, target_phys_addr_t addr) +{ + OMAP3HSUSBHostState *s = opaque; + TRACE(OMAP_FMT_plx, addr); + + switch (addr) { + case 0x00: /* USBTLL_REVISION */ + return 0x1; + case 0x10: /* USBTLL_SYSCONFIG */ + return s->tll_sysconfig; + case 0x14: /* USBTLL_SYSSTATUS */ + return 0x1; /* RESETDONE */ + case 0x18: /* USBTLL_IRQSTATUS */ + return 0; + case 0x1C: /* USBTLL_IRQENABLE */ + return 0; + default: + break; + } + return 0; +} + +static void omap3_hsusb_tll_write(void *opaque, target_phys_addr_t addr, + uint32_t value) +{ + OMAP3HSUSBHostState *s = opaque; + TRACE(OMAP_FMT_plx " = 0x%08x", addr, value); + + switch (addr) { + case 0x00: /* USBTLL_REVISION */ + case 0x14: /* USBTLL_SYSSTATUS */ + OMAP_RO_REG(addr); + break; + case 0x10: /* USBTLL_SYSCONFIG */ + s->tll_sysconfig = value & 0xFFFFFEE0;; + break; + default: + OMAP_BAD_REG(addr); + break; + } +} + +static const MemoryRegionOps omap3_hsusb_tll_ops = { + .old_mmio = { + .read = { + omap_badwidth_read32, + omap_badwidth_read32, + omap3_hsusb_tll_read, + }, + .write = { + omap_badwidth_write32, + omap_badwidth_write32, + omap3_hsusb_tll_write, + }, + }, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static int omap3_hsusb_host_init(SysBusDevice *dev) +{ + OMAP3HSUSBHostState *s = FROM_SYSBUS(OMAP3HSUSBHostState, dev); + sysbus_init_irq(dev, &s->ehci_irq); + sysbus_init_irq(dev, &s->tll_irq); + + memory_region_init_io(&s->tll_iomem, &omap3_hsusb_tll_ops, s, + "omap_usb.tll", 0x1000); + memory_region_init_io(&s->host_iomem, &omap3_hsusb_host_ops, s, + "omap_usb.tll", 0x400); + memory_region_init_io(&s->ehci_iomem, &omap3_hsusb_ehci_ops, s, + "omap_usb.tll", 0x400); + sysbus_init_mmio(dev, &s->tll_iomem); + sysbus_init_mmio(dev, &s->host_iomem); + sysbus_init_mmio(dev, &s->ehci_iomem); + /* OHCI is instantiated by omap3.c */ + vmstate_register(&dev->qdev, -1, &vmstate_omap3_hsusb_host, s); + return 0; +} + +static void omap3_hsusb_host_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + k->init = omap3_hsusb_host_init; + dc->reset = omap3_hsusb_host_reset; +} + +static TypeInfo omap3_hsusb_host_info = { + .name = "omap3_hsusb_host", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(OMAP3HSUSBHostState), + .class_init = omap3_hsusb_host_class_init, +}; + +static void omap3_hsusb_register_types(void) +{ + type_register_static(&omap3_hsusb_otg_info); + type_register_static(&omap3_hsusb_host_info); +} + +type_init(omap3_hsusb_register_types) |