mirror of
https://github.com/qemu/qemu.git
synced 2024-11-25 11:53:39 +08:00
384dce1ede
Convert musb fifo to 8bit to allow 8/16/32bit access MUSB allows reading and writing to the fifo in 32/16/8 bit width. The Linux kernel does this sometimes, most usually at the end of writing the packet to allow packet to end at a odd bytecount. Convert the fifo to 8bit allows removing lots of shifts which shows that the fifo is more natural as 8bit. While at it, add multiple missing register definitions and and cleanup debug prints. Signed-off-by: Riku Voipio <riku.voipio@nokia.com> Signed-off-by: Aurelien Jarno <aurelien@aurel32.net>
1502 lines
43 KiB
C
1502 lines
43 KiB
C
/*
|
|
* "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-common.h"
|
|
#include "qemu-timer.h"
|
|
#include "usb.h"
|
|
#include "irq.h"
|
|
#include "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", __FUNCTION__, \
|
|
__LINE__, ##__VA_ARGS__)
|
|
#else
|
|
#define TRACE(...)
|
|
#endif
|
|
|
|
|
|
static void musb_attach(USBPort *port, USBDevice *dev);
|
|
|
|
typedef struct {
|
|
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];
|
|
USBPacket 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];
|
|
} MUSBEndPoint;
|
|
|
|
struct MUSBState {
|
|
qemu_irq *irqs;
|
|
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];
|
|
} *musb_init(qemu_irq *irqs)
|
|
{
|
|
MUSBState *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;
|
|
}
|
|
|
|
usb_bus_new(&s->bus, NULL /* FIXME */);
|
|
usb_register_port(&s->bus, &s->port, s, 0, musb_attach);
|
|
|
|
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, USBDevice *dev)
|
|
{
|
|
MUSBState *s = (MUSBState *) 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)
|
|
{
|
|
MUSBEndPoint *ep = (MUSBEndPoint *) opaque;
|
|
|
|
ep->delayed_cb[0](&ep->packey[0], opaque);
|
|
}
|
|
|
|
static inline void musb_cb_tick1(void *opaque)
|
|
{
|
|
MUSBEndPoint *ep = (MUSBEndPoint *) 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)
|
|
{
|
|
MUSBEndPoint *ep = (MUSBEndPoint *) 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, get_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? */
|
|
}
|
|
|
|
hw_error("bad interval\n");
|
|
}
|
|
|
|
static inline void musb_packet(MUSBState *s, MUSBEndPoint *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->info->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. */
|
|
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;
|
|
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(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. */
|
|
}
|
|
|
|
return 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].pid == USB_TOKEN_IN && ep->status[1] >= 0 &&
|
|
(ep->fifostart[1]) + ep->rxcount <
|
|
ep->packey[1].len) {
|
|
TRACE("0x%08x, %d", ep->fifostart[1], ep->rxcount );
|
|
ep->fifostart[1] += ep->rxcount;
|
|
ep->fifolen[1] = 0;
|
|
|
|
ep->rxcount = MIN(ep->packey[0].len - (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].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 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])
|
|
qemu_del_timer(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, target_phys_addr_t 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, target_phys_addr_t 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_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;
|
|
|
|
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, target_phys_addr_t 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, target_phys_addr_t 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, target_phys_addr_t 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, target_phys_addr_t 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,
|
|
};
|