From 7aa9b859184e97ab50e9a1e041c14b1d00acd5de Mon Sep 17 00:00:00 2001 From: balrog Date: Tue, 22 Apr 2008 03:15:10 +0000 Subject: [PATCH] Inventra MUSB-HDRC host-mode USB. git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@4232 c046a42c-6fe2-441c-8c8c-71466251a162 --- Makefile.target | 2 +- hw/devices.h | 7 + hw/nseries.c | 26 + hw/tusb6010.c | 770 +++++++++++++++++++++++++++++ hw/usb-musb.c | 1449 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ hw/usb.h | 27 ++ 6 files changed, 2280 insertions(+), 1 deletion(-) create mode 100644 hw/tusb6010.c create mode 100644 hw/usb-musb.c diff --git a/Makefile.target b/Makefile.target index 880edbf..446b5f5 100644 --- a/Makefile.target +++ b/Makefile.target @@ -612,7 +612,7 @@ OBJS+= spitz.o ide.o serial.o nand.o ecc.o OBJS+= omap1.o omap_lcdc.o omap_dma.o omap_clk.o omap_mmc.o omap_i2c.o OBJS+= omap2.o omap_dss.o OBJS+= palm.o tsc210x.o -OBJS+= nseries.o blizzard.o onenand.o vga.o cbus.o +OBJS+= nseries.o blizzard.o onenand.o vga.o cbus.o tusb6010.o usb-musb.o OBJS+= mst_fpga.o mainstone.o CPPFLAGS += -DHAS_AUDIO endif diff --git a/hw/devices.h b/hw/devices.h index e2ecdf9..869b34f 100644 --- a/hw/devices.h +++ b/hw/devices.h @@ -52,4 +52,11 @@ void *tahvo_init(qemu_irq irq, int betty); void retu_key_event(void *retu, int state); +/* tusb6010.c */ +struct tusb_s; +struct tusb_s *tusb6010_init(qemu_irq intr); +int tusb6010_sync_io(struct tusb_s *s); +int tusb6010_async_io(struct tusb_s *s); +void tusb6010_power(struct tusb_s *s, int on); + #endif diff --git a/hw/nseries.c b/hw/nseries.c index 31906c1..430b8f8 100644 --- a/hw/nseries.c +++ b/hw/nseries.c @@ -42,6 +42,7 @@ struct n800_s { int keymap[0x80]; + struct tusb_s *usb; void *retu; void *tahvo; }; @@ -565,6 +566,29 @@ static void n800_cbus_setup(struct n800_s *s) cbus_attach(cbus, s->tahvo = tahvo_init(tahvo_irq, 1)); } +static void n800_usb_power_cb(void *opaque, int line, int level) +{ + struct n800_s *s = opaque; + + tusb6010_power(s->usb, level); +} + +static void n800_usb_setup(struct n800_s *s) +{ + qemu_irq tusb_irq = omap2_gpio_in_get(s->cpu->gpif, N8X0_TUSB_INT_GPIO)[0]; + qemu_irq tusb_pwr = qemu_allocate_irqs(n800_usb_power_cb, s, 1)[0]; + struct tusb_s *tusb = tusb6010_init(tusb_irq); + + /* Using the NOR interface */ + omap_gpmc_attach(s->cpu->gpmc, N8X0_USB_ASYNC_CS, + tusb6010_async_io(tusb), 0, 0, tusb); + omap_gpmc_attach(s->cpu->gpmc, N8X0_USB_SYNC_CS, + tusb6010_sync_io(tusb), 0, 0, tusb); + + s->usb = tusb; + omap2_gpio_out_set(s->cpu->gpif, N800_TUSB_ENABLE_GPIO, tusb_pwr); +} + /* This task is normally performed by the bootloader. If we're loading * a kernel directly, we need to set up GPMC mappings ourselves. */ static void n800_gpmc_init(struct n800_s *s) @@ -891,6 +915,8 @@ static void n800_init(int ram_size, int vga_ram_size, n800_spi_setup(s); n800_dss_setup(s, ds); n800_cbus_setup(s); + if (usb_enabled) + n800_usb_setup(s); /* Setup initial (reset) machine state */ diff --git a/hw/tusb6010.c b/hw/tusb6010.c new file mode 100644 index 0000000..8318c46 --- /dev/null +++ b/hw/tusb6010.c @@ -0,0 +1,770 @@ +/* + * Texas Instruments TUSB6010 emulation. + * Based on reverse-engineering of a linux driver. + * + * Copyright (C) 2008 Nokia Corporation + * Written by Andrzej Zaborowski + * + * 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) version 3 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., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ +#include "qemu-common.h" +#include "qemu-timer.h" +#include "usb.h" +#include "omap.h" +#include "irq.h" + +struct tusb_s { + int iomemtype[2]; + qemu_irq irq; + struct musb_s *musb; + QEMUTimer *otg_timer; + QEMUTimer *pwr_timer; + + int power; + uint32_t scratch; + uint16_t test_reset; + uint32_t prcm_config; + uint32_t prcm_mngmt; + uint16_t otg_status; + uint32_t dev_config; + int host_mode; + uint32_t intr; + uint32_t intr_ok; + uint32_t mask; + uint32_t usbip_intr; + uint32_t usbip_mask; + uint32_t gpio_intr; + uint32_t gpio_mask; + uint32_t gpio_config; + uint32_t dma_intr; + uint32_t dma_mask; + uint32_t dma_map; + uint32_t dma_config; + uint32_t ep0_config; + uint32_t rx_config[15]; + uint32_t tx_config[15]; + uint32_t wkup_mask; + uint32_t pullup[2]; + uint32_t control_config; + uint32_t otg_timer_val; +}; + +#define TUSB_DEVCLOCK 60000000 /* 60 MHz */ + +#define TUSB_VLYNQ_CTRL 0x004 + +/* Mentor Graphics OTG core registers. */ +#define TUSB_BASE_OFFSET 0x400 + +/* FIFO registers, 32-bit. */ +#define TUSB_FIFO_BASE 0x600 + +/* Device System & Control registers, 32-bit. */ +#define TUSB_SYS_REG_BASE 0x800 + +#define TUSB_DEV_CONF (TUSB_SYS_REG_BASE + 0x000) +#define TUSB_DEV_CONF_USB_HOST_MODE (1 << 16) +#define TUSB_DEV_CONF_PROD_TEST_MODE (1 << 15) +#define TUSB_DEV_CONF_SOFT_ID (1 << 1) +#define TUSB_DEV_CONF_ID_SEL (1 << 0) + +#define TUSB_PHY_OTG_CTRL_ENABLE (TUSB_SYS_REG_BASE + 0x004) +#define TUSB_PHY_OTG_CTRL (TUSB_SYS_REG_BASE + 0x008) +#define TUSB_PHY_OTG_CTRL_WRPROTECT (0xa5 << 24) +#define TUSB_PHY_OTG_CTRL_O_ID_PULLUP (1 << 23) +#define TUSB_PHY_OTG_CTRL_O_VBUS_DET_EN (1 << 19) +#define TUSB_PHY_OTG_CTRL_O_SESS_END_EN (1 << 18) +#define TUSB_PHY_OTG_CTRL_TESTM2 (1 << 17) +#define TUSB_PHY_OTG_CTRL_TESTM1 (1 << 16) +#define TUSB_PHY_OTG_CTRL_TESTM0 (1 << 15) +#define TUSB_PHY_OTG_CTRL_TX_DATA2 (1 << 14) +#define TUSB_PHY_OTG_CTRL_TX_GZ2 (1 << 13) +#define TUSB_PHY_OTG_CTRL_TX_ENABLE2 (1 << 12) +#define TUSB_PHY_OTG_CTRL_DM_PULLDOWN (1 << 11) +#define TUSB_PHY_OTG_CTRL_DP_PULLDOWN (1 << 10) +#define TUSB_PHY_OTG_CTRL_OSC_EN (1 << 9) +#define TUSB_PHY_OTG_CTRL_PHYREF_CLK(v) (((v) & 3) << 7) +#define TUSB_PHY_OTG_CTRL_PD (1 << 6) +#define TUSB_PHY_OTG_CTRL_PLL_ON (1 << 5) +#define TUSB_PHY_OTG_CTRL_EXT_RPU (1 << 4) +#define TUSB_PHY_OTG_CTRL_PWR_GOOD (1 << 3) +#define TUSB_PHY_OTG_CTRL_RESET (1 << 2) +#define TUSB_PHY_OTG_CTRL_SUSPENDM (1 << 1) +#define TUSB_PHY_OTG_CTRL_CLK_MODE (1 << 0) + +/* OTG status register */ +#define TUSB_DEV_OTG_STAT (TUSB_SYS_REG_BASE + 0x00c) +#define TUSB_DEV_OTG_STAT_PWR_CLK_GOOD (1 << 8) +#define TUSB_DEV_OTG_STAT_SESS_END (1 << 7) +#define TUSB_DEV_OTG_STAT_SESS_VALID (1 << 6) +#define TUSB_DEV_OTG_STAT_VBUS_VALID (1 << 5) +#define TUSB_DEV_OTG_STAT_VBUS_SENSE (1 << 4) +#define TUSB_DEV_OTG_STAT_ID_STATUS (1 << 3) +#define TUSB_DEV_OTG_STAT_HOST_DISCON (1 << 2) +#define TUSB_DEV_OTG_STAT_LINE_STATE (3 << 0) +#define TUSB_DEV_OTG_STAT_DP_ENABLE (1 << 1) +#define TUSB_DEV_OTG_STAT_DM_ENABLE (1 << 0) + +#define TUSB_DEV_OTG_TIMER (TUSB_SYS_REG_BASE + 0x010) +#define TUSB_DEV_OTG_TIMER_ENABLE (1 << 31) +#define TUSB_DEV_OTG_TIMER_VAL(v) ((v) & 0x07ffffff) +#define TUSB_PRCM_REV (TUSB_SYS_REG_BASE + 0x014) + +/* PRCM configuration register */ +#define TUSB_PRCM_CONF (TUSB_SYS_REG_BASE + 0x018) +#define TUSB_PRCM_CONF_SFW_CPEN (1 << 24) +#define TUSB_PRCM_CONF_SYS_CLKSEL(v) (((v) & 3) << 16) + +/* PRCM management register */ +#define TUSB_PRCM_MNGMT (TUSB_SYS_REG_BASE + 0x01c) +#define TUSB_PRCM_MNGMT_SRP_FIX_TMR(v) (((v) & 0xf) << 25) +#define TUSB_PRCM_MNGMT_SRP_FIX_EN (1 << 24) +#define TUSB_PRCM_MNGMT_VBUS_VAL_TMR(v) (((v) & 0xf) << 20) +#define TUSB_PRCM_MNGMT_VBUS_VAL_FLT_EN (1 << 19) +#define TUSB_PRCM_MNGMT_DFT_CLK_DIS (1 << 18) +#define TUSB_PRCM_MNGMT_VLYNQ_CLK_DIS (1 << 17) +#define TUSB_PRCM_MNGMT_OTG_SESS_END_EN (1 << 10) +#define TUSB_PRCM_MNGMT_OTG_VBUS_DET_EN (1 << 9) +#define TUSB_PRCM_MNGMT_OTG_ID_PULLUP (1 << 8) +#define TUSB_PRCM_MNGMT_15_SW_EN (1 << 4) +#define TUSB_PRCM_MNGMT_33_SW_EN (1 << 3) +#define TUSB_PRCM_MNGMT_5V_CPEN (1 << 2) +#define TUSB_PRCM_MNGMT_PM_IDLE (1 << 1) +#define TUSB_PRCM_MNGMT_DEV_IDLE (1 << 0) + +/* Wake-up source clear and mask registers */ +#define TUSB_PRCM_WAKEUP_SOURCE (TUSB_SYS_REG_BASE + 0x020) +#define TUSB_PRCM_WAKEUP_CLEAR (TUSB_SYS_REG_BASE + 0x028) +#define TUSB_PRCM_WAKEUP_MASK (TUSB_SYS_REG_BASE + 0x02c) +#define TUSB_PRCM_WAKEUP_RESERVED_BITS (0xffffe << 13) +#define TUSB_PRCM_WGPIO_7 (1 << 12) +#define TUSB_PRCM_WGPIO_6 (1 << 11) +#define TUSB_PRCM_WGPIO_5 (1 << 10) +#define TUSB_PRCM_WGPIO_4 (1 << 9) +#define TUSB_PRCM_WGPIO_3 (1 << 8) +#define TUSB_PRCM_WGPIO_2 (1 << 7) +#define TUSB_PRCM_WGPIO_1 (1 << 6) +#define TUSB_PRCM_WGPIO_0 (1 << 5) +#define TUSB_PRCM_WHOSTDISCON (1 << 4) /* Host disconnect */ +#define TUSB_PRCM_WBUS (1 << 3) /* USB bus resume */ +#define TUSB_PRCM_WNORCS (1 << 2) /* NOR chip select */ +#define TUSB_PRCM_WVBUS (1 << 1) /* OTG PHY VBUS */ +#define TUSB_PRCM_WID (1 << 0) /* OTG PHY ID detect */ + +#define TUSB_PULLUP_1_CTRL (TUSB_SYS_REG_BASE + 0x030) +#define TUSB_PULLUP_2_CTRL (TUSB_SYS_REG_BASE + 0x034) +#define TUSB_INT_CTRL_REV (TUSB_SYS_REG_BASE + 0x038) +#define TUSB_INT_CTRL_CONF (TUSB_SYS_REG_BASE + 0x03c) +#define TUSB_USBIP_INT_SRC (TUSB_SYS_REG_BASE + 0x040) +#define TUSB_USBIP_INT_SET (TUSB_SYS_REG_BASE + 0x044) +#define TUSB_USBIP_INT_CLEAR (TUSB_SYS_REG_BASE + 0x048) +#define TUSB_USBIP_INT_MASK (TUSB_SYS_REG_BASE + 0x04c) +#define TUSB_DMA_INT_SRC (TUSB_SYS_REG_BASE + 0x050) +#define TUSB_DMA_INT_SET (TUSB_SYS_REG_BASE + 0x054) +#define TUSB_DMA_INT_CLEAR (TUSB_SYS_REG_BASE + 0x058) +#define TUSB_DMA_INT_MASK (TUSB_SYS_REG_BASE + 0x05c) +#define TUSB_GPIO_INT_SRC (TUSB_SYS_REG_BASE + 0x060) +#define TUSB_GPIO_INT_SET (TUSB_SYS_REG_BASE + 0x064) +#define TUSB_GPIO_INT_CLEAR (TUSB_SYS_REG_BASE + 0x068) +#define TUSB_GPIO_INT_MASK (TUSB_SYS_REG_BASE + 0x06c) + +/* NOR flash interrupt source registers */ +#define TUSB_INT_SRC (TUSB_SYS_REG_BASE + 0x070) +#define TUSB_INT_SRC_SET (TUSB_SYS_REG_BASE + 0x074) +#define TUSB_INT_SRC_CLEAR (TUSB_SYS_REG_BASE + 0x078) +#define TUSB_INT_MASK (TUSB_SYS_REG_BASE + 0x07c) +#define TUSB_INT_SRC_TXRX_DMA_DONE (1 << 24) +#define TUSB_INT_SRC_USB_IP_CORE (1 << 17) +#define TUSB_INT_SRC_OTG_TIMEOUT (1 << 16) +#define TUSB_INT_SRC_VBUS_SENSE_CHNG (1 << 15) +#define TUSB_INT_SRC_ID_STATUS_CHNG (1 << 14) +#define TUSB_INT_SRC_DEV_WAKEUP (1 << 13) +#define TUSB_INT_SRC_DEV_READY (1 << 12) +#define TUSB_INT_SRC_USB_IP_TX (1 << 9) +#define TUSB_INT_SRC_USB_IP_RX (1 << 8) +#define TUSB_INT_SRC_USB_IP_VBUS_ERR (1 << 7) +#define TUSB_INT_SRC_USB_IP_VBUS_REQ (1 << 6) +#define TUSB_INT_SRC_USB_IP_DISCON (1 << 5) +#define TUSB_INT_SRC_USB_IP_CONN (1 << 4) +#define TUSB_INT_SRC_USB_IP_SOF (1 << 3) +#define TUSB_INT_SRC_USB_IP_RST_BABBLE (1 << 2) +#define TUSB_INT_SRC_USB_IP_RESUME (1 << 1) +#define TUSB_INT_SRC_USB_IP_SUSPEND (1 << 0) + +#define TUSB_GPIO_REV (TUSB_SYS_REG_BASE + 0x080) +#define TUSB_GPIO_CONF (TUSB_SYS_REG_BASE + 0x084) +#define TUSB_DMA_CTRL_REV (TUSB_SYS_REG_BASE + 0x100) +#define TUSB_DMA_REQ_CONF (TUSB_SYS_REG_BASE + 0x104) +#define TUSB_EP0_CONF (TUSB_SYS_REG_BASE + 0x108) +#define TUSB_EP_IN_SIZE (TUSB_SYS_REG_BASE + 0x10c) +#define TUSB_DMA_EP_MAP (TUSB_SYS_REG_BASE + 0x148) +#define TUSB_EP_OUT_SIZE (TUSB_SYS_REG_BASE + 0x14c) +#define TUSB_EP_MAX_PACKET_SIZE_OFFSET (TUSB_SYS_REG_BASE + 0x188) +#define TUSB_SCRATCH_PAD (TUSB_SYS_REG_BASE + 0x1c4) +#define TUSB_WAIT_COUNT (TUSB_SYS_REG_BASE + 0x1c8) +#define TUSB_PROD_TEST_RESET (TUSB_SYS_REG_BASE + 0x1d8) + +#define TUSB_DIDR1_LO (TUSB_SYS_REG_BASE + 0x1f8) +#define TUSB_DIDR1_HI (TUSB_SYS_REG_BASE + 0x1fc) + +/* Device System & Control register bitfields */ +#define TUSB_INT_CTRL_CONF_INT_RLCYC(v) (((v) & 0x7) << 18) +#define TUSB_INT_CTRL_CONF_INT_POLARITY (1 << 17) +#define TUSB_INT_CTRL_CONF_INT_MODE (1 << 16) +#define TUSB_GPIO_CONF_DMAREQ(v) (((v) & 0x3f) << 24) +#define TUSB_DMA_REQ_CONF_BURST_SIZE(v) (((v) & 3) << 26) +#define TUSB_DMA_REQ_CONF_DMA_RQ_EN(v) (((v) & 0x3f) << 20) +#define TUSB_DMA_REQ_CONF_DMA_RQ_ASR(v) (((v) & 0xf) << 16) +#define TUSB_EP0_CONFIG_SW_EN (1 << 8) +#define TUSB_EP0_CONFIG_DIR_TX (1 << 7) +#define TUSB_EP0_CONFIG_XFR_SIZE(v) ((v) & 0x7f) +#define TUSB_EP_CONFIG_SW_EN (1 << 31) +#define TUSB_EP_CONFIG_XFR_SIZE(v) ((v) & 0x7fffffff) +#define TUSB_PROD_TEST_RESET_VAL 0xa596 + +int tusb6010_sync_io(struct tusb_s *s) +{ + return s->iomemtype[0]; +} + +int tusb6010_async_io(struct tusb_s *s) +{ + return s->iomemtype[1]; +} + +static void tusb_intr_update(struct tusb_s *s) +{ + if (s->control_config & TUSB_INT_CTRL_CONF_INT_POLARITY) + qemu_set_irq(s->irq, s->intr & ~s->mask & s->intr_ok); + else + qemu_set_irq(s->irq, (!(s->intr & ~s->mask)) & s->intr_ok); +} + +static void tusb_usbip_intr_update(struct tusb_s *s) +{ + /* TX interrupt in the MUSB */ + if (s->usbip_intr & 0x0000ffff & ~s->usbip_mask) + s->intr |= TUSB_INT_SRC_USB_IP_TX; + else + s->intr &= ~TUSB_INT_SRC_USB_IP_TX; + + /* RX interrupt in the MUSB */ + if (s->usbip_intr & 0xffff0000 & ~s->usbip_mask) + s->intr |= TUSB_INT_SRC_USB_IP_RX; + else + s->intr &= ~TUSB_INT_SRC_USB_IP_RX; + + /* XXX: What about TUSB_INT_SRC_USB_IP_CORE? */ + + tusb_intr_update(s); +} + +static void tusb_dma_intr_update(struct tusb_s *s) +{ + if (s->dma_intr & ~s->dma_mask) + s->intr |= TUSB_INT_SRC_TXRX_DMA_DONE; + else + s->intr &= ~TUSB_INT_SRC_TXRX_DMA_DONE; + + tusb_intr_update(s); +} + +static void tusb_gpio_intr_update(struct tusb_s *s) +{ + /* TODO: How is this signalled? */ +} + +extern CPUReadMemoryFunc *musb_read[]; +extern CPUWriteMemoryFunc *musb_write[]; + +static uint32_t tusb_async_readb(void *opaque, target_phys_addr_t addr) +{ + struct tusb_s *s = (struct tusb_s *) opaque; + + switch (addr & 0xfff) { + case TUSB_BASE_OFFSET ... (TUSB_BASE_OFFSET | 0x1ff): + return musb_read[0](s->musb, addr & 0x1ff); + + case TUSB_FIFO_BASE ... (TUSB_FIFO_BASE | 0x1ff): + return musb_read[0](s->musb, 0x20 + ((addr >> 3) & 0x3c)); + } + + printf("%s: unknown register at %03x\n", + __FUNCTION__, (int) (addr & 0xfff)); + return 0; +} + +static uint32_t tusb_async_readh(void *opaque, target_phys_addr_t addr) +{ + struct tusb_s *s = (struct tusb_s *) opaque; + + switch (addr & 0xfff) { + case TUSB_BASE_OFFSET ... (TUSB_BASE_OFFSET | 0x1ff): + return musb_read[1](s->musb, addr & 0x1ff); + + case TUSB_FIFO_BASE ... (TUSB_FIFO_BASE | 0x1ff): + return musb_read[1](s->musb, 0x20 + ((addr >> 3) & 0x3c)); + } + + printf("%s: unknown register at %03x\n", + __FUNCTION__, (int) (addr & 0xfff)); + return 0; +} + +static uint32_t tusb_async_readw(void *opaque, target_phys_addr_t addr) +{ + struct tusb_s *s = (struct tusb_s *) opaque; + int offset = addr & 0xfff; + int epnum; + uint32_t ret; + + switch (offset) { + case TUSB_DEV_CONF: + return s->dev_config; + + case TUSB_BASE_OFFSET ... (TUSB_BASE_OFFSET | 0x1ff): + return musb_read[2](s->musb, offset & 0x1ff); + + case TUSB_FIFO_BASE ... (TUSB_FIFO_BASE | 0x1ff): + return musb_read[2](s->musb, 0x20 + ((addr >> 3) & 0x3c)); + + case TUSB_PHY_OTG_CTRL_ENABLE: + case TUSB_PHY_OTG_CTRL: + return 0x00; /* TODO */ + + case TUSB_DEV_OTG_STAT: + ret = s->otg_status; +#if 0 + if (!(s->prcm_mngmt & TUSB_PRCM_MNGMT_OTG_VBUS_DET_EN)) + ret &= ~TUSB_DEV_OTG_STAT_VBUS_VALID; +#endif + return ret; + case TUSB_DEV_OTG_TIMER: + return s->otg_timer_val; + + case TUSB_PRCM_REV: + return 0x20; + case TUSB_PRCM_CONF: + return s->prcm_config; + case TUSB_PRCM_MNGMT: + return s->prcm_mngmt; + case TUSB_PRCM_WAKEUP_SOURCE: + case TUSB_PRCM_WAKEUP_CLEAR: /* TODO: What does this one return? */ + return 0x00000000; + case TUSB_PRCM_WAKEUP_MASK: + return s->wkup_mask; + + case TUSB_PULLUP_1_CTRL: + return s->pullup[0]; + case TUSB_PULLUP_2_CTRL: + return s->pullup[1]; + + case TUSB_INT_CTRL_REV: + return 0x20; + case TUSB_INT_CTRL_CONF: + return s->control_config; + + case TUSB_USBIP_INT_SRC: + case TUSB_USBIP_INT_SET: /* TODO: What do these two return? */ + case TUSB_USBIP_INT_CLEAR: + return s->usbip_intr; + case TUSB_USBIP_INT_MASK: + return s->usbip_mask; + + case TUSB_DMA_INT_SRC: + case TUSB_DMA_INT_SET: /* TODO: What do these two return? */ + case TUSB_DMA_INT_CLEAR: + return s->dma_intr; + case TUSB_DMA_INT_MASK: + return s->dma_mask; + + case TUSB_GPIO_INT_SRC: /* TODO: What do these two return? */ + case TUSB_GPIO_INT_SET: + case TUSB_GPIO_INT_CLEAR: + return s->gpio_intr; + case TUSB_GPIO_INT_MASK: + return s->gpio_mask; + + case TUSB_INT_SRC: + case TUSB_INT_SRC_SET: /* TODO: What do these two return? */ + case TUSB_INT_SRC_CLEAR: + return s->intr; + case TUSB_INT_MASK: + return s->mask; + + case TUSB_GPIO_REV: + return 0x30; + case TUSB_GPIO_CONF: + return s->gpio_config; + + case TUSB_DMA_CTRL_REV: + return 0x30; + case TUSB_DMA_REQ_CONF: + return s->dma_config; + case TUSB_EP0_CONF: + return s->ep0_config; + case TUSB_EP_IN_SIZE ... (TUSB_EP_IN_SIZE + 0x3b): + epnum = (offset - TUSB_EP_IN_SIZE) >> 2; + return s->tx_config[epnum]; + case TUSB_DMA_EP_MAP: + return s->dma_map; + case TUSB_EP_OUT_SIZE ... (TUSB_EP_OUT_SIZE + 0x3b): + epnum = (offset - TUSB_EP_OUT_SIZE) >> 2; + return s->rx_config[epnum]; + case TUSB_EP_MAX_PACKET_SIZE_OFFSET ... + (TUSB_EP_MAX_PACKET_SIZE_OFFSET + 0x3b): + epnum = (offset - TUSB_EP_MAX_PACKET_SIZE_OFFSET) >> 2; + return 0x00000000; /* TODO */ + case TUSB_WAIT_COUNT: + return 0x00; /* TODO */ + + case TUSB_SCRATCH_PAD: + return s->scratch; + + case TUSB_PROD_TEST_RESET: + return s->test_reset; + + /* DIE IDs */ + case TUSB_DIDR1_LO: + return 0xa9453c59; + case TUSB_DIDR1_HI: + return 0x54059adf; + } + + printf("%s: unknown register at %03x\n", __FUNCTION__, offset); + return 0; +} + +static void tusb_async_writeb(void *opaque, target_phys_addr_t addr, + uint32_t value) +{ + struct tusb_s *s = (struct tusb_s *) opaque; + + switch (addr & 0xfff) { + case TUSB_BASE_OFFSET ... (TUSB_BASE_OFFSET | 0x1ff): + musb_write[0](s->musb, addr & 0x1ff, value); + break; + + case TUSB_FIFO_BASE ... (TUSB_FIFO_BASE | 0x1ff): + musb_write[0](s->musb, 0x20 + ((addr >> 3) & 0x3c), value); + break; + + default: + printf("%s: unknown register at %03x\n", + __FUNCTION__, (int) (addr & 0xfff)); + return; + } +} + +static void tusb_async_writeh(void *opaque, target_phys_addr_t addr, + uint32_t value) +{ + struct tusb_s *s = (struct tusb_s *) opaque; + + switch (addr & 0xfff) { + case TUSB_BASE_OFFSET ... (TUSB_BASE_OFFSET | 0x1ff): + musb_write[1](s->musb, addr & 0x1ff, value); + break; + + case TUSB_FIFO_BASE ... (TUSB_FIFO_BASE | 0x1ff): + musb_write[1](s->musb, 0x20 + ((addr >> 3) & 0x3c), value); + break; + + default: + printf("%s: unknown register at %03x\n", + __FUNCTION__, (int) (addr & 0xfff)); + return; + } +} + +static void tusb_async_writew(void *opaque, target_phys_addr_t addr, + uint32_t value) +{ + struct tusb_s *s = (struct tusb_s *) opaque; + int offset = addr & 0xfff; + int epnum; + + switch (offset) { + case TUSB_VLYNQ_CTRL: + break; + + case TUSB_BASE_OFFSET ... (TUSB_BASE_OFFSET | 0x1ff): + musb_write[2](s->musb, offset & 0x1ff, value); + break; + + case TUSB_FIFO_BASE ... (TUSB_FIFO_BASE | 0x1ff): + musb_write[2](s->musb, 0x20 + ((addr >> 3) & 0x3c), value); + break; + + case TUSB_DEV_CONF: + s->dev_config = value; + s->host_mode = (value & TUSB_DEV_CONF_USB_HOST_MODE); + if (value & TUSB_DEV_CONF_PROD_TEST_MODE) + cpu_abort(cpu_single_env, "%s: Product Test mode not allowed\n", + __FUNCTION__); + break; + + case TUSB_PHY_OTG_CTRL_ENABLE: + case TUSB_PHY_OTG_CTRL: + return; /* TODO */ + case TUSB_DEV_OTG_TIMER: + s->otg_timer_val = value; + if (value & TUSB_DEV_OTG_TIMER_ENABLE) + qemu_mod_timer(s->otg_timer, qemu_get_clock(vm_clock) + + muldiv64(TUSB_DEV_OTG_TIMER_VAL(value), + ticks_per_sec, TUSB_DEVCLOCK)); + else + qemu_del_timer(s->otg_timer); + break; + + case TUSB_PRCM_CONF: + s->prcm_config = value; + break; + case TUSB_PRCM_MNGMT: + s->prcm_mngmt = value; + break; + case TUSB_PRCM_WAKEUP_CLEAR: + break; + case TUSB_PRCM_WAKEUP_MASK: + s->wkup_mask = value; + break; + + case TUSB_PULLUP_1_CTRL: + s->pullup[0] = value; + break; + case TUSB_PULLUP_2_CTRL: + s->pullup[1] = value; + break; + case TUSB_INT_CTRL_CONF: + s->control_config = value; + tusb_intr_update(s); + break; + + case TUSB_USBIP_INT_SET: + s->usbip_intr |= value; + tusb_usbip_intr_update(s); + break; + case TUSB_USBIP_INT_CLEAR: + s->usbip_intr &= ~value; + tusb_usbip_intr_update(s); + musb_core_intr_clear(s->musb, ~value); + break; + case TUSB_USBIP_INT_MASK: + s->usbip_mask = value; + tusb_usbip_intr_update(s); + break; + + case TUSB_DMA_INT_SET: + s->dma_intr |= value; + tusb_dma_intr_update(s); + break; + case TUSB_DMA_INT_CLEAR: + s->dma_intr &= ~value; + tusb_dma_intr_update(s); + break; + case TUSB_DMA_INT_MASK: + s->dma_mask = value; + tusb_dma_intr_update(s); + break; + + case TUSB_GPIO_INT_SET: + s->gpio_intr |= value; + tusb_gpio_intr_update(s); + break; + case TUSB_GPIO_INT_CLEAR: + s->gpio_intr &= ~value; + tusb_gpio_intr_update(s); + break; + case TUSB_GPIO_INT_MASK: + s->gpio_mask = value; + tusb_gpio_intr_update(s); + break; + + case TUSB_INT_SRC_SET: + s->intr |= value; + tusb_intr_update(s); + break; + case TUSB_INT_SRC_CLEAR: + s->intr &= ~value; + tusb_intr_update(s); + break; + case TUSB_INT_MASK: + s->mask = value; + tusb_intr_update(s); + break; + + case TUSB_GPIO_CONF: + s->gpio_config = value; + break; + case TUSB_DMA_REQ_CONF: + s->dma_config = value; + break; + case TUSB_EP0_CONF: + s->ep0_config = value & 0x1ff; + musb_set_size(s->musb, 0, TUSB_EP0_CONFIG_XFR_SIZE(value), + value & TUSB_EP0_CONFIG_DIR_TX); + break; + case TUSB_EP_IN_SIZE ... (TUSB_EP_IN_SIZE + 0x3b): + epnum = (offset - TUSB_EP_IN_SIZE) >> 2; + s->tx_config[epnum] = value; + musb_set_size(s->musb, epnum + 1, TUSB_EP_CONFIG_XFR_SIZE(value), 1); + break; + case TUSB_DMA_EP_MAP: + s->dma_map = value; + break; + case TUSB_EP_OUT_SIZE ... (TUSB_EP_OUT_SIZE + 0x3b): + epnum = (offset - TUSB_EP_OUT_SIZE) >> 2; + s->rx_config[epnum] = value; + musb_set_size(s->musb, epnum + 1, TUSB_EP_CONFIG_XFR_SIZE(value), 0); + break; + case TUSB_EP_MAX_PACKET_SIZE_OFFSET ... + (TUSB_EP_MAX_PACKET_SIZE_OFFSET + 0x3b): + epnum = (offset - TUSB_EP_MAX_PACKET_SIZE_OFFSET) >> 2; + return; /* TODO */ + case TUSB_WAIT_COUNT: + return; /* TODO */ + + case TUSB_SCRATCH_PAD: + s->scratch = value; + break; + + case TUSB_PROD_TEST_RESET: + s->test_reset = value; + break; + + default: + printf("%s: unknown register at %03x\n", __FUNCTION__, offset); + return; + } +} + +static CPUReadMemoryFunc *tusb_async_readfn[] = { + tusb_async_readb, + tusb_async_readh, + tusb_async_readw, +}; + +static CPUWriteMemoryFunc *tusb_async_writefn[] = { + tusb_async_writeb, + tusb_async_writeh, + tusb_async_writew, +}; + +static void tusb_otg_tick(void *opaque) +{ + struct tusb_s *s = (struct tusb_s *) opaque; + + s->otg_timer_val = 0; + s->intr |= TUSB_INT_SRC_OTG_TIMEOUT; + tusb_intr_update(s); +} + +static void tusb_power_tick(void *opaque) +{ + struct tusb_s *s = (struct tusb_s *) opaque; + + if (s->power) { + s->intr_ok = ~0; + tusb_intr_update(s); + } +} + +static void tusb_musb_core_intr(void *opaque, int source, int level) +{ + struct tusb_s *s = (struct tusb_s *) opaque; + uint16_t otg_status = s->otg_status; + + switch (source) { + case musb_set_vbus: + if (level) + otg_status |= TUSB_DEV_OTG_STAT_VBUS_VALID; + else + otg_status &= ~TUSB_DEV_OTG_STAT_VBUS_VALID; + + /* XXX: only if TUSB_PHY_OTG_CTRL_OTG_VBUS_DET_EN set? */ + /* XXX: only if TUSB_PRCM_MNGMT_OTG_VBUS_DET_EN set? */ + if (s->otg_status != otg_status) { + s->otg_status = otg_status; + s->intr |= TUSB_INT_SRC_VBUS_SENSE_CHNG; + tusb_intr_update(s); + } + break; + + case musb_set_session: + /* XXX: only if TUSB_PHY_OTG_CTRL_OTG_SESS_END_EN set? */ + /* XXX: only if TUSB_PRCM_MNGMT_OTG_SESS_END_EN set? */ + if (level) { + s->otg_status |= TUSB_DEV_OTG_STAT_SESS_VALID; + s->otg_status &= ~TUSB_DEV_OTG_STAT_SESS_END; + } else { + s->otg_status &= ~TUSB_DEV_OTG_STAT_SESS_VALID; + s->otg_status |= TUSB_DEV_OTG_STAT_SESS_END; + } + + /* XXX: some IRQ or anything? */ + break; + + case musb_irq_tx: + case musb_irq_rx: + s->usbip_intr = musb_core_intr_get(s->musb); + /* Fall through. */ + default: + if (level) + s->intr |= 1 << source; + else + s->intr &= ~(1 << source); + tusb_intr_update(s); + break; + } +} + +struct tusb_s *tusb6010_init(qemu_irq intr) +{ + struct tusb_s *s = qemu_mallocz(sizeof(*s)); + + s->test_reset = TUSB_PROD_TEST_RESET_VAL; + s->host_mode = 0; + s->dev_config = 0; + s->otg_status = 0; /* !TUSB_DEV_OTG_STAT_ID_STATUS means host mode */ + s->power = 0; + s->mask = 0xffffffff; + s->intr = 0x00000000; + s->otg_timer_val = 0; + s->iomemtype[1] = cpu_register_io_memory(0, tusb_async_readfn, + tusb_async_writefn, s); + s->irq = intr; + s->otg_timer = qemu_new_timer(vm_clock, tusb_otg_tick, s); + s->pwr_timer = qemu_new_timer(vm_clock, tusb_power_tick, s); + s->musb = musb_init(qemu_allocate_irqs(tusb_musb_core_intr, s, + __musb_irq_max)); + + return s; +} + +void tusb6010_power(struct tusb_s *s, int on) +{ + if (!on) + s->power = 0; + else if (!s->power && on) { + s->power = 1; + + /* Pull the interrupt down after TUSB6010 comes up. */ + s->intr_ok = 0; + tusb_intr_update(s); + qemu_mod_timer(s->pwr_timer, + qemu_get_clock(vm_clock) + ticks_per_sec / 2); + } +} diff --git a/hw/usb-musb.c b/hw/usb-musb.c new file mode 100644 index 0000000..1499b31 --- /dev/null +++ b/hw/usb-musb.c @@ -0,0 +1,1449 @@ +/* + * "Inventra" High-speed Dual-Role Controller (MUSB-HDRC), Mentor Graphics, + * USB2.0 OTG compliant core used in various chips. + * + * Copyright (C) 2008 Nokia Corporation + * Written by Andrzej Zaborowski + * + * 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) version 3 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., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + * + * Only host-mode and non-DMA accesses are currently supported. + */ +#include "qemu-common.h" +#include "qemu-timer.h" +#include "usb.h" +#include "irq.h" + +/* Common USB registers */ +#define MUSB_HDRC_FADDR 0x00 /* 8-bit */ +#define MUSB_HDRC_POWER 0x01 /* 8-bit */ + +#define MUSB_HDRC_INTRTX 0x02 /* 16-bit */ +#define MUSB_HDRC_INTRRX 0x04 +#define MUSB_HDRC_INTRTXE 0x06 +#define MUSB_HDRC_INTRRXE 0x08 +#define MUSB_HDRC_INTRUSB 0x0a /* 8 bit */ +#define MUSB_HDRC_INTRUSBE 0x0b /* 8 bit */ +#define MUSB_HDRC_FRAME 0x0c /* 16-bit */ +#define MUSB_HDRC_INDEX 0x0e /* 8 bit */ +#define MUSB_HDRC_TESTMODE 0x0f /* 8 bit */ + +/* Per-EP registers in indexed mode */ +#define MUSB_HDRC_EP_IDX 0x10 /* 8-bit */ + +/* EP FIFOs */ +#define MUSB_HDRC_FIFO 0x20 + +/* Additional Control Registers */ +#define MUSB_HDRC_DEVCTL 0x60 /* 8 bit */ + +/* These are indexed */ +#define MUSB_HDRC_TXFIFOSZ 0x62 /* 8 bit (see masks) */ +#define MUSB_HDRC_RXFIFOSZ 0x63 /* 8 bit (see masks) */ +#define MUSB_HDRC_TXFIFOADDR 0x64 /* 16 bit offset shifted right 3 */ +#define MUSB_HDRC_RXFIFOADDR 0x66 /* 16 bit offset shifted right 3 */ + +/* Some more registers */ +#define MUSB_HDRC_VCTRL 0x68 /* 8 bit */ +#define MUSB_HDRC_HWVERS 0x6c /* 8 bit */ + +/* Added in HDRC 1.9(?) & MHDRC 1.4 */ +/* ULPI pass-through */ +#define MUSB_HDRC_ULPI_VBUSCTL 0x70 +#define MUSB_HDRC_ULPI_REGDATA 0x74 +#define MUSB_HDRC_ULPI_REGADDR 0x75 +#define MUSB_HDRC_ULPI_REGCTL 0x76 + +/* Extended config & PHY control */ +#define MUSB_HDRC_ENDCOUNT 0x78 /* 8 bit */ +#define MUSB_HDRC_DMARAMCFG 0x79 /* 8 bit */ +#define MUSB_HDRC_PHYWAIT 0x7a /* 8 bit */ +#define MUSB_HDRC_PHYVPLEN 0x7b /* 8 bit */ +#define MUSB_HDRC_HS_EOF1 0x7c /* 8 bit, units of 546.1 us */ +#define MUSB_HDRC_FS_EOF1 0x7d /* 8 bit, units of 533.3 ns */ +#define MUSB_HDRC_LS_EOF1 0x7e /* 8 bit, units of 1.067 us */ + +/* Per-EP BUSCTL registers */ +#define MUSB_HDRC_BUSCTL 0x80 + +/* Per-EP registers in flat mode */ +#define MUSB_HDRC_EP 0x100 + +/* offsets to registers in flat model */ +#define MUSB_HDRC_TXMAXP 0x00 /* 16 bit apparently */ +#define MUSB_HDRC_TXCSR 0x02 /* 16 bit apparently */ +#define MUSB_HDRC_CSR0 MUSB_HDRC_TXCSR /* re-used for EP0 */ +#define MUSB_HDRC_RXMAXP 0x04 /* 16 bit apparently */ +#define MUSB_HDRC_RXCSR 0x06 /* 16 bit apparently */ +#define MUSB_HDRC_RXCOUNT 0x08 /* 16 bit apparently */ +#define MUSB_HDRC_COUNT0 MUSB_HDRC_RXCOUNT /* re-used for EP0 */ +#define MUSB_HDRC_TXTYPE 0x0a /* 8 bit apparently */ +#define MUSB_HDRC_TYPE0 MUSB_HDRC_TXTYPE /* re-used for EP0 */ +#define MUSB_HDRC_TXINTERVAL 0x0b /* 8 bit apparently */ +#define MUSB_HDRC_NAKLIMIT0 MUSB_HDRC_TXINTERVAL /* re-used for EP0 */ +#define MUSB_HDRC_RXTYPE 0x0c /* 8 bit apparently */ +#define MUSB_HDRC_RXINTERVAL 0x0d /* 8 bit apparently */ +#define MUSB_HDRC_FIFOSIZE 0x0f /* 8 bit apparently */ +#define MUSB_HDRC_CONFIGDATA MGC_O_HDRC_FIFOSIZE /* re-used for EP0 */ + +/* "Bus control" registers */ +#define MUSB_HDRC_TXFUNCADDR 0x00 +#define MUSB_HDRC_TXHUBADDR 0x02 +#define MUSB_HDRC_TXHUBPORT 0x03 + +#define MUSB_HDRC_RXFUNCADDR 0x04 +#define MUSB_HDRC_RXHUBADDR 0x06 +#define MUSB_HDRC_RXHUBPORT 0x07 + +/* + * MUSBHDRC Register bit masks + */ + +/* POWER */ +#define MGC_M_POWER_ISOUPDATE 0x80 +#define MGC_M_POWER_SOFTCONN 0x40 +#define MGC_M_POWER_HSENAB 0x20 +#define MGC_M_POWER_HSMODE 0x10 +#define MGC_M_POWER_RESET 0x08 +#define MGC_M_POWER_RESUME 0x04 +#define MGC_M_POWER_SUSPENDM 0x02 +#define MGC_M_POWER_ENSUSPEND 0x01 + +/* INTRUSB */ +#define MGC_M_INTR_SUSPEND 0x01 +#define MGC_M_INTR_RESUME 0x02 +#define MGC_M_INTR_RESET 0x04 +#define MGC_M_INTR_BABBLE 0x04 +#define MGC_M_INTR_SOF 0x08 +#define MGC_M_INTR_CONNECT 0x10 +#define MGC_M_INTR_DISCONNECT 0x20 +#define MGC_M_INTR_SESSREQ 0x40 +#define MGC_M_INTR_VBUSERROR 0x80 /* FOR SESSION END */ +#define MGC_M_INTR_EP0 0x01 /* FOR EP0 INTERRUPT */ + +/* DEVCTL */ +#define MGC_M_DEVCTL_BDEVICE 0x80 +#define MGC_M_DEVCTL_FSDEV 0x40 +#define MGC_M_DEVCTL_LSDEV 0x20 +#define MGC_M_DEVCTL_VBUS 0x18 +#define MGC_S_DEVCTL_VBUS 3 +#define MGC_M_DEVCTL_HM 0x04 +#define MGC_M_DEVCTL_HR 0x02 +#define MGC_M_DEVCTL_SESSION 0x01 + +/* TESTMODE */ +#define MGC_M_TEST_FORCE_HOST 0x80 +#define MGC_M_TEST_FIFO_ACCESS 0x40 +#define MGC_M_TEST_FORCE_FS 0x20 +#define MGC_M_TEST_FORCE_HS 0x10 +#define MGC_M_TEST_PACKET 0x08 +#define MGC_M_TEST_K 0x04 +#define MGC_M_TEST_J 0x02 +#define MGC_M_TEST_SE0_NAK 0x01 + +/* CSR0 */ +#define MGC_M_CSR0_FLUSHFIFO 0x0100 +#define MGC_M_CSR0_TXPKTRDY 0x0002 +#define MGC_M_CSR0_RXPKTRDY 0x0001 + +/* CSR0 in Peripheral mode */ +#define MGC_M_CSR0_P_SVDSETUPEND 0x0080 +#define MGC_M_CSR0_P_SVDRXPKTRDY 0x0040 +#define MGC_M_CSR0_P_SENDSTALL 0x0020 +#define MGC_M_CSR0_P_SETUPEND 0x0010 +#define MGC_M_CSR0_P_DATAEND 0x0008 +#define MGC_M_CSR0_P_SENTSTALL 0x0004 + +/* CSR0 in Host mode */ +#define MGC_M_CSR0_H_NO_PING 0x0800 +#define MGC_M_CSR0_H_WR_DATATOGGLE 0x0400 /* set to allow setting: */ +#define MGC_M_CSR0_H_DATATOGGLE 0x0200 /* data toggle control */ +#define MGC_M_CSR0_H_NAKTIMEOUT 0x0080 +#define MGC_M_CSR0_H_STATUSPKT 0x0040 +#define MGC_M_CSR0_H_REQPKT 0x0020 +#define MGC_M_CSR0_H_ERROR 0x0010 +#define MGC_M_CSR0_H_SETUPPKT 0x0008 +#define MGC_M_CSR0_H_RXSTALL 0x0004 + +/* CONFIGDATA */ +#define MGC_M_CONFIGDATA_MPRXE 0x80 /* auto bulk pkt combining */ +#define MGC_M_CONFIGDATA_MPTXE 0x40 /* auto bulk pkt splitting */ +#define MGC_M_CONFIGDATA_BIGENDIAN 0x20 +#define MGC_M_CONFIGDATA_HBRXE 0x10 /* HB-ISO for RX */ +#define MGC_M_CONFIGDATA_HBTXE 0x08 /* HB-ISO for TX */ +#define MGC_M_CONFIGDATA_DYNFIFO 0x04 /* dynamic FIFO sizing */ +#define MGC_M_CONFIGDATA_SOFTCONE 0x02 /* SoftConnect */ +#define MGC_M_CONFIGDATA_UTMIDW 0x01 /* Width, 0 => 8b, 1 => 16b */ + +/* TXCSR in Peripheral and Host mode */ +#define MGC_M_TXCSR_AUTOSET 0x8000 +#define MGC_M_TXCSR_ISO 0x4000 +#define MGC_M_TXCSR_MODE 0x2000 +#define MGC_M_TXCSR_DMAENAB 0x1000 +#define MGC_M_TXCSR_FRCDATATOG 0x0800 +#define MGC_M_TXCSR_DMAMODE 0x0400 +#define MGC_M_TXCSR_CLRDATATOG 0x0040 +#define MGC_M_TXCSR_FLUSHFIFO 0x0008 +#define MGC_M_TXCSR_FIFONOTEMPTY 0x0002 +#define MGC_M_TXCSR_TXPKTRDY 0x0001 + +/* TXCSR in Peripheral mode */ +#define MGC_M_TXCSR_P_INCOMPTX 0x0080 +#define MGC_M_TXCSR_P_SENTSTALL 0x0020 +#define MGC_M_TXCSR_P_SENDSTALL 0x0010 +#define MGC_M_TXCSR_P_UNDERRUN 0x0004 + +/* TXCSR in Host mode */ +#define MGC_M_TXCSR_H_WR_DATATOGGLE 0x0200 +#define MGC_M_TXCSR_H_DATATOGGLE 0x0100 +#define MGC_M_TXCSR_H_NAKTIMEOUT 0x0080 +#define MGC_M_TXCSR_H_RXSTALL 0x0020 +#define MGC_M_TXCSR_H_ERROR 0x0004 + +/* RXCSR in Peripheral and Host mode */ +#define MGC_M_RXCSR_AUTOCLEAR 0x8000 +#define MGC_M_RXCSR_DMAENAB 0x2000 +#define MGC_M_RXCSR_DISNYET 0x1000 +#define MGC_M_RXCSR_DMAMODE 0x0800 +#define MGC_M_RXCSR_INCOMPRX 0x0100 +#define MGC_M_RXCSR_CLRDATATOG 0x0080 +#define MGC_M_RXCSR_FLUSHFIFO 0x0010 +#define MGC_M_RXCSR_DATAERROR 0x0008 +#define MGC_M_RXCSR_FIFOFULL 0x0002 +#define MGC_M_RXCSR_RXPKTRDY 0x0001 + +/* RXCSR in Peripheral mode */ +#define MGC_M_RXCSR_P_ISO 0x4000 +#define MGC_M_RXCSR_P_SENTSTALL 0x0040 +#define MGC_M_RXCSR_P_SENDSTALL 0x0020 +#define MGC_M_RXCSR_P_OVERRUN 0x0004 + +/* RXCSR in Host mode */ +#define MGC_M_RXCSR_H_AUTOREQ 0x4000 +#define MGC_M_RXCSR_H_WR_DATATOGGLE 0x0400 +#define MGC_M_RXCSR_H_DATATOGGLE 0x0200 +#define MGC_M_RXCSR_H_RXSTALL 0x0040 +#define MGC_M_RXCSR_H_REQPKT 0x0020 +#define MGC_M_RXCSR_H_ERROR 0x0004 + +/* HUBADDR */ +#define MGC_M_HUBADDR_MULTI_TT 0x80 + +/* ULPI: Added in HDRC 1.9(?) & MHDRC 1.4 */ +#define MGC_M_ULPI_VBCTL_USEEXTVBUSIND 0x02 +#define MGC_M_ULPI_VBCTL_USEEXTVBUS 0x01 +#define MGC_M_ULPI_REGCTL_INT_ENABLE 0x08 +#define MGC_M_ULPI_REGCTL_READNOTWRITE 0x04 +#define MGC_M_ULPI_REGCTL_COMPLETE 0x02 +#define MGC_M_ULPI_REGCTL_REG 0x01 + +static void musb_attach(USBPort *port, USBDevice *dev); + +struct musb_s { + qemu_irq *irqs; + USBPort port; + + int idx; + uint8_t devctl; + uint8_t power; + uint8_t faddr; + + uint8_t intr; + uint8_t mask; + uint16_t tx_intr; + uint16_t tx_mask; + uint16_t rx_intr; + uint16_t rx_mask; + + int setup_len; + int session; + + uint32_t buf[0x2000]; + + struct musb_ep_s { + uint16_t faddr[2]; + uint8_t haddr[2]; + uint8_t hport[2]; + uint16_t csr[2]; + uint16_t maxp[2]; + uint16_t rxcount; + uint8_t type[2]; + uint8_t interval[2]; + uint8_t config; + uint8_t fifosize; + int timeout[2]; /* Always in microframes */ + + uint32_t *buf[2]; + int fifolen[2]; + int fifostart[2]; + int fifoaddr[2]; + USBPacket packey[2]; + int status[2]; + int ext_size[2]; + + /* For callbacks' use */ + int epnum; + int interrupt[2]; + struct musb_s *musb; + USBCallback *delayed_cb[2]; + QEMUTimer *intv_timer[2]; + /* Duplicating the world since 2008!... probably we should have 32 + * logical, single endpoints instead. */ + } ep[16]; +} *musb_init(qemu_irq *irqs) +{ + struct musb_s *s = qemu_mallocz(sizeof(*s)); + int i; + + s->irqs = irqs; + + s->faddr = 0x00; + s->power = MGC_M_POWER_HSENAB; + s->tx_intr = 0x0000; + s->rx_intr = 0x0000; + s->tx_mask = 0xffff; + s->rx_mask = 0xffff; + s->intr = 0x00; + s->mask = 0x06; + s->idx = 0; + + /* TODO: _DW */ + s->ep[0].config = MGC_M_CONFIGDATA_SOFTCONE | MGC_M_CONFIGDATA_DYNFIFO; + for (i = 0; i < 16; i ++) { + s->ep[i].fifosize = 64; + s->ep[i].maxp[0] = 0x40; + s->ep[i].maxp[1] = 0x40; + s->ep[i].musb = s; + s->ep[i].epnum = i; + } + + qemu_register_usb_port(&s->port, s, 0, musb_attach); + + return s; +} + +static void musb_vbus_set(struct musb_s *s, int level) +{ + if (level) + s->devctl |= 3 << MGC_S_DEVCTL_VBUS; + else + s->devctl &= ~MGC_M_DEVCTL_VBUS; + + qemu_set_irq(s->irqs[musb_set_vbus], level); +} + +static void musb_intr_set(struct musb_s *s, int line, int level) +{ + if (!level) { + s->intr &= ~(1 << line); + qemu_irq_lower(s->irqs[line]); + } else if (s->mask & (1 << line)) { + s->intr |= 1 << line; + qemu_irq_raise(s->irqs[line]); + } +} + +static void musb_tx_intr_set(struct musb_s *s, int line, int level) +{ + if (!level) { + s->tx_intr &= ~(1 << line); + if (!s->tx_intr) + qemu_irq_lower(s->irqs[musb_irq_tx]); + } else if (s->tx_mask & (1 << line)) { + s->tx_intr |= 1 << line; + qemu_irq_raise(s->irqs[musb_irq_tx]); + } +} + +static void musb_rx_intr_set(struct musb_s *s, int line, int level) +{ + if (line) { + if (!level) { + s->rx_intr &= ~(1 << line); + if (!s->rx_intr) + qemu_irq_lower(s->irqs[musb_irq_rx]); + } else if (s->rx_mask & (1 << line)) { + s->rx_intr |= 1 << line; + qemu_irq_raise(s->irqs[musb_irq_rx]); + } + } else + musb_tx_intr_set(s, line, level); +} + +uint32_t musb_core_intr_get(struct musb_s *s) +{ + return (s->rx_intr << 15) | s->tx_intr; +} + +void musb_core_intr_clear(struct musb_s *s, uint32_t mask) +{ + if (s->rx_intr) { + s->rx_intr &= mask >> 15; + if (!s->rx_intr) + qemu_irq_lower(s->irqs[musb_irq_rx]); + } + + if (s->tx_intr) { + s->tx_intr &= mask & 0xffff; + if (!s->tx_intr) + qemu_irq_lower(s->irqs[musb_irq_tx]); + } +} + +void musb_set_size(struct musb_s *s, int epnum, int size, int is_tx) +{ + s->ep[epnum].ext_size[!is_tx] = size; + s->ep[epnum].fifostart[0] = 0; + s->ep[epnum].fifostart[1] = 0; + s->ep[epnum].fifolen[0] = 0; + s->ep[epnum].fifolen[1] = 0; +} + +static void musb_session_update(struct musb_s *s, int prev_dev, int prev_sess) +{ + int detect_prev = prev_dev && prev_sess; + int detect = !!s->port.dev && s->session; + + if (detect && !detect_prev) { + /* Let's skip the ID pin sense and VBUS sense formalities and + * and signal a successful SRP directly. This should work at least + * for the Linux driver stack. */ + musb_intr_set(s, musb_irq_connect, 1); + + if (s->port.dev->speed == USB_SPEED_LOW) { + s->devctl &= ~MGC_M_DEVCTL_FSDEV; + s->devctl |= MGC_M_DEVCTL_LSDEV; + } else { + s->devctl |= MGC_M_DEVCTL_FSDEV; + s->devctl &= ~MGC_M_DEVCTL_LSDEV; + } + + /* A-mode? */ + s->devctl &= ~MGC_M_DEVCTL_BDEVICE; + + /* Host-mode bit? */ + s->devctl |= MGC_M_DEVCTL_HM; +#if 1 + musb_vbus_set(s, 1); +#endif + } else if (!detect && detect_prev) { +#if 1 + musb_vbus_set(s, 0); +#endif + } +} + +/* Attach or detach a device on our only port. */ +static void musb_attach(USBPort *port, USBDevice *dev) +{ + struct musb_s *s = (struct musb_s *) port->opaque; + USBDevice *curr; + + port = &s->port; + curr = port->dev; + + if (dev) { + if (curr) { + usb_attach(port, NULL); + /* TODO: signal some interrupts */ + } + + musb_intr_set(s, musb_irq_vbus_request, 1); + + /* Send the attach message to device */ + usb_send_msg(dev, USB_MSG_ATTACH); + } else if (curr) { + /* Send the detach message */ + usb_send_msg(curr, USB_MSG_DETACH); + + musb_intr_set(s, musb_irq_disconnect, 1); + } + + port->dev = dev; + + musb_session_update(s, !!curr, s->session); +} + +static inline void musb_cb_tick0(void *opaque) +{ + struct musb_ep_s *ep = (struct musb_ep_s *) opaque; + + ep->delayed_cb[0](&ep->packey[0], opaque); +} + +static inline void musb_cb_tick1(void *opaque) +{ + struct musb_ep_s *ep = (struct musb_ep_s *) opaque; + + ep->delayed_cb[1](&ep->packey[1], opaque); +} + +#define musb_cb_tick (dir ? musb_cb_tick1 : musb_cb_tick0) + +static inline void musb_schedule_cb(USBPacket *packey, void *opaque, int dir) +{ + struct musb_ep_s *ep = (struct musb_ep_s *) opaque; + int timeout = 0; + + if (ep->status[dir] == USB_RET_NAK) + timeout = ep->timeout[dir]; + else if (ep->interrupt[dir]) + timeout = 8; + else + return musb_cb_tick(opaque); + + if (!ep->intv_timer[dir]) + ep->intv_timer[dir] = qemu_new_timer(vm_clock, musb_cb_tick, opaque); + + qemu_mod_timer(ep->intv_timer[dir], qemu_get_clock(vm_clock) + + muldiv64(timeout, ticks_per_sec, 8000)); +} + +static void musb_schedule0_cb(USBPacket *packey, void *opaque) +{ + return musb_schedule_cb(packey, opaque, 0); +} + +static void musb_schedule1_cb(USBPacket *packey, void *opaque) +{ + return musb_schedule_cb(packey, opaque, 1); +} + +static int musb_timeout(int ttype, int speed, int val) +{ +#if 1 + return val << 3; +#endif + + switch (ttype) { + case USB_ENDPOINT_XFER_CONTROL: + if (val < 2) + return 0; + else if (speed == USB_SPEED_HIGH) + return 1 << (val - 1); + else + return 8 << (val - 1); + + case USB_ENDPOINT_XFER_INT: + if (speed == USB_SPEED_HIGH) + if (val < 2) + return 0; + else + return 1 << (val - 1); + else + return val << 3; + + case USB_ENDPOINT_XFER_BULK: + case USB_ENDPOINT_XFER_ISOC: + if (val < 2) + return 0; + else if (speed == USB_SPEED_HIGH) + return 1 << (val - 1); + else + return 8 << (val - 1); + /* TODO: what with low-speed Bulk and Isochronous? */ + } + + cpu_abort(cpu_single_env, "bad interval\n"); +} + +static inline void musb_packet(struct musb_s *s, struct musb_ep_s *ep, + int epnum, int pid, int len, USBCallback cb, int dir) +{ + int ret; + int idx = epnum && dir; + int ttype; + + /* ep->type[0,1] contains: + * in bits 7:6 the speed (0 - invalid, 1 - high, 2 - full, 3 - slow) + * in bits 5:4 the transfer type (BULK / INT) + * in bits 3:0 the EP num + */ + ttype = epnum ? (ep->type[idx] >> 4) & 3 : 0; + + ep->timeout[dir] = musb_timeout(ttype, + ep->type[idx] >> 6, ep->interval[idx]); + ep->interrupt[dir] = ttype == USB_ENDPOINT_XFER_INT; + ep->delayed_cb[dir] = cb; + cb = dir ? musb_schedule1_cb : musb_schedule0_cb; + + ep->packey[dir].pid = pid; + /* A wild guess on the FADDR semantics... */ + ep->packey[dir].devaddr = ep->faddr[idx]; + ep->packey[dir].devep = ep->type[idx] & 0xf; + ep->packey[dir].data = (void *) ep->buf[idx]; + ep->packey[dir].len = len; + ep->packey[dir].complete_cb = cb; + ep->packey[dir].complete_opaque = ep; + + if (s->port.dev) + ret = s->port.dev->handle_packet(s->port.dev, &ep->packey[dir]); + else + ret = USB_RET_NODEV; + + if (ret == USB_RET_ASYNC) { + ep->status[dir] = len; + return; + } + + ep->status[dir] = ret; + usb_packet_complete(&ep->packey[dir]); +} + +static void musb_tx_packet_complete(USBPacket *packey, void *opaque) +{ + /* Unfortunately we can't use packey->devep because that's the remote + * endpoint number and may be different than our local. */ + struct musb_ep_s *ep = (struct musb_ep_s *) opaque; + int epnum = ep->epnum; + struct musb_s *s = ep->musb; + + ep->fifostart[0] = 0; + ep->fifolen[0] = 0; +#ifdef CLEAR_NAK + if (ep->status[0] != USB_RET_NAK) { +#endif + if (epnum) + ep->csr[0] &= ~(MGC_M_TXCSR_FIFONOTEMPTY | MGC_M_TXCSR_TXPKTRDY); + else + ep->csr[0] &= ~MGC_M_CSR0_TXPKTRDY; +#ifdef CLEAR_NAK + } +#endif + + /* Clear all of the error bits first */ + if (epnum) + ep->csr[0] &= ~(MGC_M_TXCSR_H_ERROR | MGC_M_TXCSR_H_RXSTALL | + MGC_M_TXCSR_H_NAKTIMEOUT); + else + ep->csr[0] &= ~(MGC_M_CSR0_H_ERROR | MGC_M_CSR0_H_RXSTALL | + MGC_M_CSR0_H_NAKTIMEOUT | MGC_M_CSR0_H_NO_PING); + + if (ep->status[0] == USB_RET_STALL) { + /* Command not supported by target! */ + ep->status[0] = 0; + + if (epnum) + ep->csr[0] |= MGC_M_TXCSR_H_RXSTALL; + else + ep->csr[0] |= MGC_M_CSR0_H_RXSTALL; + } + + if (ep->status[0] == USB_RET_NAK) { + ep->status[0] = 0; + + /* NAK timeouts are only generated in Bulk transfers and + * Data-errors in Isochronous. */ + if (ep->interrupt[0]) { + return; + } + + if (epnum) + ep->csr[0] |= MGC_M_TXCSR_H_NAKTIMEOUT; + else + ep->csr[0] |= MGC_M_CSR0_H_NAKTIMEOUT; + } + + if (ep->status[0] < 0) { + if (ep->status[0] == USB_RET_BABBLE) + musb_intr_set(s, musb_irq_rst_babble, 1); + + /* Pretend we've tried three times already and failed (in + * case of USB_TOKEN_SETUP). */ + if (epnum) + ep->csr[0] |= MGC_M_TXCSR_H_ERROR; + else + ep->csr[0] |= MGC_M_CSR0_H_ERROR; + + musb_tx_intr_set(s, epnum, 1); + return; + } + /* TODO: check len for over/underruns of an OUT packet? */ + +#ifdef SETUPLEN_HACK + if (!epnum && ep->packey[0].pid == USB_TOKEN_SETUP) + s->setup_len = ep->packey[0].data[6]; +#endif + + /* In DMA mode: if no error, assert DMA request for this EP, + * and skip the interrupt. */ + musb_tx_intr_set(s, epnum, 1); +} + +static void musb_rx_packet_complete(USBPacket *packey, void *opaque) +{ + /* Unfortunately we can't use packey->devep because that's the remote + * endpoint number and may be different than our local. */ + struct musb_ep_s *ep = (struct musb_ep_s *) opaque; + int epnum = ep->epnum; + struct musb_s *s = ep->musb; + + ep->fifostart[1] = 0; + ep->fifolen[1] = 0; + +#ifdef CLEAR_NAK + if (ep->status[1] != USB_RET_NAK) { +#endif + ep->csr[1] &= ~MGC_M_RXCSR_H_REQPKT; + if (!epnum) + ep->csr[0] &= ~MGC_M_CSR0_H_REQPKT; +#ifdef CLEAR_NAK + } +#endif + + /* Clear all of the imaginable error bits first */ + ep->csr[1] &= ~(MGC_M_RXCSR_H_ERROR | MGC_M_RXCSR_H_RXSTALL | + MGC_M_RXCSR_DATAERROR); + if (!epnum) + ep->csr[0] &= ~(MGC_M_CSR0_H_ERROR | MGC_M_CSR0_H_RXSTALL | + MGC_M_CSR0_H_NAKTIMEOUT | MGC_M_CSR0_H_NO_PING); + + if (ep->status[1] == USB_RET_STALL) { + ep->status[1] = 0; + packey->len = 0; + + ep->csr[1] |= MGC_M_RXCSR_H_RXSTALL; + if (!epnum) + ep->csr[0] |= MGC_M_CSR0_H_RXSTALL; + } + + if (ep->status[1] == USB_RET_NAK) { + ep->status[1] = 0; + + /* NAK timeouts are only generated in Bulk transfers and + * Data-errors in Isochronous. */ + if (ep->interrupt[1]) + return musb_packet(s, ep, epnum, USB_TOKEN_IN, + packey->len, musb_rx_packet_complete, 1); + + ep->csr[1] |= MGC_M_RXCSR_DATAERROR; + if (!epnum) + ep->csr[0] |= MGC_M_CSR0_H_NAKTIMEOUT; + } + + if (ep->status[1] < 0) { + if (ep->status[1] == USB_RET_BABBLE) { + musb_intr_set(s, musb_irq_rst_babble, 1); + return; + } + + /* Pretend we've tried three times already and failed (in + * case of a control transfer). */ + ep->csr[1] |= MGC_M_RXCSR_H_ERROR; + if (!epnum) + ep->csr[0] |= MGC_M_CSR0_H_ERROR; + + musb_rx_intr_set(s, epnum, 1); + return; + } + /* TODO: check len for over/underruns of an OUT packet? */ + /* TODO: perhaps make use of e->ext_size[1] here. */ + + packey->len = ep->status[1]; + + if (!(ep->csr[1] & (MGC_M_RXCSR_H_RXSTALL | MGC_M_RXCSR_DATAERROR))) { + ep->csr[1] |= MGC_M_RXCSR_FIFOFULL | MGC_M_RXCSR_RXPKTRDY; + if (!epnum) + ep->csr[0] |= MGC_M_CSR0_RXPKTRDY; + + ep->rxcount = packey->len; /* XXX: MIN(packey->len, ep->maxp[1]); */ + /* In DMA mode: assert DMA request for this EP */ + } + + /* Only if DMA has not been asserted */ + musb_rx_intr_set(s, epnum, 1); +} + +static void musb_tx_rdy(struct musb_s *s, int epnum) +{ + struct musb_ep_s *ep = s->ep + epnum; + int pid; + int total, valid = 0; + + ep->fifostart[0] += ep->fifolen[0]; + ep->fifolen[0] = 0; + + /* XXX: how's the total size of the packet retrieved exactly in + * the generic case? */ + total = ep->maxp[0] & 0x3ff; + + if (ep->ext_size[0]) { + total = ep->ext_size[0]; + ep->ext_size[0] = 0; + valid = 1; + } + + /* If the packet is not fully ready yet, wait for a next segment. */ + if (epnum && (ep->fifostart[0] << 2) < total) + return; + + if (!valid) + total = ep->fifostart[0] << 2; + + pid = USB_TOKEN_OUT; + if (!epnum && (ep->csr[0] & MGC_M_CSR0_H_SETUPPKT)) { + pid = USB_TOKEN_SETUP; + if (total != 8) + printf("%s: illegal SETUPPKT length of %i bytes\n", + __FUNCTION__, total); + /* Controller should retry SETUP packets three times on errors + * but it doesn't make sense for us to do that. */ + } + + return musb_packet(s, ep, epnum, pid, + total, musb_tx_packet_complete, 0); +} + +static void musb_rx_req(struct musb_s *s, int epnum) +{ + struct musb_ep_s *ep = s->ep + epnum; + int total; + + /* If we already have a packet, which didn't fit into the + * 64 bytes of the FIFO, only move the FIFO start and return. (Obsolete) */ + if (ep->packey[1].pid == USB_TOKEN_IN && ep->status[1] >= 0 && + (ep->fifostart[1] << 2) + ep->rxcount < + ep->packey[1].len) { + ep->fifostart[1] += ep->rxcount >> 2; + ep->fifolen[1] = 0; + + ep->rxcount = MIN(ep->packey[0].len - (ep->fifostart[1] << 2), + ep->maxp[1]); + + ep->csr[1] &= ~MGC_M_RXCSR_H_REQPKT; + if (!epnum) + ep->csr[0] &= ~MGC_M_CSR0_H_REQPKT; + + /* Clear all of the error bits first */ + ep->csr[1] &= ~(MGC_M_RXCSR_H_ERROR | MGC_M_RXCSR_H_RXSTALL | + MGC_M_RXCSR_DATAERROR); + if (!epnum) + ep->csr[0] &= ~(MGC_M_CSR0_H_ERROR | MGC_M_CSR0_H_RXSTALL | + MGC_M_CSR0_H_NAKTIMEOUT | MGC_M_CSR0_H_NO_PING); + + ep->csr[1] |= MGC_M_RXCSR_FIFOFULL | MGC_M_RXCSR_RXPKTRDY; + if (!epnum) + ep->csr[0] |= MGC_M_CSR0_RXPKTRDY; + musb_rx_intr_set(s, epnum, 1); + return; + } + + /* The driver sets maxp[1] to 64 or less because it knows the hardware + * FIFO is this deep. Bigger packets get split in + * usb_generic_handle_packet but we can also do the splitting locally + * for performance. It turns out we can also have a bigger FIFO and + * ignore the limit set in ep->maxp[1]. The Linux MUSB driver deals + * OK with single packets of even 32KB and we avoid splitting, however + * usb_msd.c sometimes sends a packet bigger than what Linux expects + * (e.g. 8192 bytes instead of 4096) and we get an OVERRUN. Splitting + * hides this overrun from Linux. Up to 4096 everything is fine + * though. Currently this is disabled. + * + * XXX: mind ep->fifosize. */ + total = MIN(ep->maxp[1] & 0x3ff, sizeof(s->buf)); + +#ifdef SETUPLEN_HACK + /* Why should *we* do that instead of Linux? */ + if (!epnum) { + if (ep->packey[0].devaddr == 2) + total = MIN(s->setup_len, 8); + else + total = MIN(s->setup_len, 64); + s->setup_len -= total; + } +#endif + + return musb_packet(s, ep, epnum, USB_TOKEN_IN, + total, musb_rx_packet_complete, 1); +} + +static void musb_ep_frame_cancel(struct musb_ep_s *ep, int dir) +{ + if (ep->intv_timer[dir]) + qemu_del_timer(ep->intv_timer[dir]); +} + +/* Bus control */ +static uint8_t musb_busctl_readb(void *opaque, int ep, int addr) +{ + struct musb_s *s = (struct musb_s *) opaque; + + switch (addr) { + /* For USB2.0 HS hubs only */ + case MUSB_HDRC_TXHUBADDR: + return s->ep[ep].haddr[0]; + case MUSB_HDRC_TXHUBPORT: + return s->ep[ep].hport[0]; + case MUSB_HDRC_RXHUBADDR: + return s->ep[ep].haddr[1]; + case MUSB_HDRC_RXHUBPORT: + return s->ep[ep].hport[1]; + + default: + printf("%s: unknown register at %02x\n", __FUNCTION__, addr); + return 0x00; + }; +} + +static void musb_busctl_writeb(void *opaque, int ep, int addr, uint8_t value) +{ + struct musb_s *s = (struct musb_s *) opaque; + + switch (addr) { + case MUSB_HDRC_TXHUBADDR: + s->ep[ep].haddr[0] = value; + break; + case MUSB_HDRC_TXHUBPORT: + s->ep[ep].hport[0] = value; + break; + case MUSB_HDRC_RXHUBADDR: + s->ep[ep].haddr[1] = value; + break; + case MUSB_HDRC_RXHUBPORT: + s->ep[ep].hport[1] = value; + break; + + default: + printf("%s: unknown register at %02x\n", __FUNCTION__, addr); + }; +} + +static uint16_t musb_busctl_readh(void *opaque, int ep, int addr) +{ + struct musb_s *s = (struct musb_s *) opaque; + + switch (addr) { + case MUSB_HDRC_TXFUNCADDR: + return s->ep[ep].faddr[0]; + case MUSB_HDRC_RXFUNCADDR: + return s->ep[ep].faddr[1]; + + default: + return musb_busctl_readb(s, ep, addr) | + (musb_busctl_readb(s, ep, addr | 1) << 8); + }; +} + +static void musb_busctl_writeh(void *opaque, int ep, int addr, uint16_t value) +{ + struct musb_s *s = (struct musb_s *) opaque; + + switch (addr) { + case MUSB_HDRC_TXFUNCADDR: + s->ep[ep].faddr[0] = value; + break; + case MUSB_HDRC_RXFUNCADDR: + s->ep[ep].faddr[1] = value; + break; + + default: + musb_busctl_writeb(s, ep, addr, value & 0xff); + musb_busctl_writeb(s, ep, addr | 1, value >> 8); + }; +} + +/* Endpoint control */ +static uint8_t musb_ep_readb(void *opaque, int ep, int addr) +{ + struct musb_s *s = (struct musb_s *) opaque; + + switch (addr) { + case MUSB_HDRC_TXTYPE: + return s->ep[ep].type[0]; + case MUSB_HDRC_TXINTERVAL: + return s->ep[ep].interval[0]; + case MUSB_HDRC_RXTYPE: + return s->ep[ep].type[1]; + case MUSB_HDRC_RXINTERVAL: + return s->ep[ep].interval[1]; + case (MUSB_HDRC_FIFOSIZE & ~1): + return 0x00; + case MUSB_HDRC_FIFOSIZE: + return ep ? s->ep[ep].fifosize : s->ep[ep].config; + + default: + printf("%s: unknown register at %02x\n", __FUNCTION__, addr); + return 0x00; + }; +} + +static void musb_ep_writeb(void *opaque, int ep, int addr, uint8_t value) +{ + struct musb_s *s = (struct musb_s *) opaque; + + switch (addr) { + case MUSB_HDRC_TXTYPE: + s->ep[ep].type[0] = value; + break; + case MUSB_HDRC_TXINTERVAL: + s->ep[ep].interval[0] = value; + musb_ep_frame_cancel(&s->ep[ep], 0); + break; + case MUSB_HDRC_RXTYPE: + s->ep[ep].type[1] = value; + break; + case MUSB_HDRC_RXINTERVAL: + s->ep[ep].interval[1] = value; + musb_ep_frame_cancel(&s->ep[ep], 1); + break; + case (MUSB_HDRC_FIFOSIZE & ~1): + break; + case MUSB_HDRC_FIFOSIZE: + printf("%s: somebody messes with fifosize (now %i bytes)\n", + __FUNCTION__, value); + s->ep[ep].fifosize = value; + break; + + default: + printf("%s: unknown register at %02x\n", __FUNCTION__, addr); + }; +} + +static uint16_t musb_ep_readh(void *opaque, int ep, int addr) +{ + struct musb_s *s = (struct musb_s *) opaque; + uint16_t ret; + + switch (addr) { + case MUSB_HDRC_TXMAXP: + return s->ep[ep].maxp[0]; + case MUSB_HDRC_TXCSR: + return s->ep[ep].csr[0]; + case MUSB_HDRC_RXMAXP: + return s->ep[ep].maxp[1]; + case MUSB_HDRC_RXCSR: + ret = s->ep[ep].csr[1]; + + /* TODO: This and other bits probably depend on + * ep->csr[1] & MGC_M_RXCSR_AUTOCLEAR. */ + if (s->ep[ep].csr[1] & MGC_M_RXCSR_AUTOCLEAR) + s->ep[ep].csr[1] &= ~MGC_M_RXCSR_RXPKTRDY; + + return ret; + case MUSB_HDRC_RXCOUNT: + return s->ep[ep].rxcount; + + default: + return musb_ep_readb(s, ep, addr) | + (musb_ep_readb(s, ep, addr | 1) << 8); + }; +} + +static void musb_ep_writeh(void *opaque, int ep, int addr, uint16_t value) +{ + struct musb_s *s = (struct musb_s *) opaque; + + switch (addr) { + case MUSB_HDRC_TXMAXP: + s->ep[ep].maxp[0] = value; + break; + case MUSB_HDRC_TXCSR: + if (ep) { + s->ep[ep].csr[0] &= value & 0xa6; + s->ep[ep].csr[0] |= value & 0xff59; + } else { + s->ep[ep].csr[0] &= value & 0x85; + s->ep[ep].csr[0] |= value & 0xf7a; + } + + musb_ep_frame_cancel(&s->ep[ep], 0); + + if ((ep && (value & MGC_M_TXCSR_FLUSHFIFO)) || + (!ep && (value & MGC_M_CSR0_FLUSHFIFO))) { + s->ep[ep].fifolen[0] = 0; + s->ep[ep].fifostart[0] = 0; + if (ep) + s->ep[ep].csr[0] &= + ~(MGC_M_TXCSR_FIFONOTEMPTY | MGC_M_TXCSR_TXPKTRDY); + else + s->ep[ep].csr[0] &= + ~(MGC_M_CSR0_TXPKTRDY | MGC_M_CSR0_RXPKTRDY); + } + if ( + (ep && +#ifdef CLEAR_NAK + (value & MGC_M_TXCSR_TXPKTRDY) && + !(value & MGC_M_TXCSR_H_NAKTIMEOUT)) || +#else + (value & MGC_M_TXCSR_TXPKTRDY)) || +#endif + (!ep && +#ifdef CLEAR_NAK + (value & MGC_M_CSR0_TXPKTRDY) && + !(value & MGC_M_CSR0_H_NAKTIMEOUT))) +#else + (value & MGC_M_CSR0_TXPKTRDY))) +#endif + musb_tx_rdy(s, ep); + if (!ep && + (value & MGC_M_CSR0_H_REQPKT) && +#ifdef CLEAR_NAK + !(value & (MGC_M_CSR0_H_NAKTIMEOUT | + MGC_M_CSR0_RXPKTRDY))) +#else + !(value & MGC_M_CSR0_RXPKTRDY)) +#endif + musb_rx_req(s, ep); + break; + + case MUSB_HDRC_RXMAXP: + s->ep[ep].maxp[1] = value; + break; + case MUSB_HDRC_RXCSR: + /* (DMA mode only) */ + if ( + (value & MGC_M_RXCSR_H_AUTOREQ) && + !(value & MGC_M_RXCSR_RXPKTRDY) && + (s->ep[ep].csr[1] & MGC_M_RXCSR_RXPKTRDY)) + value |= MGC_M_RXCSR_H_REQPKT; + + s->ep[ep].csr[1] &= 0x102 | (value & 0x4d); + s->ep[ep].csr[1] |= value & 0xfeb0; + + musb_ep_frame_cancel(&s->ep[ep], 1); + + if (value & MGC_M_RXCSR_FLUSHFIFO) { + s->ep[ep].fifolen[1] = 0; + s->ep[ep].fifostart[1] = 0; + s->ep[ep].csr[1] &= ~(MGC_M_RXCSR_FIFOFULL | MGC_M_RXCSR_RXPKTRDY); + /* If double buffering and we have two packets ready, flush + * only the first one and set up the fifo at the second packet. */ + } +#ifdef CLEAR_NAK + if ((value & MGC_M_RXCSR_H_REQPKT) && !(value & MGC_M_RXCSR_DATAERROR)) +#else + if (value & MGC_M_RXCSR_H_REQPKT) +#endif + musb_rx_req(s, ep); + break; + case MUSB_HDRC_RXCOUNT: + s->ep[ep].rxcount = value; + break; + + default: + musb_ep_writeb(s, ep, addr, value & 0xff); + musb_ep_writeb(s, ep, addr | 1, value >> 8); + }; +} + +/* Generic control */ +static uint32_t musb_readb(void *opaque, target_phys_addr_t addr) +{ + struct musb_s *s = (struct musb_s *) opaque; + int ep, i; + uint8_t ret; + + switch (addr) { + case MUSB_HDRC_FADDR: + return s->faddr; + case MUSB_HDRC_POWER: + return s->power; + case MUSB_HDRC_INTRUSB: + ret = s->intr; + for (i = 0; i < sizeof(ret) * 8; i ++) + if (ret & (1 << i)) + musb_intr_set(s, i, 0); + return ret; + case MUSB_HDRC_INTRUSBE: + return s->mask; + case MUSB_HDRC_INDEX: + return s->idx; + case MUSB_HDRC_TESTMODE: + return 0x00; + + case MUSB_HDRC_EP_IDX ... (MUSB_HDRC_EP_IDX + 0xf): + return musb_ep_readb(s, s->idx, addr & 0xf); + + case MUSB_HDRC_DEVCTL: + return s->devctl; + + case MUSB_HDRC_TXFIFOSZ: + case MUSB_HDRC_RXFIFOSZ: + case MUSB_HDRC_VCTRL: + /* TODO */ + return 0x00; + + case MUSB_HDRC_HWVERS: + return (1 << 10) | 400; + + case (MUSB_HDRC_VCTRL | 1): + case (MUSB_HDRC_HWVERS | 1): + case (MUSB_HDRC_DEVCTL | 1): + return 0x00; + + case MUSB_HDRC_BUSCTL ... (MUSB_HDRC_BUSCTL + 0x7f): + ep = (addr >> 3) & 0xf; + return musb_busctl_readb(s, ep, addr & 0x7); + + case MUSB_HDRC_EP ... (MUSB_HDRC_EP + 0xff): + ep = (addr >> 4) & 0xf; + return musb_ep_readb(s, ep, addr & 0xf); + + default: + printf("%s: unknown register at %02x\n", __FUNCTION__, (int) addr); + return 0x00; + }; +} + +static void musb_writeb(void *opaque, target_phys_addr_t addr, uint32_t value) +{ + struct musb_s *s = (struct musb_s *) opaque; + int ep; + + switch (addr) { + case MUSB_HDRC_FADDR: + s->faddr = value & 0x7f; + break; + case MUSB_HDRC_POWER: + s->power = (value & 0xef) | (s->power & 0x10); + /* MGC_M_POWER_RESET is also read-only in Peripheral Mode */ + if ((value & MGC_M_POWER_RESET) && s->port.dev) { + usb_send_msg(s->port.dev, USB_MSG_RESET); + /* Negotiate high-speed operation if MGC_M_POWER_HSENAB is set. */ + if ((value & MGC_M_POWER_HSENAB) && + s->port.dev->speed == USB_SPEED_HIGH) + s->power |= MGC_M_POWER_HSMODE; /* Success */ + /* Restart frame counting. */ + } + if (value & MGC_M_POWER_SUSPENDM) { + /* When all transfers finish, suspend and if MGC_M_POWER_ENSUSPEND + * is set, also go into low power mode. Frame counting stops. */ + /* XXX: Cleared when the interrupt register is read */ + } + if (value & MGC_M_POWER_RESUME) { + /* Wait 20ms and signal resuming on the bus. Frame counting + * restarts. */ + } + break; + case MUSB_HDRC_INTRUSB: + break; + case MUSB_HDRC_INTRUSBE: + s->mask = value & 0xff; + break; + case MUSB_HDRC_INDEX: + s->idx = value & 0xf; + break; + case MUSB_HDRC_TESTMODE: + break; + + case MUSB_HDRC_EP_IDX ... (MUSB_HDRC_EP_IDX + 0xf): + musb_ep_writeb(s, s->idx, addr & 0xf, value); + break; + + case MUSB_HDRC_DEVCTL: + s->session = !!(value & MGC_M_DEVCTL_SESSION); + musb_session_update(s, + !!s->port.dev, + !!(s->devctl & MGC_M_DEVCTL_SESSION)); + + /* It seems this is the only R/W bit in this register? */ + s->devctl &= ~MGC_M_DEVCTL_SESSION; + s->devctl |= value & MGC_M_DEVCTL_SESSION; + break; + + case MUSB_HDRC_TXFIFOSZ: + case MUSB_HDRC_RXFIFOSZ: + case MUSB_HDRC_VCTRL: + /* TODO */ + break; + + case (MUSB_HDRC_VCTRL | 1): + case (MUSB_HDRC_DEVCTL | 1): + break; + + case MUSB_HDRC_BUSCTL ... (MUSB_HDRC_BUSCTL + 0x7f): + ep = (addr >> 3) & 0xf; + musb_busctl_writeb(s, ep, addr & 0x7, value); + break; + + case MUSB_HDRC_EP ... (MUSB_HDRC_EP + 0xff): + ep = (addr >> 4) & 0xf; + musb_ep_writeb(s, ep, addr & 0xf, value); + break; + + default: + printf("%s: unknown register at %02x\n", __FUNCTION__, (int) addr); + }; +} + +static uint32_t musb_readh(void *opaque, target_phys_addr_t addr) +{ + struct musb_s *s = (struct musb_s *) opaque; + int ep, i; + uint16_t ret; + + switch (addr) { + case MUSB_HDRC_INTRTX: + ret = s->tx_intr; + /* Auto clear */ + for (i = 0; i < sizeof(ret) * 8; i ++) + if (ret & (1 << i)) + musb_tx_intr_set(s, i, 0); + return ret; + case MUSB_HDRC_INTRRX: + ret = s->rx_intr; + /* Auto clear */ + for (i = 0; i < sizeof(ret) * 8; i ++) + if (ret & (1 << i)) + musb_rx_intr_set(s, i, 0); + return ret; + case MUSB_HDRC_INTRTXE: + return s->tx_mask; + case MUSB_HDRC_INTRRXE: + return s->rx_mask; + + case MUSB_HDRC_FRAME: + /* TODO */ + return 0x0000; + case MUSB_HDRC_TXFIFOADDR: + return s->ep[s->idx].fifoaddr[0]; + case MUSB_HDRC_RXFIFOADDR: + return s->ep[s->idx].fifoaddr[1]; + + case MUSB_HDRC_EP_IDX ... (MUSB_HDRC_EP_IDX + 0xf): + return musb_ep_readh(s, s->idx, addr & 0xf); + + case MUSB_HDRC_BUSCTL ... (MUSB_HDRC_BUSCTL + 0x7f): + ep = (addr >> 3) & 0xf; + return musb_busctl_readh(s, ep, addr & 0x7); + + case MUSB_HDRC_EP ... (MUSB_HDRC_EP + 0xff): + ep = (addr >> 4) & 0xf; + return musb_ep_readh(s, ep, addr & 0xf); + + default: + return musb_readb(s, addr) | (musb_readb(s, addr | 1) << 8); + }; +} + +static void musb_writeh(void *opaque, target_phys_addr_t addr, uint32_t value) +{ + struct musb_s *s = (struct musb_s *) opaque; + int ep; + + switch (addr) { + case MUSB_HDRC_INTRTXE: + s->tx_mask = value; + /* XXX: the masks seem to apply on the raising edge like with + * edge-triggered interrupts, thus no need to update. I may be + * wrong though. */ + break; + case MUSB_HDRC_INTRRXE: + s->rx_mask = value; + break; + + case MUSB_HDRC_FRAME: + /* TODO */ + break; + case MUSB_HDRC_TXFIFOADDR: + s->ep[s->idx].fifoaddr[0] = value; + s->ep[s->idx].buf[0] = + s->buf + ((value << 1) & (sizeof(s->buf) / 4 - 1)); + break; + case MUSB_HDRC_RXFIFOADDR: + s->ep[s->idx].fifoaddr[1] = value; + s->ep[s->idx].buf[1] = + s->buf + ((value << 1) & (sizeof(s->buf) / 4 - 1)); + break; + + case MUSB_HDRC_EP_IDX ... (MUSB_HDRC_EP_IDX + 0xf): + musb_ep_writeh(s, s->idx, addr & 0xf, value); + break; + + case MUSB_HDRC_BUSCTL ... (MUSB_HDRC_BUSCTL + 0x7f): + ep = (addr >> 3) & 0xf; + musb_busctl_writeh(s, ep, addr & 0x7, value); + break; + + case MUSB_HDRC_EP ... (MUSB_HDRC_EP + 0xff): + ep = (addr >> 4) & 0xf; + musb_ep_writeh(s, ep, addr & 0xf, value); + break; + + default: + musb_writeb(s, addr, value & 0xff); + musb_writeb(s, addr | 1, value >> 8); + }; +} + +static uint32_t musb_readw(void *opaque, target_phys_addr_t addr) +{ + struct musb_s *s = (struct musb_s *) opaque; + struct musb_ep_s *ep; + int epnum; + + switch (addr) { + case MUSB_HDRC_FIFO ... (MUSB_HDRC_FIFO + 0x3f): + epnum = ((addr - MUSB_HDRC_FIFO) >> 2) & 0xf; + ep = s->ep + epnum; + + if (ep->fifolen[1] >= 16) { + /* We have a FIFO underrun */ + printf("%s: EP%i FIFO is now empty, stop reading\n", + __FUNCTION__, epnum); + return 0x00000000; + } + /* In DMA mode clear RXPKTRDY and set REQPKT automatically + * (if AUTOREQ is set) */ + + ep->csr[1] &= ~MGC_M_RXCSR_FIFOFULL; + return ep->buf[1][ep->fifostart[1] + ep->fifolen[1] ++]; + + default: + printf("%s: unknown register at %02x\n", __FUNCTION__, (int) addr); + return 0x00000000; + }; +} + +static void musb_writew(void *opaque, target_phys_addr_t addr, uint32_t value) +{ + struct musb_s *s = (struct musb_s *) opaque; + struct musb_ep_s *ep; + int epnum; + + switch (addr) { + case MUSB_HDRC_FIFO ... (MUSB_HDRC_FIFO + 0x3f): + epnum = ((addr - MUSB_HDRC_FIFO) >> 2) & 0xf; + ep = s->ep + epnum; + + if (ep->fifolen[0] >= 16) { + /* We have a FIFO overrun */ + printf("%s: EP%i FIFO exceeded 64 bytes, stop feeding data\n", + __FUNCTION__, epnum); + break; + } + + ep->buf[0][ep->fifostart[0] + ep->fifolen[0] ++] = value; + if (epnum) + ep->csr[0] |= MGC_M_TXCSR_FIFONOTEMPTY; + break; + + default: + printf("%s: unknown register at %02x\n", __FUNCTION__, (int) addr); + }; +} + +CPUReadMemoryFunc *musb_read[] = { + musb_readb, + musb_readh, + musb_readw, +}; + +CPUWriteMemoryFunc *musb_write[] = { + musb_writeb, + musb_writeh, + musb_writew, +}; diff --git a/hw/usb.h b/hw/usb.h index db6146e..9b759d4 100644 --- a/hw/usb.h +++ b/hw/usb.h @@ -108,6 +108,11 @@ #define USB_DT_INTERFACE 0x04 #define USB_DT_ENDPOINT 0x05 +#define USB_ENDPOINT_XFER_CONTROL 0 +#define USB_ENDPOINT_XFER_ISOC 1 +#define USB_ENDPOINT_XFER_BULK 2 +#define USB_ENDPOINT_XFER_INT 3 + typedef struct USBPort USBPort; typedef struct USBDevice USBDevice; typedef struct USBPacket USBPacket; @@ -227,3 +232,25 @@ void qemu_register_usb_port(USBPort *port, void *opaque, int index, #define VM_USB_HUB_SIZE 8 +/* usb-musb.c */ +enum musb_irq_source_e { + musb_irq_suspend = 0, + musb_irq_resume, + musb_irq_rst_babble, + musb_irq_sof, + musb_irq_connect, + musb_irq_disconnect, + musb_irq_vbus_request, + musb_irq_vbus_error, + musb_irq_rx, + musb_irq_tx, + musb_set_vbus, + musb_set_session, + __musb_irq_max, +}; + +struct musb_s; +struct musb_s *musb_init(qemu_irq *irqs); +uint32_t musb_core_intr_get(struct musb_s *s); +void musb_core_intr_clear(struct musb_s *s, uint32_t mask); +void musb_set_size(struct musb_s *s, int epnum, int size, int is_tx); -- 2.7.4