/* * "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 <andrew@openedhand.com> * * 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, see <http://www.gnu.org/licenses/>. * * Only host-mode and non-DMA accesses are currently supported. */ #include "qemu/osdep.h" #include "qemu/timer.h" #include "hw/usb.h" #include "hw/irq.h" #include "hw/hw.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 /* #define MUSB_DEBUG */ #ifdef MUSB_DEBUG #define TRACE(fmt, ...) fprintf(stderr, "%s@%d: " fmt "\n", __func__, \ __LINE__, ##__VA_ARGS__) #else #define TRACE(...) #endif static void musb_attach(USBPort *port); static void musb_detach(USBPort *port); static void musb_child_detach(USBPort *port, USBDevice *child); static void musb_schedule_cb(USBPort *port, USBPacket *p); static void musb_async_cancel_device(MUSBState *s, USBDevice *dev); static USBPortOps musb_port_ops = { .attach = musb_attach, .detach = musb_detach, .child_detach = musb_child_detach, .complete = musb_schedule_cb, }; static USBBusOps musb_bus_ops = { }; typedef struct MUSBPacket MUSBPacket; typedef struct MUSBEndPoint MUSBEndPoint; struct MUSBPacket { USBPacket p; MUSBEndPoint *ep; int dir; }; struct MUSBEndPoint { 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 */ uint8_t *buf[2]; int fifolen[2]; int fifostart[2]; int fifoaddr[2]; MUSBPacket packey[2]; int status[2]; int ext_size[2]; /* For callbacks' use */ int epnum; int interrupt[2]; MUSBState *musb; USBCallback *delayed_cb[2]; QEMUTimer *intv_timer[2]; }; struct MUSBState { qemu_irq irqs[musb_irq_max]; USBBus bus; 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; uint8_t buf[0x8000]; /* Duplicating the world since 2008!... probably we should have 32 * logical, single endpoints instead. */ MUSBEndPoint ep[16]; }; void musb_reset(MUSBState *s) { int i; s->faddr = 0x00; s->devctl = 0; 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; s->setup_len = 0; s->session = 0; memset(s->buf, 0, sizeof(s->buf)); /* 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; usb_packet_init(&s->ep[i].packey[0].p); usb_packet_init(&s->ep[i].packey[1].p); } } struct MUSBState *musb_init(DeviceState *parent_device, int gpio_base) { MUSBState *s = g_malloc0(sizeof(*s)); int i; for (i = 0; i < musb_irq_max; i++) { s->irqs[i] = qdev_get_gpio_in(parent_device, gpio_base + i); } musb_reset(s); usb_bus_new(&s->bus, sizeof(s->bus), &musb_bus_ops, parent_device); usb_register_port(&s->bus, &s->port, s, 0, &musb_port_ops, USB_SPEED_MASK_LOW | USB_SPEED_MASK_FULL); return s; } static void musb_vbus_set(MUSBState *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(MUSBState *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(MUSBState *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(MUSBState *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(MUSBState *s) { return (s->rx_intr << 15) | s->tx_intr; } void musb_core_intr_clear(MUSBState *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(MUSBState *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(MUSBState *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) { MUSBState *s = (MUSBState *) port->opaque; musb_intr_set(s, musb_irq_vbus_request, 1); musb_session_update(s, 0, s->session); } static void musb_detach(USBPort *port) { MUSBState *s = (MUSBState *) port->opaque; musb_async_cancel_device(s, port->dev); musb_intr_set(s, musb_irq_disconnect, 1); musb_session_update(s, 1, s->session); } static void musb_child_detach(USBPort *port, USBDevice *child) { MUSBState *s = (MUSBState *) port->opaque; musb_async_cancel_device(s, child); } static void musb_cb_tick0(void *opaque) { MUSBEndPoint *ep = (MUSBEndPoint *) opaque; ep->delayed_cb[0](&ep->packey[0].p, opaque); } static void musb_cb_tick1(void *opaque) { MUSBEndPoint *ep = (MUSBEndPoint *) opaque; ep->delayed_cb[1](&ep->packey[1].p, opaque); } #define musb_cb_tick (dir ? musb_cb_tick1 : musb_cb_tick0) static void musb_schedule_cb(USBPort *port, USBPacket *packey) { MUSBPacket *p = container_of(packey, MUSBPacket, p); MUSBEndPoint *ep = p->ep; int dir = p->dir; int timeout = 0; if (ep->status[dir] == USB_RET_NAK) timeout = ep->timeout[dir]; else if (ep->interrupt[dir]) timeout = 8; else { musb_cb_tick(ep); return; } if (!ep->intv_timer[dir]) ep->intv_timer[dir] = timer_new_ns(QEMU_CLOCK_VIRTUAL, musb_cb_tick, ep); timer_mod(ep->intv_timer[dir], qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + muldiv64(timeout, NANOSECONDS_PER_SECOND, 8000)); } 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? */ } hw_error("bad interval\n"); } static void musb_packet(MUSBState *s, MUSBEndPoint *ep, int epnum, int pid, int len, USBCallback cb, int dir) { USBDevice *dev; USBEndpoint *uep; int idx = epnum && dir; int id; 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; /* A wild guess on the FADDR semantics... */ dev = usb_find_device(&s->port, ep->faddr[idx]); if (dev == NULL) { return; } uep = usb_ep_get(dev, pid, ep->type[idx] & 0xf); id = pid | (dev->addr << 16) | (uep->nr << 8); usb_packet_setup(&ep->packey[dir].p, pid, uep, 0, id, false, true); usb_packet_addbuf(&ep->packey[dir].p, ep->buf[idx], len); ep->packey[dir].ep = ep; ep->packey[dir].dir = dir; usb_handle_packet(dev, &ep->packey[dir].p); if (ep->packey[dir].p.status == USB_RET_ASYNC) { usb_device_flush_ep_queue(dev, uep); ep->status[dir] = len; return; } if (ep->packey[dir].p.status == USB_RET_SUCCESS) { ep->status[dir] = ep->packey[dir].p.actual_length; } else { ep->status[dir] = ep->packey[dir].p.status; } musb_schedule_cb(&s->port, &ep->packey[dir].p); } 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. */ MUSBEndPoint *ep = (MUSBEndPoint *) opaque; int epnum = ep->epnum; MUSBState *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. */ MUSBEndPoint *ep = (MUSBEndPoint *) opaque; int epnum = ep->epnum; MUSBState *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; 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]) { musb_packet(s, ep, epnum, USB_TOKEN_IN, packey->iov.size, musb_rx_packet_complete, 1); return; } 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. */ 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 = ep->status[1]; /* 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_async_cancel_device(MUSBState *s, USBDevice *dev) { int ep, dir; for (ep = 0; ep < 16; ep++) { for (dir = 0; dir < 2; dir++) { if (!usb_packet_is_inflight(&s->ep[ep].packey[dir].p) || s->ep[ep].packey[dir].p.ep->dev != dev) { continue; } usb_cancel_packet(&s->ep[ep].packey[dir].p); /* status updates needed here? */ } } } static void musb_tx_rdy(MUSBState *s, int epnum) { MUSBEndPoint *ep = s->ep + epnum; int pid; int total, valid = 0; TRACE("start %d, len %d", ep->fifostart[0], ep->fifolen[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]) < total) return; if (!valid) total = ep->fifostart[0]; pid = USB_TOKEN_OUT; if (!epnum && (ep->csr[0] & MGC_M_CSR0_H_SETUPPKT)) { pid = USB_TOKEN_SETUP; if (total != 8) { TRACE("illegal SETUPPKT length of %i bytes", total); } /* Controller should retry SETUP packets three times on errors * but it doesn't make sense for us to do that. */ } musb_packet(s, ep, epnum, pid, total, musb_tx_packet_complete, 0); } static void musb_rx_req(MUSBState *s, int epnum) { MUSBEndPoint *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].p.pid == USB_TOKEN_IN && ep->status[1] >= 0 && (ep->fifostart[1]) + ep->rxcount < ep->packey[1].p.iov.size) { TRACE("0x%08x, %d", ep->fifostart[1], ep->rxcount ); ep->fifostart[1] += ep->rxcount; ep->fifolen[1] = 0; ep->rxcount = MIN(ep->packey[0].p.iov.size - (ep->fifostart[1]), 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].p.devaddr == 2) { total = MIN(s->setup_len, 8); } else { total = MIN(s->setup_len, 64); } s->setup_len -= total; } #endif musb_packet(s, ep, epnum, USB_TOKEN_IN, total, musb_rx_packet_complete, 1); } static uint8_t musb_read_fifo(MUSBEndPoint *ep) { uint8_t value; if (ep->fifolen[1] >= 64) { /* We have a FIFO underrun */ TRACE("EP%d FIFO is now empty, stop reading", ep->epnum); return 0x00000000; } /* In DMA mode clear RXPKTRDY and set REQPKT automatically * (if AUTOREQ is set) */ ep->csr[1] &= ~MGC_M_RXCSR_FIFOFULL; value=ep->buf[1][ep->fifostart[1] + ep->fifolen[1] ++]; TRACE("EP%d 0x%02x, %d", ep->epnum, value, ep->fifolen[1] ); return value; } static void musb_write_fifo(MUSBEndPoint *ep, uint8_t value) { TRACE("EP%d = %02x", ep->epnum, value); if (ep->fifolen[0] >= 64) { /* We have a FIFO overrun */ TRACE("EP%d FIFO exceeded 64 bytes, stop feeding data", ep->epnum); return; } ep->buf[0][ep->fifostart[0] + ep->fifolen[0] ++] = value; ep->csr[0] |= MGC_M_TXCSR_FIFONOTEMPTY; } static void musb_ep_frame_cancel(MUSBEndPoint *ep, int dir) { if (ep->intv_timer[dir]) timer_del(ep->intv_timer[dir]); } /* Bus control */ static uint8_t musb_busctl_readb(void *opaque, int ep, int addr) { MUSBState *s = (MUSBState *) 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: TRACE("unknown register 0x%02x", addr); return 0x00; }; } static void musb_busctl_writeb(void *opaque, int ep, int addr, uint8_t value) { MUSBState *s = (MUSBState *) 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; 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: TRACE("unknown register 0x%02x", addr); break; }; } static uint16_t musb_busctl_readh(void *opaque, int ep, int addr) { MUSBState *s = (MUSBState *) 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) { MUSBState *s = (MUSBState *) 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) { MUSBState *s = (MUSBState *) 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; case MUSB_HDRC_RXCOUNT: return s->ep[ep].rxcount; default: TRACE("unknown register 0x%02x", addr); return 0x00; }; } static void musb_ep_writeb(void *opaque, int ep, int addr, uint8_t value) { MUSBState *s = (MUSBState *) 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: TRACE("somebody messes with fifosize (now %i bytes)", value); s->ep[ep].fifosize = value; break; default: TRACE("unknown register 0x%02x", addr); break; }; } static uint16_t musb_ep_readh(void *opaque, int ep, int addr) { MUSBState *s = (MUSBState *) 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) { MUSBState *s = (MUSBState *) 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, hwaddr addr) { MUSBState *s = (MUSBState *) 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); case MUSB_HDRC_FIFO ... (MUSB_HDRC_FIFO + 0x3f): ep = ((addr - MUSB_HDRC_FIFO) >> 2) & 0xf; return musb_read_fifo(s->ep + ep); default: TRACE("unknown register 0x%02x", (int) addr); return 0x00; }; } static void musb_writeb(void *opaque, hwaddr addr, uint32_t value) { MUSBState *s = (MUSBState *) 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_device_reset(s->port.dev); /* 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; case MUSB_HDRC_FIFO ... (MUSB_HDRC_FIFO + 0x3f): ep = ((addr - MUSB_HDRC_FIFO) >> 2) & 0xf; musb_write_fifo(s->ep + ep, value & 0xff); break; default: TRACE("unknown register 0x%02x", (int) addr); break; }; } static uint32_t musb_readh(void *opaque, hwaddr addr) { MUSBState *s = (MUSBState *) 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); case MUSB_HDRC_FIFO ... (MUSB_HDRC_FIFO + 0x3f): ep = ((addr - MUSB_HDRC_FIFO) >> 2) & 0xf; return (musb_read_fifo(s->ep + ep) | musb_read_fifo(s->ep + ep) << 8); default: return musb_readb(s, addr) | (musb_readb(s, addr | 1) << 8); }; } static void musb_writeh(void *opaque, hwaddr addr, uint32_t value) { MUSBState *s = (MUSBState *) 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 << 3) & 0x7ff ); break; case MUSB_HDRC_RXFIFOADDR: s->ep[s->idx].fifoaddr[1] = value; s->ep[s->idx].buf[1] = s->buf + ((value << 3) & 0x7ff); 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; case MUSB_HDRC_FIFO ... (MUSB_HDRC_FIFO + 0x3f): ep = ((addr - MUSB_HDRC_FIFO) >> 2) & 0xf; musb_write_fifo(s->ep + ep, value & 0xff); musb_write_fifo(s->ep + ep, (value >> 8) & 0xff); break; default: musb_writeb(s, addr, value & 0xff); musb_writeb(s, addr | 1, value >> 8); }; } static uint32_t musb_readw(void *opaque, hwaddr addr) { MUSBState *s = (MUSBState *) opaque; int ep; switch (addr) { case MUSB_HDRC_FIFO ... (MUSB_HDRC_FIFO + 0x3f): ep = ((addr - MUSB_HDRC_FIFO) >> 2) & 0xf; return ( musb_read_fifo(s->ep + ep) | musb_read_fifo(s->ep + ep) << 8 | musb_read_fifo(s->ep + ep) << 16 | musb_read_fifo(s->ep + ep) << 24 ); default: TRACE("unknown register 0x%02x", (int) addr); return 0x00000000; }; } static void musb_writew(void *opaque, hwaddr addr, uint32_t value) { MUSBState *s = (MUSBState *) opaque; int ep; switch (addr) { case MUSB_HDRC_FIFO ... (MUSB_HDRC_FIFO + 0x3f): ep = ((addr - MUSB_HDRC_FIFO) >> 2) & 0xf; musb_write_fifo(s->ep + ep, value & 0xff); musb_write_fifo(s->ep + ep, (value >> 8 ) & 0xff); musb_write_fifo(s->ep + ep, (value >> 16) & 0xff); musb_write_fifo(s->ep + ep, (value >> 24) & 0xff); break; default: TRACE("unknown register 0x%02x", (int) addr); break; }; } CPUReadMemoryFunc * const musb_read[] = { musb_readb, musb_readh, musb_readw, }; CPUWriteMemoryFunc * const musb_write[] = { musb_writeb, musb_writeh, musb_writew, };