mirror of
https://github.com/AsahiLinux/u-boot
synced 2024-11-19 11:18:28 +00:00
83d290c56f
When U-Boot started using SPDX tags we were among the early adopters and there weren't a lot of other examples to borrow from. So we picked the area of the file that usually had a full license text and replaced it with an appropriate SPDX-License-Identifier: entry. Since then, the Linux Kernel has adopted SPDX tags and they place it as the very first line in a file (except where shebangs are used, then it's second line) and with slightly different comment styles than us. In part due to community overlap, in part due to better tag visibility and in part for other minor reasons, switch over to that style. This commit changes all instances where we have a single declared license in the tag as both the before and after are identical in tag contents. There's also a few places where I found we did not have a tag and have introduced one. Signed-off-by: Tom Rini <trini@konsulko.com>
1166 lines
27 KiB
C
1166 lines
27 KiB
C
// SPDX-License-Identifier: GPL-2.0+
|
|
/*
|
|
* Mentor USB OTG Core host controller driver.
|
|
*
|
|
* Copyright (c) 2008 Texas Instruments
|
|
*
|
|
* Author: Thomas Abraham t-abraham@ti.com, Texas Instruments
|
|
*/
|
|
|
|
#include <common.h>
|
|
#include <usb.h>
|
|
#include "musb_hcd.h"
|
|
|
|
/* MSC control transfers */
|
|
#define USB_MSC_BBB_RESET 0xFF
|
|
#define USB_MSC_BBB_GET_MAX_LUN 0xFE
|
|
|
|
/* Endpoint configuration information */
|
|
static const struct musb_epinfo epinfo[3] = {
|
|
{MUSB_BULK_EP, 1, 512}, /* EP1 - Bluk Out - 512 Bytes */
|
|
{MUSB_BULK_EP, 0, 512}, /* EP1 - Bluk In - 512 Bytes */
|
|
{MUSB_INTR_EP, 0, 64} /* EP2 - Interrupt IN - 64 Bytes */
|
|
};
|
|
|
|
/* --- Virtual Root Hub ---------------------------------------------------- */
|
|
#ifdef MUSB_NO_MULTIPOINT
|
|
static int rh_devnum;
|
|
static u32 port_status;
|
|
|
|
#include <usbroothubdes.h>
|
|
|
|
#endif
|
|
|
|
/*
|
|
* This function writes the data toggle value.
|
|
*/
|
|
static void write_toggle(struct usb_device *dev, u8 ep, u8 dir_out)
|
|
{
|
|
u16 toggle = usb_gettoggle(dev, ep, dir_out);
|
|
u16 csr;
|
|
|
|
if (dir_out) {
|
|
csr = readw(&musbr->txcsr);
|
|
if (!toggle) {
|
|
if (csr & MUSB_TXCSR_MODE)
|
|
csr = MUSB_TXCSR_CLRDATATOG;
|
|
else
|
|
csr = 0;
|
|
writew(csr, &musbr->txcsr);
|
|
} else {
|
|
csr |= MUSB_TXCSR_H_WR_DATATOGGLE;
|
|
writew(csr, &musbr->txcsr);
|
|
csr |= (toggle << MUSB_TXCSR_H_DATATOGGLE_SHIFT);
|
|
writew(csr, &musbr->txcsr);
|
|
}
|
|
} else {
|
|
if (!toggle) {
|
|
csr = readw(&musbr->txcsr);
|
|
if (csr & MUSB_TXCSR_MODE)
|
|
csr = MUSB_RXCSR_CLRDATATOG;
|
|
else
|
|
csr = 0;
|
|
writew(csr, &musbr->rxcsr);
|
|
} else {
|
|
csr = readw(&musbr->rxcsr);
|
|
csr |= MUSB_RXCSR_H_WR_DATATOGGLE;
|
|
writew(csr, &musbr->rxcsr);
|
|
csr |= (toggle << MUSB_S_RXCSR_H_DATATOGGLE);
|
|
writew(csr, &musbr->rxcsr);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This function checks if RxStall has occurred on the endpoint. If a RxStall
|
|
* has occurred, the RxStall is cleared and 1 is returned. If RxStall has
|
|
* not occurred, 0 is returned.
|
|
*/
|
|
static u8 check_stall(u8 ep, u8 dir_out)
|
|
{
|
|
u16 csr;
|
|
|
|
/* For endpoint 0 */
|
|
if (!ep) {
|
|
csr = readw(&musbr->txcsr);
|
|
if (csr & MUSB_CSR0_H_RXSTALL) {
|
|
csr &= ~MUSB_CSR0_H_RXSTALL;
|
|
writew(csr, &musbr->txcsr);
|
|
return 1;
|
|
}
|
|
} else { /* For non-ep0 */
|
|
if (dir_out) { /* is it tx ep */
|
|
csr = readw(&musbr->txcsr);
|
|
if (csr & MUSB_TXCSR_H_RXSTALL) {
|
|
csr &= ~MUSB_TXCSR_H_RXSTALL;
|
|
writew(csr, &musbr->txcsr);
|
|
return 1;
|
|
}
|
|
} else { /* is it rx ep */
|
|
csr = readw(&musbr->rxcsr);
|
|
if (csr & MUSB_RXCSR_H_RXSTALL) {
|
|
csr &= ~MUSB_RXCSR_H_RXSTALL;
|
|
writew(csr, &musbr->rxcsr);
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* waits until ep0 is ready. Returns 0 if ep is ready, -1 for timeout
|
|
* error and -2 for stall.
|
|
*/
|
|
static int wait_until_ep0_ready(struct usb_device *dev, u32 bit_mask)
|
|
{
|
|
u16 csr;
|
|
int result = 1;
|
|
int timeout = CONFIG_USB_MUSB_TIMEOUT;
|
|
|
|
while (result > 0) {
|
|
csr = readw(&musbr->txcsr);
|
|
if (csr & MUSB_CSR0_H_ERROR) {
|
|
csr &= ~MUSB_CSR0_H_ERROR;
|
|
writew(csr, &musbr->txcsr);
|
|
dev->status = USB_ST_CRC_ERR;
|
|
result = -1;
|
|
break;
|
|
}
|
|
|
|
switch (bit_mask) {
|
|
case MUSB_CSR0_TXPKTRDY:
|
|
if (!(csr & MUSB_CSR0_TXPKTRDY)) {
|
|
if (check_stall(MUSB_CONTROL_EP, 0)) {
|
|
dev->status = USB_ST_STALLED;
|
|
result = -2;
|
|
} else
|
|
result = 0;
|
|
}
|
|
break;
|
|
|
|
case MUSB_CSR0_RXPKTRDY:
|
|
if (check_stall(MUSB_CONTROL_EP, 0)) {
|
|
dev->status = USB_ST_STALLED;
|
|
result = -2;
|
|
} else
|
|
if (csr & MUSB_CSR0_RXPKTRDY)
|
|
result = 0;
|
|
break;
|
|
|
|
case MUSB_CSR0_H_REQPKT:
|
|
if (!(csr & MUSB_CSR0_H_REQPKT)) {
|
|
if (check_stall(MUSB_CONTROL_EP, 0)) {
|
|
dev->status = USB_ST_STALLED;
|
|
result = -2;
|
|
} else
|
|
result = 0;
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* Check the timeout */
|
|
if (--timeout)
|
|
udelay(1);
|
|
else {
|
|
dev->status = USB_ST_CRC_ERR;
|
|
result = -1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* waits until tx ep is ready. Returns 1 when ep is ready and 0 on error.
|
|
*/
|
|
static int wait_until_txep_ready(struct usb_device *dev, u8 ep)
|
|
{
|
|
u16 csr;
|
|
int timeout = CONFIG_USB_MUSB_TIMEOUT;
|
|
|
|
do {
|
|
if (check_stall(ep, 1)) {
|
|
dev->status = USB_ST_STALLED;
|
|
return 0;
|
|
}
|
|
|
|
csr = readw(&musbr->txcsr);
|
|
if (csr & MUSB_TXCSR_H_ERROR) {
|
|
dev->status = USB_ST_CRC_ERR;
|
|
return 0;
|
|
}
|
|
|
|
/* Check the timeout */
|
|
if (--timeout)
|
|
udelay(1);
|
|
else {
|
|
dev->status = USB_ST_CRC_ERR;
|
|
return -1;
|
|
}
|
|
|
|
} while (csr & MUSB_TXCSR_TXPKTRDY);
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* waits until rx ep is ready. Returns 1 when ep is ready and 0 on error.
|
|
*/
|
|
static int wait_until_rxep_ready(struct usb_device *dev, u8 ep)
|
|
{
|
|
u16 csr;
|
|
int timeout = CONFIG_USB_MUSB_TIMEOUT;
|
|
|
|
do {
|
|
if (check_stall(ep, 0)) {
|
|
dev->status = USB_ST_STALLED;
|
|
return 0;
|
|
}
|
|
|
|
csr = readw(&musbr->rxcsr);
|
|
if (csr & MUSB_RXCSR_H_ERROR) {
|
|
dev->status = USB_ST_CRC_ERR;
|
|
return 0;
|
|
}
|
|
|
|
/* Check the timeout */
|
|
if (--timeout)
|
|
udelay(1);
|
|
else {
|
|
dev->status = USB_ST_CRC_ERR;
|
|
return -1;
|
|
}
|
|
|
|
} while (!(csr & MUSB_RXCSR_RXPKTRDY));
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* This function performs the setup phase of the control transfer
|
|
*/
|
|
static int ctrlreq_setup_phase(struct usb_device *dev, struct devrequest *setup)
|
|
{
|
|
int result;
|
|
u16 csr;
|
|
|
|
/* write the control request to ep0 fifo */
|
|
write_fifo(MUSB_CONTROL_EP, sizeof(struct devrequest), (void *)setup);
|
|
|
|
/* enable transfer of setup packet */
|
|
csr = readw(&musbr->txcsr);
|
|
csr |= (MUSB_CSR0_TXPKTRDY|MUSB_CSR0_H_SETUPPKT);
|
|
writew(csr, &musbr->txcsr);
|
|
|
|
/* wait until the setup packet is transmitted */
|
|
result = wait_until_ep0_ready(dev, MUSB_CSR0_TXPKTRDY);
|
|
dev->act_len = 0;
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* This function handles the control transfer in data phase
|
|
*/
|
|
static int ctrlreq_in_data_phase(struct usb_device *dev, u32 len, void *buffer)
|
|
{
|
|
u16 csr;
|
|
u32 rxlen = 0;
|
|
u32 nextlen = 0;
|
|
u8 maxpktsize = (1 << dev->maxpacketsize) * 8;
|
|
u8 *rxbuff = (u8 *)buffer;
|
|
u8 rxedlength;
|
|
int result;
|
|
|
|
while (rxlen < len) {
|
|
/* Determine the next read length */
|
|
nextlen = ((len-rxlen) > maxpktsize) ? maxpktsize : (len-rxlen);
|
|
|
|
/* Set the ReqPkt bit */
|
|
csr = readw(&musbr->txcsr);
|
|
writew(csr | MUSB_CSR0_H_REQPKT, &musbr->txcsr);
|
|
result = wait_until_ep0_ready(dev, MUSB_CSR0_RXPKTRDY);
|
|
if (result < 0)
|
|
return result;
|
|
|
|
/* Actual number of bytes received by usb */
|
|
rxedlength = readb(&musbr->rxcount);
|
|
|
|
/* Read the data from the RxFIFO */
|
|
read_fifo(MUSB_CONTROL_EP, rxedlength, &rxbuff[rxlen]);
|
|
|
|
/* Clear the RxPktRdy Bit */
|
|
csr = readw(&musbr->txcsr);
|
|
csr &= ~MUSB_CSR0_RXPKTRDY;
|
|
writew(csr, &musbr->txcsr);
|
|
|
|
/* short packet? */
|
|
if (rxedlength != nextlen) {
|
|
dev->act_len += rxedlength;
|
|
break;
|
|
}
|
|
rxlen += nextlen;
|
|
dev->act_len = rxlen;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* This function handles the control transfer out data phase
|
|
*/
|
|
static int ctrlreq_out_data_phase(struct usb_device *dev, u32 len, void *buffer)
|
|
{
|
|
u16 csr;
|
|
u32 txlen = 0;
|
|
u32 nextlen = 0;
|
|
u8 maxpktsize = (1 << dev->maxpacketsize) * 8;
|
|
u8 *txbuff = (u8 *)buffer;
|
|
int result = 0;
|
|
|
|
while (txlen < len) {
|
|
/* Determine the next write length */
|
|
nextlen = ((len-txlen) > maxpktsize) ? maxpktsize : (len-txlen);
|
|
|
|
/* Load the data to send in FIFO */
|
|
write_fifo(MUSB_CONTROL_EP, txlen, &txbuff[txlen]);
|
|
|
|
/* Set TXPKTRDY bit */
|
|
csr = readw(&musbr->txcsr);
|
|
|
|
csr |= MUSB_CSR0_TXPKTRDY;
|
|
#if !defined(CONFIG_SOC_DM365)
|
|
csr |= MUSB_CSR0_H_DIS_PING;
|
|
#endif
|
|
writew(csr, &musbr->txcsr);
|
|
result = wait_until_ep0_ready(dev, MUSB_CSR0_TXPKTRDY);
|
|
if (result < 0)
|
|
break;
|
|
|
|
txlen += nextlen;
|
|
dev->act_len = txlen;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* This function handles the control transfer out status phase
|
|
*/
|
|
static int ctrlreq_out_status_phase(struct usb_device *dev)
|
|
{
|
|
u16 csr;
|
|
int result;
|
|
|
|
/* Set the StatusPkt bit */
|
|
csr = readw(&musbr->txcsr);
|
|
csr |= (MUSB_CSR0_TXPKTRDY | MUSB_CSR0_H_STATUSPKT);
|
|
#if !defined(CONFIG_SOC_DM365)
|
|
csr |= MUSB_CSR0_H_DIS_PING;
|
|
#endif
|
|
writew(csr, &musbr->txcsr);
|
|
|
|
/* Wait until TXPKTRDY bit is cleared */
|
|
result = wait_until_ep0_ready(dev, MUSB_CSR0_TXPKTRDY);
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* This function handles the control transfer in status phase
|
|
*/
|
|
static int ctrlreq_in_status_phase(struct usb_device *dev)
|
|
{
|
|
u16 csr;
|
|
int result;
|
|
|
|
/* Set the StatusPkt bit and ReqPkt bit */
|
|
csr = MUSB_CSR0_H_REQPKT | MUSB_CSR0_H_STATUSPKT;
|
|
#if !defined(CONFIG_SOC_DM365)
|
|
csr |= MUSB_CSR0_H_DIS_PING;
|
|
#endif
|
|
writew(csr, &musbr->txcsr);
|
|
result = wait_until_ep0_ready(dev, MUSB_CSR0_H_REQPKT);
|
|
|
|
/* clear StatusPkt bit and RxPktRdy bit */
|
|
csr = readw(&musbr->txcsr);
|
|
csr &= ~(MUSB_CSR0_RXPKTRDY | MUSB_CSR0_H_STATUSPKT);
|
|
writew(csr, &musbr->txcsr);
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* determines the speed of the device (High/Full/Slow)
|
|
*/
|
|
static u8 get_dev_speed(struct usb_device *dev)
|
|
{
|
|
return (dev->speed == USB_SPEED_HIGH) ? MUSB_TYPE_SPEED_HIGH :
|
|
((dev->speed == USB_SPEED_LOW) ? MUSB_TYPE_SPEED_LOW :
|
|
MUSB_TYPE_SPEED_FULL);
|
|
}
|
|
|
|
/*
|
|
* configure the hub address and the port address.
|
|
*/
|
|
static void config_hub_port(struct usb_device *dev, u8 ep)
|
|
{
|
|
u8 chid;
|
|
u8 hub;
|
|
|
|
/* Find out the nearest parent which is high speed */
|
|
while (dev->parent->parent != NULL)
|
|
if (get_dev_speed(dev->parent) != MUSB_TYPE_SPEED_HIGH)
|
|
dev = dev->parent;
|
|
else
|
|
break;
|
|
|
|
/* determine the port address at that hub */
|
|
hub = dev->parent->devnum;
|
|
for (chid = 0; chid < USB_MAXCHILDREN; chid++)
|
|
if (dev->parent->children[chid] == dev)
|
|
break;
|
|
|
|
#ifndef MUSB_NO_MULTIPOINT
|
|
/* configure the hub address and the port address */
|
|
writeb(hub, &musbr->tar[ep].txhubaddr);
|
|
writeb((chid + 1), &musbr->tar[ep].txhubport);
|
|
writeb(hub, &musbr->tar[ep].rxhubaddr);
|
|
writeb((chid + 1), &musbr->tar[ep].rxhubport);
|
|
#endif
|
|
}
|
|
|
|
#ifdef MUSB_NO_MULTIPOINT
|
|
|
|
static void musb_port_reset(int do_reset)
|
|
{
|
|
u8 power = readb(&musbr->power);
|
|
|
|
if (do_reset) {
|
|
power &= 0xf0;
|
|
writeb(power | MUSB_POWER_RESET, &musbr->power);
|
|
port_status |= USB_PORT_STAT_RESET;
|
|
port_status &= ~USB_PORT_STAT_ENABLE;
|
|
udelay(30000);
|
|
} else {
|
|
writeb(power & ~MUSB_POWER_RESET, &musbr->power);
|
|
|
|
power = readb(&musbr->power);
|
|
if (power & MUSB_POWER_HSMODE)
|
|
port_status |= USB_PORT_STAT_HIGH_SPEED;
|
|
|
|
port_status &= ~(USB_PORT_STAT_RESET | (USB_PORT_STAT_C_CONNECTION << 16));
|
|
port_status |= USB_PORT_STAT_ENABLE
|
|
| (USB_PORT_STAT_C_RESET << 16)
|
|
| (USB_PORT_STAT_C_ENABLE << 16);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* root hub control
|
|
*/
|
|
static int musb_submit_rh_msg(struct usb_device *dev, unsigned long pipe,
|
|
void *buffer, int transfer_len,
|
|
struct devrequest *cmd)
|
|
{
|
|
int leni = transfer_len;
|
|
int len = 0;
|
|
int stat = 0;
|
|
u32 datab[4];
|
|
const u8 *data_buf = (u8 *) datab;
|
|
u16 bmRType_bReq;
|
|
u16 wValue;
|
|
u16 wIndex;
|
|
u16 wLength;
|
|
u16 int_usb;
|
|
|
|
if ((pipe & PIPE_INTERRUPT) == PIPE_INTERRUPT) {
|
|
debug("Root-Hub submit IRQ: NOT implemented\n");
|
|
return 0;
|
|
}
|
|
|
|
bmRType_bReq = cmd->requesttype | (cmd->request << 8);
|
|
wValue = swap_16(cmd->value);
|
|
wIndex = swap_16(cmd->index);
|
|
wLength = swap_16(cmd->length);
|
|
|
|
debug("--- HUB ----------------------------------------\n");
|
|
debug("submit rh urb, req=%x val=%#x index=%#x len=%d\n",
|
|
bmRType_bReq, wValue, wIndex, wLength);
|
|
debug("------------------------------------------------\n");
|
|
|
|
switch (bmRType_bReq) {
|
|
case RH_GET_STATUS:
|
|
debug("RH_GET_STATUS\n");
|
|
|
|
*(__u16 *) data_buf = swap_16(1);
|
|
len = 2;
|
|
break;
|
|
|
|
case RH_GET_STATUS | RH_INTERFACE:
|
|
debug("RH_GET_STATUS | RH_INTERFACE\n");
|
|
|
|
*(__u16 *) data_buf = swap_16(0);
|
|
len = 2;
|
|
break;
|
|
|
|
case RH_GET_STATUS | RH_ENDPOINT:
|
|
debug("RH_GET_STATUS | RH_ENDPOINT\n");
|
|
|
|
*(__u16 *) data_buf = swap_16(0);
|
|
len = 2;
|
|
break;
|
|
|
|
case RH_GET_STATUS | RH_CLASS:
|
|
debug("RH_GET_STATUS | RH_CLASS\n");
|
|
|
|
*(__u32 *) data_buf = swap_32(0);
|
|
len = 4;
|
|
break;
|
|
|
|
case RH_GET_STATUS | RH_OTHER | RH_CLASS:
|
|
debug("RH_GET_STATUS | RH_OTHER | RH_CLASS\n");
|
|
|
|
int_usb = readw(&musbr->intrusb);
|
|
if (int_usb & MUSB_INTR_CONNECT) {
|
|
port_status |= USB_PORT_STAT_CONNECTION
|
|
| (USB_PORT_STAT_C_CONNECTION << 16);
|
|
port_status |= USB_PORT_STAT_HIGH_SPEED
|
|
| USB_PORT_STAT_ENABLE;
|
|
}
|
|
|
|
if (port_status & USB_PORT_STAT_RESET)
|
|
musb_port_reset(0);
|
|
|
|
*(__u32 *) data_buf = swap_32(port_status);
|
|
len = 4;
|
|
break;
|
|
|
|
case RH_CLEAR_FEATURE | RH_ENDPOINT:
|
|
debug("RH_CLEAR_FEATURE | RH_ENDPOINT\n");
|
|
|
|
switch (wValue) {
|
|
case RH_ENDPOINT_STALL:
|
|
debug("C_HUB_ENDPOINT_STALL\n");
|
|
len = 0;
|
|
break;
|
|
}
|
|
port_status &= ~(1 << wValue);
|
|
break;
|
|
|
|
case RH_CLEAR_FEATURE | RH_CLASS:
|
|
debug("RH_CLEAR_FEATURE | RH_CLASS\n");
|
|
|
|
switch (wValue) {
|
|
case RH_C_HUB_LOCAL_POWER:
|
|
debug("C_HUB_LOCAL_POWER\n");
|
|
len = 0;
|
|
break;
|
|
|
|
case RH_C_HUB_OVER_CURRENT:
|
|
debug("C_HUB_OVER_CURRENT\n");
|
|
len = 0;
|
|
break;
|
|
}
|
|
port_status &= ~(1 << wValue);
|
|
break;
|
|
|
|
case RH_CLEAR_FEATURE | RH_OTHER | RH_CLASS:
|
|
debug("RH_CLEAR_FEATURE | RH_OTHER | RH_CLASS\n");
|
|
|
|
switch (wValue) {
|
|
case RH_PORT_ENABLE:
|
|
len = 0;
|
|
break;
|
|
|
|
case RH_PORT_SUSPEND:
|
|
len = 0;
|
|
break;
|
|
|
|
case RH_PORT_POWER:
|
|
len = 0;
|
|
break;
|
|
|
|
case RH_C_PORT_CONNECTION:
|
|
len = 0;
|
|
break;
|
|
|
|
case RH_C_PORT_ENABLE:
|
|
len = 0;
|
|
break;
|
|
|
|
case RH_C_PORT_SUSPEND:
|
|
len = 0;
|
|
break;
|
|
|
|
case RH_C_PORT_OVER_CURRENT:
|
|
len = 0;
|
|
break;
|
|
|
|
case RH_C_PORT_RESET:
|
|
len = 0;
|
|
break;
|
|
|
|
default:
|
|
debug("invalid wValue\n");
|
|
stat = USB_ST_STALLED;
|
|
}
|
|
|
|
port_status &= ~(1 << wValue);
|
|
break;
|
|
|
|
case RH_SET_FEATURE | RH_OTHER | RH_CLASS:
|
|
debug("RH_SET_FEATURE | RH_OTHER | RH_CLASS\n");
|
|
|
|
switch (wValue) {
|
|
case RH_PORT_SUSPEND:
|
|
len = 0;
|
|
break;
|
|
|
|
case RH_PORT_RESET:
|
|
musb_port_reset(1);
|
|
len = 0;
|
|
break;
|
|
|
|
case RH_PORT_POWER:
|
|
len = 0;
|
|
break;
|
|
|
|
case RH_PORT_ENABLE:
|
|
len = 0;
|
|
break;
|
|
|
|
default:
|
|
debug("invalid wValue\n");
|
|
stat = USB_ST_STALLED;
|
|
}
|
|
|
|
port_status |= 1 << wValue;
|
|
break;
|
|
|
|
case RH_SET_ADDRESS:
|
|
debug("RH_SET_ADDRESS\n");
|
|
|
|
rh_devnum = wValue;
|
|
len = 0;
|
|
break;
|
|
|
|
case RH_GET_DESCRIPTOR:
|
|
debug("RH_GET_DESCRIPTOR: %x, %d\n", wValue, wLength);
|
|
|
|
switch (wValue) {
|
|
case (USB_DT_DEVICE << 8): /* device descriptor */
|
|
len = min_t(unsigned int,
|
|
leni, min_t(unsigned int,
|
|
sizeof(root_hub_dev_des),
|
|
wLength));
|
|
data_buf = root_hub_dev_des;
|
|
break;
|
|
|
|
case (USB_DT_CONFIG << 8): /* configuration descriptor */
|
|
len = min_t(unsigned int,
|
|
leni, min_t(unsigned int,
|
|
sizeof(root_hub_config_des),
|
|
wLength));
|
|
data_buf = root_hub_config_des;
|
|
break;
|
|
|
|
case ((USB_DT_STRING << 8) | 0x00): /* string 0 descriptors */
|
|
len = min_t(unsigned int,
|
|
leni, min_t(unsigned int,
|
|
sizeof(root_hub_str_index0),
|
|
wLength));
|
|
data_buf = root_hub_str_index0;
|
|
break;
|
|
|
|
case ((USB_DT_STRING << 8) | 0x01): /* string 1 descriptors */
|
|
len = min_t(unsigned int,
|
|
leni, min_t(unsigned int,
|
|
sizeof(root_hub_str_index1),
|
|
wLength));
|
|
data_buf = root_hub_str_index1;
|
|
break;
|
|
|
|
default:
|
|
debug("invalid wValue\n");
|
|
stat = USB_ST_STALLED;
|
|
}
|
|
|
|
break;
|
|
|
|
case RH_GET_DESCRIPTOR | RH_CLASS: {
|
|
u8 *_data_buf = (u8 *) datab;
|
|
debug("RH_GET_DESCRIPTOR | RH_CLASS\n");
|
|
|
|
_data_buf[0] = 0x09; /* min length; */
|
|
_data_buf[1] = 0x29;
|
|
_data_buf[2] = 0x1; /* 1 port */
|
|
_data_buf[3] = 0x01; /* per-port power switching */
|
|
_data_buf[3] |= 0x10; /* no overcurrent reporting */
|
|
|
|
/* Corresponds to data_buf[4-7] */
|
|
_data_buf[4] = 0;
|
|
_data_buf[5] = 5;
|
|
_data_buf[6] = 0;
|
|
_data_buf[7] = 0x02;
|
|
_data_buf[8] = 0xff;
|
|
|
|
len = min_t(unsigned int, leni,
|
|
min_t(unsigned int, data_buf[0], wLength));
|
|
break;
|
|
}
|
|
|
|
case RH_GET_CONFIGURATION:
|
|
debug("RH_GET_CONFIGURATION\n");
|
|
|
|
*(__u8 *) data_buf = 0x01;
|
|
len = 1;
|
|
break;
|
|
|
|
case RH_SET_CONFIGURATION:
|
|
debug("RH_SET_CONFIGURATION\n");
|
|
|
|
len = 0;
|
|
break;
|
|
|
|
default:
|
|
debug("*** *** *** unsupported root hub command *** *** ***\n");
|
|
stat = USB_ST_STALLED;
|
|
}
|
|
|
|
len = min_t(int, len, leni);
|
|
if (buffer != data_buf)
|
|
memcpy(buffer, data_buf, len);
|
|
|
|
dev->act_len = len;
|
|
dev->status = stat;
|
|
debug("dev act_len %d, status %lu\n", dev->act_len, dev->status);
|
|
|
|
return stat;
|
|
}
|
|
|
|
static void musb_rh_init(void)
|
|
{
|
|
rh_devnum = 0;
|
|
port_status = 0;
|
|
}
|
|
|
|
#else
|
|
|
|
static void musb_rh_init(void) {}
|
|
|
|
#endif
|
|
|
|
/*
|
|
* do a control transfer
|
|
*/
|
|
int submit_control_msg(struct usb_device *dev, unsigned long pipe, void *buffer,
|
|
int len, struct devrequest *setup)
|
|
{
|
|
int devnum = usb_pipedevice(pipe);
|
|
u8 devspeed;
|
|
|
|
#ifdef MUSB_NO_MULTIPOINT
|
|
/* Control message is for the HUB? */
|
|
if (devnum == rh_devnum) {
|
|
int stat = musb_submit_rh_msg(dev, pipe, buffer, len, setup);
|
|
if (stat)
|
|
return stat;
|
|
}
|
|
#endif
|
|
|
|
/* select control endpoint */
|
|
writeb(MUSB_CONTROL_EP, &musbr->index);
|
|
readw(&musbr->txcsr);
|
|
|
|
#ifndef MUSB_NO_MULTIPOINT
|
|
/* target addr and (for multipoint) hub addr/port */
|
|
writeb(devnum, &musbr->tar[MUSB_CONTROL_EP].txfuncaddr);
|
|
writeb(devnum, &musbr->tar[MUSB_CONTROL_EP].rxfuncaddr);
|
|
#endif
|
|
|
|
/* configure the hub address and the port number as required */
|
|
devspeed = get_dev_speed(dev);
|
|
if ((musb_ishighspeed()) && (dev->parent != NULL) &&
|
|
(devspeed != MUSB_TYPE_SPEED_HIGH)) {
|
|
config_hub_port(dev, MUSB_CONTROL_EP);
|
|
writeb(devspeed << 6, &musbr->txtype);
|
|
} else {
|
|
writeb(musb_cfg.musb_speed << 6, &musbr->txtype);
|
|
#ifndef MUSB_NO_MULTIPOINT
|
|
writeb(0, &musbr->tar[MUSB_CONTROL_EP].txhubaddr);
|
|
writeb(0, &musbr->tar[MUSB_CONTROL_EP].txhubport);
|
|
writeb(0, &musbr->tar[MUSB_CONTROL_EP].rxhubaddr);
|
|
writeb(0, &musbr->tar[MUSB_CONTROL_EP].rxhubport);
|
|
#endif
|
|
}
|
|
|
|
/* Control transfer setup phase */
|
|
if (ctrlreq_setup_phase(dev, setup) < 0)
|
|
return 0;
|
|
|
|
switch (setup->request) {
|
|
case USB_REQ_GET_DESCRIPTOR:
|
|
case USB_REQ_GET_CONFIGURATION:
|
|
case USB_REQ_GET_INTERFACE:
|
|
case USB_REQ_GET_STATUS:
|
|
case USB_MSC_BBB_GET_MAX_LUN:
|
|
/* control transfer in-data-phase */
|
|
if (ctrlreq_in_data_phase(dev, len, buffer) < 0)
|
|
return 0;
|
|
/* control transfer out-status-phase */
|
|
if (ctrlreq_out_status_phase(dev) < 0)
|
|
return 0;
|
|
break;
|
|
|
|
case USB_REQ_SET_ADDRESS:
|
|
case USB_REQ_SET_CONFIGURATION:
|
|
case USB_REQ_SET_FEATURE:
|
|
case USB_REQ_SET_INTERFACE:
|
|
case USB_REQ_CLEAR_FEATURE:
|
|
case USB_MSC_BBB_RESET:
|
|
/* control transfer in status phase */
|
|
if (ctrlreq_in_status_phase(dev) < 0)
|
|
return 0;
|
|
break;
|
|
|
|
case USB_REQ_SET_DESCRIPTOR:
|
|
/* control transfer out data phase */
|
|
if (ctrlreq_out_data_phase(dev, len, buffer) < 0)
|
|
return 0;
|
|
/* control transfer in status phase */
|
|
if (ctrlreq_in_status_phase(dev) < 0)
|
|
return 0;
|
|
break;
|
|
|
|
default:
|
|
/* unhandled control transfer */
|
|
return -1;
|
|
}
|
|
|
|
dev->status = 0;
|
|
dev->act_len = len;
|
|
|
|
#ifdef MUSB_NO_MULTIPOINT
|
|
/* Set device address to USB_FADDR register */
|
|
if (setup->request == USB_REQ_SET_ADDRESS)
|
|
writeb(dev->devnum, &musbr->faddr);
|
|
#endif
|
|
|
|
return len;
|
|
}
|
|
|
|
/*
|
|
* do a bulk transfer
|
|
*/
|
|
int submit_bulk_msg(struct usb_device *dev, unsigned long pipe,
|
|
void *buffer, int len)
|
|
{
|
|
int dir_out = usb_pipeout(pipe);
|
|
int ep = usb_pipeendpoint(pipe);
|
|
#ifndef MUSB_NO_MULTIPOINT
|
|
int devnum = usb_pipedevice(pipe);
|
|
#endif
|
|
u8 type;
|
|
u16 csr;
|
|
u32 txlen = 0;
|
|
u32 nextlen = 0;
|
|
u8 devspeed;
|
|
|
|
/* select bulk endpoint */
|
|
writeb(MUSB_BULK_EP, &musbr->index);
|
|
|
|
#ifndef MUSB_NO_MULTIPOINT
|
|
/* write the address of the device */
|
|
if (dir_out)
|
|
writeb(devnum, &musbr->tar[MUSB_BULK_EP].txfuncaddr);
|
|
else
|
|
writeb(devnum, &musbr->tar[MUSB_BULK_EP].rxfuncaddr);
|
|
#endif
|
|
|
|
/* configure the hub address and the port number as required */
|
|
devspeed = get_dev_speed(dev);
|
|
if ((musb_ishighspeed()) && (dev->parent != NULL) &&
|
|
(devspeed != MUSB_TYPE_SPEED_HIGH)) {
|
|
/*
|
|
* MUSB is in high speed and the destination device is full
|
|
* speed device. So configure the hub address and port
|
|
* address registers.
|
|
*/
|
|
config_hub_port(dev, MUSB_BULK_EP);
|
|
} else {
|
|
#ifndef MUSB_NO_MULTIPOINT
|
|
if (dir_out) {
|
|
writeb(0, &musbr->tar[MUSB_BULK_EP].txhubaddr);
|
|
writeb(0, &musbr->tar[MUSB_BULK_EP].txhubport);
|
|
} else {
|
|
writeb(0, &musbr->tar[MUSB_BULK_EP].rxhubaddr);
|
|
writeb(0, &musbr->tar[MUSB_BULK_EP].rxhubport);
|
|
}
|
|
#endif
|
|
devspeed = musb_cfg.musb_speed;
|
|
}
|
|
|
|
/* Write the saved toggle bit value */
|
|
write_toggle(dev, ep, dir_out);
|
|
|
|
if (dir_out) { /* bulk-out transfer */
|
|
/* Program the TxType register */
|
|
type = (devspeed << MUSB_TYPE_SPEED_SHIFT) |
|
|
(MUSB_TYPE_PROTO_BULK << MUSB_TYPE_PROTO_SHIFT) |
|
|
(ep & MUSB_TYPE_REMOTE_END);
|
|
writeb(type, &musbr->txtype);
|
|
|
|
/* Write maximum packet size to the TxMaxp register */
|
|
writew(dev->epmaxpacketout[ep], &musbr->txmaxp);
|
|
while (txlen < len) {
|
|
nextlen = ((len-txlen) < dev->epmaxpacketout[ep]) ?
|
|
(len-txlen) : dev->epmaxpacketout[ep];
|
|
|
|
/* Write the data to the FIFO */
|
|
write_fifo(MUSB_BULK_EP, nextlen,
|
|
(void *)(((u8 *)buffer) + txlen));
|
|
|
|
/* Set the TxPktRdy bit */
|
|
csr = readw(&musbr->txcsr);
|
|
writew(csr | MUSB_TXCSR_TXPKTRDY, &musbr->txcsr);
|
|
|
|
/* Wait until the TxPktRdy bit is cleared */
|
|
if (wait_until_txep_ready(dev, MUSB_BULK_EP) != 1) {
|
|
readw(&musbr->txcsr);
|
|
usb_settoggle(dev, ep, dir_out,
|
|
(csr >> MUSB_TXCSR_H_DATATOGGLE_SHIFT) & 1);
|
|
dev->act_len = txlen;
|
|
return 0;
|
|
}
|
|
txlen += nextlen;
|
|
}
|
|
|
|
/* Keep a copy of the data toggle bit */
|
|
csr = readw(&musbr->txcsr);
|
|
usb_settoggle(dev, ep, dir_out,
|
|
(csr >> MUSB_TXCSR_H_DATATOGGLE_SHIFT) & 1);
|
|
} else { /* bulk-in transfer */
|
|
/* Write the saved toggle bit value */
|
|
write_toggle(dev, ep, dir_out);
|
|
|
|
/* Program the RxType register */
|
|
type = (devspeed << MUSB_TYPE_SPEED_SHIFT) |
|
|
(MUSB_TYPE_PROTO_BULK << MUSB_TYPE_PROTO_SHIFT) |
|
|
(ep & MUSB_TYPE_REMOTE_END);
|
|
writeb(type, &musbr->rxtype);
|
|
|
|
/* Write the maximum packet size to the RxMaxp register */
|
|
writew(dev->epmaxpacketin[ep], &musbr->rxmaxp);
|
|
while (txlen < len) {
|
|
nextlen = ((len-txlen) < dev->epmaxpacketin[ep]) ?
|
|
(len-txlen) : dev->epmaxpacketin[ep];
|
|
|
|
/* Set the ReqPkt bit */
|
|
csr = readw(&musbr->rxcsr);
|
|
writew(csr | MUSB_RXCSR_H_REQPKT, &musbr->rxcsr);
|
|
|
|
/* Wait until the RxPktRdy bit is set */
|
|
if (wait_until_rxep_ready(dev, MUSB_BULK_EP) != 1) {
|
|
csr = readw(&musbr->rxcsr);
|
|
usb_settoggle(dev, ep, dir_out,
|
|
(csr >> MUSB_S_RXCSR_H_DATATOGGLE) & 1);
|
|
csr &= ~MUSB_RXCSR_RXPKTRDY;
|
|
writew(csr, &musbr->rxcsr);
|
|
dev->act_len = txlen;
|
|
return 0;
|
|
}
|
|
|
|
/* Read the data from the FIFO */
|
|
read_fifo(MUSB_BULK_EP, nextlen,
|
|
(void *)(((u8 *)buffer) + txlen));
|
|
|
|
/* Clear the RxPktRdy bit */
|
|
csr = readw(&musbr->rxcsr);
|
|
csr &= ~MUSB_RXCSR_RXPKTRDY;
|
|
writew(csr, &musbr->rxcsr);
|
|
txlen += nextlen;
|
|
}
|
|
|
|
/* Keep a copy of the data toggle bit */
|
|
csr = readw(&musbr->rxcsr);
|
|
usb_settoggle(dev, ep, dir_out,
|
|
(csr >> MUSB_S_RXCSR_H_DATATOGGLE) & 1);
|
|
}
|
|
|
|
/* bulk transfer is complete */
|
|
dev->status = 0;
|
|
dev->act_len = len;
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* This function initializes the usb controller module.
|
|
*/
|
|
int usb_lowlevel_init(int index, enum usb_init_type init, void **controller)
|
|
{
|
|
u8 power;
|
|
u32 timeout;
|
|
|
|
musb_rh_init();
|
|
|
|
if (musb_platform_init() == -1)
|
|
return -1;
|
|
|
|
/* Configure all the endpoint FIFO's and start usb controller */
|
|
musbr = musb_cfg.regs;
|
|
musb_configure_ep(&epinfo[0], ARRAY_SIZE(epinfo));
|
|
musb_start();
|
|
|
|
/*
|
|
* Wait until musb is enabled in host mode with a timeout. There
|
|
* should be a usb device connected.
|
|
*/
|
|
timeout = musb_cfg.timeout;
|
|
while (--timeout)
|
|
if (readb(&musbr->devctl) & MUSB_DEVCTL_HM)
|
|
break;
|
|
|
|
/* if musb core is not in host mode, then return */
|
|
if (!timeout)
|
|
return -1;
|
|
|
|
/* start usb bus reset */
|
|
power = readb(&musbr->power);
|
|
writeb(power | MUSB_POWER_RESET, &musbr->power);
|
|
|
|
/* After initiating a usb reset, wait for about 20ms to 30ms */
|
|
udelay(30000);
|
|
|
|
/* stop usb bus reset */
|
|
power = readb(&musbr->power);
|
|
power &= ~MUSB_POWER_RESET;
|
|
writeb(power, &musbr->power);
|
|
|
|
/* Determine if the connected device is a high/full/low speed device */
|
|
musb_cfg.musb_speed = (readb(&musbr->power) & MUSB_POWER_HSMODE) ?
|
|
MUSB_TYPE_SPEED_HIGH :
|
|
((readb(&musbr->devctl) & MUSB_DEVCTL_FSDEV) ?
|
|
MUSB_TYPE_SPEED_FULL : MUSB_TYPE_SPEED_LOW);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* This function stops the operation of the davinci usb module.
|
|
*/
|
|
int usb_lowlevel_stop(int index)
|
|
{
|
|
/* Reset the USB module */
|
|
musb_platform_deinit();
|
|
writeb(0, &musbr->devctl);
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* This function supports usb interrupt transfers. Currently, usb interrupt
|
|
* transfers are not supported.
|
|
*/
|
|
int submit_int_msg(struct usb_device *dev, unsigned long pipe,
|
|
void *buffer, int len, int interval)
|
|
{
|
|
int dir_out = usb_pipeout(pipe);
|
|
int ep = usb_pipeendpoint(pipe);
|
|
#ifndef MUSB_NO_MULTIPOINT
|
|
int devnum = usb_pipedevice(pipe);
|
|
#endif
|
|
u8 type;
|
|
u16 csr;
|
|
u32 txlen = 0;
|
|
u32 nextlen = 0;
|
|
u8 devspeed;
|
|
|
|
/* select interrupt endpoint */
|
|
writeb(MUSB_INTR_EP, &musbr->index);
|
|
|
|
#ifndef MUSB_NO_MULTIPOINT
|
|
/* write the address of the device */
|
|
if (dir_out)
|
|
writeb(devnum, &musbr->tar[MUSB_INTR_EP].txfuncaddr);
|
|
else
|
|
writeb(devnum, &musbr->tar[MUSB_INTR_EP].rxfuncaddr);
|
|
#endif
|
|
|
|
/* configure the hub address and the port number as required */
|
|
devspeed = get_dev_speed(dev);
|
|
if ((musb_ishighspeed()) && (dev->parent != NULL) &&
|
|
(devspeed != MUSB_TYPE_SPEED_HIGH)) {
|
|
/*
|
|
* MUSB is in high speed and the destination device is full
|
|
* speed device. So configure the hub address and port
|
|
* address registers.
|
|
*/
|
|
config_hub_port(dev, MUSB_INTR_EP);
|
|
} else {
|
|
#ifndef MUSB_NO_MULTIPOINT
|
|
if (dir_out) {
|
|
writeb(0, &musbr->tar[MUSB_INTR_EP].txhubaddr);
|
|
writeb(0, &musbr->tar[MUSB_INTR_EP].txhubport);
|
|
} else {
|
|
writeb(0, &musbr->tar[MUSB_INTR_EP].rxhubaddr);
|
|
writeb(0, &musbr->tar[MUSB_INTR_EP].rxhubport);
|
|
}
|
|
#endif
|
|
devspeed = musb_cfg.musb_speed;
|
|
}
|
|
|
|
/* Write the saved toggle bit value */
|
|
write_toggle(dev, ep, dir_out);
|
|
|
|
if (!dir_out) { /* intrrupt-in transfer */
|
|
/* Write the saved toggle bit value */
|
|
write_toggle(dev, ep, dir_out);
|
|
writeb(interval, &musbr->rxinterval);
|
|
|
|
/* Program the RxType register */
|
|
type = (devspeed << MUSB_TYPE_SPEED_SHIFT) |
|
|
(MUSB_TYPE_PROTO_INTR << MUSB_TYPE_PROTO_SHIFT) |
|
|
(ep & MUSB_TYPE_REMOTE_END);
|
|
writeb(type, &musbr->rxtype);
|
|
|
|
/* Write the maximum packet size to the RxMaxp register */
|
|
writew(dev->epmaxpacketin[ep], &musbr->rxmaxp);
|
|
|
|
while (txlen < len) {
|
|
nextlen = ((len-txlen) < dev->epmaxpacketin[ep]) ?
|
|
(len-txlen) : dev->epmaxpacketin[ep];
|
|
|
|
/* Set the ReqPkt bit */
|
|
csr = readw(&musbr->rxcsr);
|
|
writew(csr | MUSB_RXCSR_H_REQPKT, &musbr->rxcsr);
|
|
|
|
/* Wait until the RxPktRdy bit is set */
|
|
if (wait_until_rxep_ready(dev, MUSB_INTR_EP) != 1) {
|
|
csr = readw(&musbr->rxcsr);
|
|
usb_settoggle(dev, ep, dir_out,
|
|
(csr >> MUSB_S_RXCSR_H_DATATOGGLE) & 1);
|
|
csr &= ~MUSB_RXCSR_RXPKTRDY;
|
|
writew(csr, &musbr->rxcsr);
|
|
dev->act_len = txlen;
|
|
return 0;
|
|
}
|
|
|
|
/* Read the data from the FIFO */
|
|
read_fifo(MUSB_INTR_EP, nextlen,
|
|
(void *)(((u8 *)buffer) + txlen));
|
|
|
|
/* Clear the RxPktRdy bit */
|
|
csr = readw(&musbr->rxcsr);
|
|
csr &= ~MUSB_RXCSR_RXPKTRDY;
|
|
writew(csr, &musbr->rxcsr);
|
|
txlen += nextlen;
|
|
}
|
|
|
|
/* Keep a copy of the data toggle bit */
|
|
csr = readw(&musbr->rxcsr);
|
|
usb_settoggle(dev, ep, dir_out,
|
|
(csr >> MUSB_S_RXCSR_H_DATATOGGLE) & 1);
|
|
}
|
|
|
|
/* interrupt transfer is complete */
|
|
dev->irq_status = 0;
|
|
dev->irq_act_len = len;
|
|
dev->irq_handle(dev);
|
|
dev->status = 0;
|
|
dev->act_len = len;
|
|
return 0;
|
|
}
|