diff options
author | balrog <balrog@c046a42c-6fe2-441c-8c8c-71466251a162> | 2008-04-22 03:15:10 +0000 |
---|---|---|
committer | balrog <balrog@c046a42c-6fe2-441c-8c8c-71466251a162> | 2008-04-22 03:15:10 +0000 |
commit | 942ac05261a4e913ce347501b811555cb48cda60 (patch) | |
tree | 4ff0346c1ad157b12301b7a577232ff41418fa58 /hw/usb-musb.c | |
parent | a050e24df2d534d960ed4ead4865f741e40d472b (diff) |
Inventra MUSB-HDRC host-mode USB.
git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@4232 c046a42c-6fe2-441c-8c8c-71466251a162
Diffstat (limited to 'hw/usb-musb.c')
-rw-r--r-- | hw/usb-musb.c | 1449 |
1 files changed, 1449 insertions, 0 deletions
diff --git a/hw/usb-musb.c b/hw/usb-musb.c new file mode 100644 index 0000000000..1499b310b7 --- /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 <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, 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, +}; |