summaryrefslogtreecommitdiffstats
path: root/drivers/usb
diff options
context:
space:
mode:
authorAlbert ARIBAUD <albert.u.boot@aribaud.net>2012-09-30 23:49:17 +0200
committerAlbert ARIBAUD <albert.u.boot@aribaud.net>2012-09-30 23:49:17 +0200
commit1c27059a2f7158a9c9a8778535b030935d75179d (patch)
treebf577d5c9f0da21c5d57feed1091214e54c39dec /drivers/usb
parent8f0732ac3dc3bdbbcada313dc4b4b38d5d2c376a (diff)
parent4668a086bb0a769b741e3a4ffab85f1c41c7cdb8 (diff)
downloadblackbird-obmc-uboot-1c27059a2f7158a9c9a8778535b030935d75179d.tar.gz
blackbird-obmc-uboot-1c27059a2f7158a9c9a8778535b030935d75179d.zip
Merge remote-tracking branch 'u-boot/master'
Diffstat (limited to 'drivers/usb')
-rw-r--r--drivers/usb/eth/asix.c185
-rw-r--r--drivers/usb/eth/smsc95xx.c49
-rw-r--r--drivers/usb/gadget/Makefile2
-rw-r--r--drivers/usb/gadget/f_dfu.c749
-rw-r--r--drivers/usb/gadget/f_dfu.h100
-rw-r--r--drivers/usb/gadget/g_dnl.c202
-rw-r--r--drivers/usb/host/ehci-hcd.c312
-rw-r--r--drivers/usb/host/ehci.h60
-rw-r--r--drivers/usb/host/ohci-hcd.c70
-rw-r--r--drivers/usb/musb/musb_hcd.c2
10 files changed, 1529 insertions, 202 deletions
diff --git a/drivers/usb/eth/asix.c b/drivers/usb/eth/asix.c
index 8fb7fc8c90..75ec8f7881 100644
--- a/drivers/usb/eth/asix.c
+++ b/drivers/usb/eth/asix.c
@@ -23,6 +23,7 @@
#include <usb.h>
#include <linux/mii.h>
#include "usb_ether.h"
+#include <malloc.h>
/* ASIX AX8817X based USB 2.0 Ethernet Devices */
@@ -31,10 +32,12 @@
#define AX_CMD_READ_MII_REG 0x07
#define AX_CMD_WRITE_MII_REG 0x08
#define AX_CMD_SET_HW_MII 0x0a
+#define AX_CMD_READ_EEPROM 0x0b
#define AX_CMD_READ_RX_CTL 0x0f
#define AX_CMD_WRITE_RX_CTL 0x10
#define AX_CMD_WRITE_IPG0 0x12
#define AX_CMD_READ_NODE_ID 0x13
+#define AX_CMD_WRITE_NODE_ID 0x14
#define AX_CMD_READ_PHY_ID 0x19
#define AX_CMD_WRITE_MEDIUM_MODE 0x1b
#define AX_CMD_WRITE_GPIOS 0x1f
@@ -97,9 +100,21 @@
#define AX_RX_URB_SIZE 2048
#define PHY_CONNECT_TIMEOUT 5000
+/* asix_flags defines */
+#define FLAG_NONE 0
+#define FLAG_TYPE_AX88172 (1U << 0)
+#define FLAG_TYPE_AX88772 (1U << 1)
+#define FLAG_TYPE_AX88772B (1U << 2)
+#define FLAG_EEPROM_MAC (1U << 3) /* initial mac address in eeprom */
+
/* local vars */
static int curr_eth_dev; /* index for name of next device detected */
+/* driver private */
+struct asix_private {
+ int flags;
+};
+
/*
* Asix infrastructure commands
*/
@@ -284,6 +299,21 @@ static int asix_write_gpio(struct ueth_data *dev, u16 value, int sleep)
return ret;
}
+static int asix_write_hwaddr(struct eth_device *eth)
+{
+ struct ueth_data *dev = (struct ueth_data *)eth->priv;
+ int ret;
+ ALLOC_CACHE_ALIGN_BUFFER(unsigned char, buf, ETH_ALEN);
+
+ memcpy(buf, eth->enetaddr, ETH_ALEN);
+
+ ret = asix_write_cmd(dev, AX_CMD_WRITE_NODE_ID, 0, 0, ETH_ALEN, buf);
+ if (ret < 0)
+ debug("Failed to set MAC address: %02x\n", ret);
+
+ return ret;
+}
+
/*
* mii commands
*/
@@ -310,66 +340,87 @@ static int mii_nway_restart(struct ueth_data *dev)
return r;
}
-/*
- * Asix callbacks
- */
-static int asix_init(struct eth_device *eth, bd_t *bd)
+static int asix_read_mac(struct eth_device *eth)
{
- int embd_phy;
+ struct ueth_data *dev = (struct ueth_data *)eth->priv;
+ struct asix_private *priv = (struct asix_private *)dev->dev_priv;
+ int i;
ALLOC_CACHE_ALIGN_BUFFER(unsigned char, buf, ETH_ALEN);
- u16 rx_ctl;
- struct ueth_data *dev = (struct ueth_data *)eth->priv;
- int timeout = 0;
-#define TIMEOUT_RESOLUTION 50 /* ms */
- int link_detected;
- debug("** %s()\n", __func__);
+ if (priv->flags & FLAG_EEPROM_MAC) {
+ for (i = 0; i < (ETH_ALEN >> 1); i++) {
+ if (asix_read_cmd(dev, AX_CMD_READ_EEPROM,
+ 0x04 + i, 0, 2, buf) < 0) {
+ debug("Failed to read SROM address 04h.\n");
+ return -1;
+ }
+ memcpy((eth->enetaddr + i * 2), buf, 2);
+ }
+ } else {
+ if (asix_read_cmd(dev, AX_CMD_READ_NODE_ID, 0, 0, ETH_ALEN, buf)
+ < 0) {
+ debug("Failed to read MAC address.\n");
+ return -1;
+ }
+ memcpy(eth->enetaddr, buf, ETH_ALEN);
+ }
+
+ return 0;
+}
+
+static int asix_basic_reset(struct ueth_data *dev)
+{
+ int embd_phy;
+ u16 rx_ctl;
if (asix_write_gpio(dev,
AX_GPIO_RSE | AX_GPIO_GPO_2 | AX_GPIO_GPO2EN, 5) < 0)
- goto out_err;
+ return -1;
/* 0x10 is the phy id of the embedded 10/100 ethernet phy */
embd_phy = ((asix_get_phy_addr(dev) & 0x1f) == 0x10 ? 1 : 0);
if (asix_write_cmd(dev, AX_CMD_SW_PHY_SELECT,
embd_phy, 0, 0, NULL) < 0) {
debug("Select PHY #1 failed\n");
- goto out_err;
+ return -1;
}
if (asix_sw_reset(dev, AX_SWRESET_IPPD | AX_SWRESET_PRL) < 0)
- goto out_err;
+ return -1;
if (asix_sw_reset(dev, AX_SWRESET_CLEAR) < 0)
- goto out_err;
+ return -1;
if (embd_phy) {
if (asix_sw_reset(dev, AX_SWRESET_IPRL) < 0)
- goto out_err;
+ return -1;
} else {
if (asix_sw_reset(dev, AX_SWRESET_PRTE) < 0)
- goto out_err;
+ return -1;
}
rx_ctl = asix_read_rx_ctl(dev);
debug("RX_CTL is 0x%04x after software reset\n", rx_ctl);
if (asix_write_rx_ctl(dev, 0x0000) < 0)
- goto out_err;
+ return -1;
rx_ctl = asix_read_rx_ctl(dev);
debug("RX_CTL is 0x%04x setting to 0x0000\n", rx_ctl);
- /* Get the MAC address */
- if (asix_read_cmd(dev, AX_CMD_READ_NODE_ID,
- 0, 0, ETH_ALEN, buf) < 0) {
- debug("Failed to read MAC address.\n");
- goto out_err;
- }
- memcpy(eth->enetaddr, buf, ETH_ALEN);
- debug("MAC %02x:%02x:%02x:%02x:%02x:%02x\n",
- eth->enetaddr[0], eth->enetaddr[1],
- eth->enetaddr[2], eth->enetaddr[3],
- eth->enetaddr[4], eth->enetaddr[5]);
+ return 0;
+}
+
+/*
+ * Asix callbacks
+ */
+static int asix_init(struct eth_device *eth, bd_t *bd)
+{
+ struct ueth_data *dev = (struct ueth_data *)eth->priv;
+ int timeout = 0;
+#define TIMEOUT_RESOLUTION 50 /* ms */
+ int link_detected;
+
+ debug("** %s()\n", __func__);
dev->phy_id = asix_get_phy_addr(dev);
if (dev->phy_id < 0)
@@ -493,13 +544,13 @@ static int asix_recv(struct eth_device *eth)
}
memcpy(&packet_len, buf_ptr, sizeof(packet_len));
le32_to_cpus(&packet_len);
- if (((packet_len >> 16) ^ 0xffff) != (packet_len & 0xffff)) {
+ if (((~packet_len >> 16) & 0x7ff) != (packet_len & 0x7ff)) {
debug("Rx: malformed packet length: %#x (%#x:%#x)\n",
- packet_len, (packet_len >> 16) ^ 0xffff,
- packet_len & 0xffff);
+ packet_len, (~packet_len >> 16) & 0x7ff,
+ packet_len & 0x7ff);
return -1;
}
- packet_len = packet_len & 0xffff;
+ packet_len = packet_len & 0x7ff;
if (packet_len > actual_len - sizeof(packet_len)) {
debug("Rx: too large packet: %d\n", packet_len);
return -1;
@@ -534,19 +585,24 @@ void asix_eth_before_probe(void)
struct asix_dongle {
unsigned short vendor;
unsigned short product;
+ int flags;
};
-static struct asix_dongle asix_dongles[] = {
- { 0x05ac, 0x1402 }, /* Apple USB Ethernet Adapter */
- { 0x07d1, 0x3c05 }, /* D-Link DUB-E100 H/W Ver B1 */
- { 0x0b95, 0x772a }, /* Cables-to-Go USB Ethernet Adapter */
- { 0x0b95, 0x7720 }, /* Trendnet TU2-ET100 V3.0R */
- { 0x0b95, 0x1720 }, /* SMC */
- { 0x0db0, 0xa877 }, /* MSI - ASIX 88772a */
- { 0x13b1, 0x0018 }, /* Linksys 200M v2.1 */
- { 0x1557, 0x7720 }, /* 0Q0 cable ethernet */
- { 0x2001, 0x3c05 }, /* DLink DUB-E100 H/W Ver B1 Alternate */
- { 0x0000, 0x0000 } /* END - Do not remove */
+static const struct asix_dongle const asix_dongles[] = {
+ { 0x05ac, 0x1402, FLAG_TYPE_AX88772 }, /* Apple USB Ethernet Adapter */
+ { 0x07d1, 0x3c05, FLAG_TYPE_AX88772 }, /* D-Link DUB-E100 H/W Ver B1 */
+ /* Cables-to-Go USB Ethernet Adapter */
+ { 0x0b95, 0x772a, FLAG_TYPE_AX88772 },
+ { 0x0b95, 0x7720, FLAG_TYPE_AX88772 }, /* Trendnet TU2-ET100 V3.0R */
+ { 0x0b95, 0x1720, FLAG_TYPE_AX88172 }, /* SMC */
+ { 0x0db0, 0xa877, FLAG_TYPE_AX88772 }, /* MSI - ASIX 88772a */
+ { 0x13b1, 0x0018, FLAG_TYPE_AX88172 }, /* Linksys 200M v2.1 */
+ { 0x1557, 0x7720, FLAG_TYPE_AX88772 }, /* 0Q0 cable ethernet */
+ /* DLink DUB-E100 H/W Ver B1 Alternate */
+ { 0x2001, 0x3c05, FLAG_TYPE_AX88772 },
+ /* ASIX 88772B */
+ { 0x0b95, 0x772b, FLAG_TYPE_AX88772B | FLAG_EEPROM_MAC },
+ { 0x0000, 0x0000, FLAG_NONE } /* END - Do not remove */
};
/* Probe to see if a new device is actually an asix device */
@@ -555,6 +611,7 @@ int asix_eth_probe(struct usb_device *dev, unsigned int ifnum,
{
struct usb_interface *iface;
struct usb_interface_descriptor *iface_desc;
+ int ep_in_found = 0, ep_out_found = 0;
int i;
/* let's examine the device now */
@@ -583,6 +640,13 @@ int asix_eth_probe(struct usb_device *dev, unsigned int ifnum,
ss->subclass = iface_desc->bInterfaceSubClass;
ss->protocol = iface_desc->bInterfaceProtocol;
+ /* alloc driver private */
+ ss->dev_priv = calloc(1, sizeof(struct asix_private));
+ if (!ss->dev_priv)
+ return 0;
+
+ ((struct asix_private *)ss->dev_priv)->flags = asix_dongles[i].flags;
+
/*
* We are expecting a minimum of 3 endpoints - in, out (bulk), and
* int. We will ignore any others.
@@ -591,13 +655,20 @@ int asix_eth_probe(struct usb_device *dev, unsigned int ifnum,
/* is it an BULK endpoint? */
if ((iface->ep_desc[i].bmAttributes &
USB_ENDPOINT_XFERTYPE_MASK) == USB_ENDPOINT_XFER_BULK) {
- if (iface->ep_desc[i].bEndpointAddress & USB_DIR_IN)
- ss->ep_in = iface->ep_desc[i].bEndpointAddress &
- USB_ENDPOINT_NUMBER_MASK;
- else
- ss->ep_out =
- iface->ep_desc[i].bEndpointAddress &
- USB_ENDPOINT_NUMBER_MASK;
+ u8 ep_addr = iface->ep_desc[i].bEndpointAddress;
+ if (ep_addr & USB_DIR_IN) {
+ if (!ep_in_found) {
+ ss->ep_in = ep_addr &
+ USB_ENDPOINT_NUMBER_MASK;
+ ep_in_found = 1;
+ }
+ } else {
+ if (!ep_out_found) {
+ ss->ep_out = ep_addr &
+ USB_ENDPOINT_NUMBER_MASK;
+ ep_out_found = 1;
+ }
+ }
}
/* is it an interrupt endpoint? */
@@ -624,6 +695,8 @@ int asix_eth_probe(struct usb_device *dev, unsigned int ifnum,
int asix_eth_get_info(struct usb_device *dev, struct ueth_data *ss,
struct eth_device *eth)
{
+ struct asix_private *priv = (struct asix_private *)ss->dev_priv;
+
if (!eth) {
debug("%s: missing parameter.\n", __func__);
return 0;
@@ -633,7 +706,17 @@ int asix_eth_get_info(struct usb_device *dev, struct ueth_data *ss,
eth->send = asix_send;
eth->recv = asix_recv;
eth->halt = asix_halt;
+ if (!(priv->flags & FLAG_TYPE_AX88172))
+ eth->write_hwaddr = asix_write_hwaddr;
eth->priv = ss;
+ if (asix_basic_reset(ss))
+ return 0;
+
+ /* Get the MAC address */
+ if (asix_read_mac(eth))
+ return 0;
+ debug("MAC %pM\n", eth->enetaddr);
+
return 1;
}
diff --git a/drivers/usb/eth/smsc95xx.c b/drivers/usb/eth/smsc95xx.c
index c62a8c112e..dc5ca65463 100644
--- a/drivers/usb/eth/smsc95xx.c
+++ b/drivers/usb/eth/smsc95xx.c
@@ -25,6 +25,7 @@
#include <usb.h>
#include <linux/mii.h>
#include "usb_ether.h"
+#include <malloc.h>
/* SMSC LAN95xx based USB 2.0 Ethernet Devices */
@@ -146,6 +147,12 @@
/* local vars */
static int curr_eth_dev; /* index for name of next device detected */
+/* driver private */
+struct smsc95xx_private {
+ size_t rx_urb_size; /* maximum USB URB size */
+ u32 mac_cr; /* MAC control register value */
+ int have_hwaddr; /* 1 if we have a hardware MAC address */
+};
/*
* Smsc95xx infrastructure commands
@@ -377,6 +384,7 @@ static int smsc95xx_init_mac_address(struct eth_device *eth,
static int smsc95xx_write_hwaddr(struct eth_device *eth)
{
struct ueth_data *dev = (struct ueth_data *)eth->priv;
+ struct smsc95xx_private *priv = dev->dev_priv;
u32 addr_lo = __get_unaligned_le32(&eth->enetaddr[0]);
u32 addr_hi = __get_unaligned_le16(&eth->enetaddr[4]);
int ret;
@@ -392,7 +400,7 @@ static int smsc95xx_write_hwaddr(struct eth_device *eth)
return ret;
debug("MAC %pM\n", eth->enetaddr);
- dev->have_hwaddr = 1;
+ priv->have_hwaddr = 1;
return 0;
}
@@ -425,19 +433,22 @@ static int smsc95xx_set_csums(struct ueth_data *dev,
static void smsc95xx_set_multicast(struct ueth_data *dev)
{
+ struct smsc95xx_private *priv = dev->dev_priv;
+
/* No multicast in u-boot */
- dev->mac_cr &= ~(MAC_CR_PRMS_ | MAC_CR_MCPAS_ | MAC_CR_HPFILT_);
+ priv->mac_cr &= ~(MAC_CR_PRMS_ | MAC_CR_MCPAS_ | MAC_CR_HPFILT_);
}
/* starts the TX path */
static void smsc95xx_start_tx_path(struct ueth_data *dev)
{
+ struct smsc95xx_private *priv = dev->dev_priv;
u32 reg_val;
/* Enable Tx at MAC */
- dev->mac_cr |= MAC_CR_TXEN_;
+ priv->mac_cr |= MAC_CR_TXEN_;
- smsc95xx_write_reg(dev, MAC_CR, dev->mac_cr);
+ smsc95xx_write_reg(dev, MAC_CR, priv->mac_cr);
/* Enable Tx at SCSRs */
reg_val = TX_CFG_ON_;
@@ -447,8 +458,10 @@ static void smsc95xx_start_tx_path(struct ueth_data *dev)
/* Starts the Receive path */
static void smsc95xx_start_rx_path(struct ueth_data *dev)
{
- dev->mac_cr |= MAC_CR_RXEN_;
- smsc95xx_write_reg(dev, MAC_CR, dev->mac_cr);
+ struct smsc95xx_private *priv = dev->dev_priv;
+
+ priv->mac_cr |= MAC_CR_RXEN_;
+ smsc95xx_write_reg(dev, MAC_CR, priv->mac_cr);
}
/*
@@ -462,6 +475,8 @@ static int smsc95xx_init(struct eth_device *eth, bd_t *bd)
u32 burst_cap;
int timeout;
struct ueth_data *dev = (struct ueth_data *)eth->priv;
+ struct smsc95xx_private *priv =
+ (struct smsc95xx_private *)dev->dev_priv;
#define TIMEOUT_RESOLUTION 50 /* ms */
int link_detected;
@@ -504,9 +519,9 @@ static int smsc95xx_init(struct eth_device *eth, bd_t *bd)
debug("timeout waiting for PHY Reset\n");
return -1;
}
- if (!dev->have_hwaddr && smsc95xx_init_mac_address(eth, dev) == 0)
- dev->have_hwaddr = 1;
- if (!dev->have_hwaddr) {
+ if (!priv->have_hwaddr && smsc95xx_init_mac_address(eth, dev) == 0)
+ priv->have_hwaddr = 1;
+ if (!priv->have_hwaddr) {
puts("Error: SMSC95xx: No MAC address set - set usbethaddr\n");
return -1;
}
@@ -532,16 +547,16 @@ static int smsc95xx_init(struct eth_device *eth, bd_t *bd)
#ifdef TURBO_MODE
if (dev->pusb_dev->speed == USB_SPEED_HIGH) {
burst_cap = DEFAULT_HS_BURST_CAP_SIZE / HS_USB_PKT_SIZE;
- dev->rx_urb_size = DEFAULT_HS_BURST_CAP_SIZE;
+ priv->rx_urb_size = DEFAULT_HS_BURST_CAP_SIZE;
} else {
burst_cap = DEFAULT_FS_BURST_CAP_SIZE / FS_USB_PKT_SIZE;
- dev->rx_urb_size = DEFAULT_FS_BURST_CAP_SIZE;
+ priv->rx_urb_size = DEFAULT_FS_BURST_CAP_SIZE;
}
#else
burst_cap = 0;
- dev->rx_urb_size = MAX_SINGLE_PACKET_SIZE;
+ priv->rx_urb_size = MAX_SINGLE_PACKET_SIZE;
#endif
- debug("rx_urb_size=%ld\n", (ulong)dev->rx_urb_size);
+ debug("rx_urb_size=%ld\n", (ulong)priv->rx_urb_size);
ret = smsc95xx_write_reg(dev, BURST_CAP, burst_cap);
if (ret < 0)
@@ -606,7 +621,7 @@ static int smsc95xx_init(struct eth_device *eth, bd_t *bd)
if (ret < 0)
return ret;
- ret = smsc95xx_read_reg(dev, MAC_CR, &dev->mac_cr);
+ ret = smsc95xx_read_reg(dev, MAC_CR, &priv->mac_cr);
if (ret < 0)
return ret;
@@ -857,6 +872,12 @@ int smsc95xx_eth_probe(struct usb_device *dev, unsigned int ifnum,
return 0;
}
dev->privptr = (void *)ss;
+
+ /* alloc driver private */
+ ss->dev_priv = calloc(1, sizeof(struct smsc95xx_private));
+ if (!ss->dev_priv)
+ return 0;
+
return 1;
}
diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile
index 87d1918cb9..5bbdd368f4 100644
--- a/drivers/usb/gadget/Makefile
+++ b/drivers/usb/gadget/Makefile
@@ -29,6 +29,8 @@ LIB := $(obj)libusb_gadget.o
ifdef CONFIG_USB_GADGET
COBJS-y += epautoconf.o config.o usbstring.o
COBJS-$(CONFIG_USB_GADGET_S3C_UDC_OTG) += s3c_udc_otg.o
+COBJS-$(CONFIG_USBDOWNLOAD_GADGET) += g_dnl.o
+COBJS-$(CONFIG_DFU_FUNCTION) += f_dfu.o
endif
ifdef CONFIG_USB_ETHER
COBJS-y += ether.o epautoconf.o config.o usbstring.o
diff --git a/drivers/usb/gadget/f_dfu.c b/drivers/usb/gadget/f_dfu.c
new file mode 100644
index 0000000000..3ec4c65e74
--- /dev/null
+++ b/drivers/usb/gadget/f_dfu.c
@@ -0,0 +1,749 @@
+/*
+ * f_dfu.c -- Device Firmware Update USB function
+ *
+ * Copyright (C) 2012 Samsung Electronics
+ * authors: Andrzej Pietrasiewicz <andrzej.p@samsung.com>
+ * Lukasz Majewski <l.majewski@samsung.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 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <errno.h>
+#include <common.h>
+#include <malloc.h>
+
+#include <linux/usb/ch9.h>
+#include <usbdescriptors.h>
+#include <linux/usb/gadget.h>
+#include <linux/usb/composite.h>
+
+#include <dfu.h>
+#include "f_dfu.h"
+
+struct f_dfu {
+ struct usb_function usb_function;
+
+ struct usb_descriptor_header **function;
+ struct usb_string *strings;
+
+ /* when configured, we have one config */
+ u8 config;
+ u8 altsetting;
+ enum dfu_state dfu_state;
+ unsigned int dfu_status;
+
+ /* Send/received block number is handy for data integrity check */
+ int blk_seq_num;
+};
+
+typedef int (*dfu_state_fn) (struct f_dfu *,
+ const struct usb_ctrlrequest *,
+ struct usb_gadget *,
+ struct usb_request *);
+
+static inline struct f_dfu *func_to_dfu(struct usb_function *f)
+{
+ return container_of(f, struct f_dfu, usb_function);
+}
+
+static const struct dfu_function_descriptor dfu_func = {
+ .bLength = sizeof dfu_func,
+ .bDescriptorType = DFU_DT_FUNC,
+ .bmAttributes = DFU_BIT_WILL_DETACH |
+ DFU_BIT_MANIFESTATION_TOLERANT |
+ DFU_BIT_CAN_UPLOAD |
+ DFU_BIT_CAN_DNLOAD,
+ .wDetachTimeOut = 0,
+ .wTransferSize = DFU_USB_BUFSIZ,
+ .bcdDFUVersion = __constant_cpu_to_le16(0x0110),
+};
+
+static struct usb_interface_descriptor dfu_intf_runtime = {
+ .bLength = sizeof dfu_intf_runtime,
+ .bDescriptorType = USB_DT_INTERFACE,
+ .bNumEndpoints = 0,
+ .bInterfaceClass = USB_CLASS_APP_SPEC,
+ .bInterfaceSubClass = 1,
+ .bInterfaceProtocol = 1,
+ /* .iInterface = DYNAMIC */
+};
+
+static struct usb_descriptor_header *dfu_runtime_descs[] = {
+ (struct usb_descriptor_header *) &dfu_intf_runtime,
+ NULL,
+};
+
+static const struct usb_qualifier_descriptor dev_qualifier = {
+ .bLength = sizeof dev_qualifier,
+ .bDescriptorType = USB_DT_DEVICE_QUALIFIER,
+ .bcdUSB = __constant_cpu_to_le16(0x0200),
+ .bDeviceClass = USB_CLASS_VENDOR_SPEC,
+ .bNumConfigurations = 1,
+};
+
+static const char dfu_name[] = "Device Firmware Upgrade";
+
+/*
+ * static strings, in UTF-8
+ *
+ * dfu_generic configuration
+ */
+static struct usb_string strings_dfu_generic[] = {
+ [0].s = dfu_name,
+ { } /* end of list */
+};
+
+static struct usb_gadget_strings stringtab_dfu_generic = {
+ .language = 0x0409, /* en-us */
+ .strings = strings_dfu_generic,
+};
+
+static struct usb_gadget_strings *dfu_generic_strings[] = {
+ &stringtab_dfu_generic,
+ NULL,
+};
+
+/*
+ * usb_function specific
+ */
+static struct usb_gadget_strings stringtab_dfu = {
+ .language = 0x0409, /* en-us */
+ /*
+ * .strings
+ *
+ * assigned during initialization,
+ * depends on number of flash entities
+ *
+ */
+};
+
+static struct usb_gadget_strings *dfu_strings[] = {
+ &stringtab_dfu,
+ NULL,
+};
+
+/*-------------------------------------------------------------------------*/
+
+static void dnload_request_complete(struct usb_ep *ep, struct usb_request *req)
+{
+ struct f_dfu *f_dfu = req->context;
+
+ dfu_write(dfu_get_entity(f_dfu->altsetting), req->buf,
+ req->length, f_dfu->blk_seq_num);
+
+ if (req->length == 0)
+ puts("DOWNLOAD ... OK\nCtrl+C to exit ...\n");
+}
+
+static void handle_getstatus(struct usb_request *req)
+{
+ struct dfu_status *dstat = (struct dfu_status *)req->buf;
+ struct f_dfu *f_dfu = req->context;
+
+ switch (f_dfu->dfu_state) {
+ case DFU_STATE_dfuDNLOAD_SYNC:
+ case DFU_STATE_dfuDNBUSY:
+ f_dfu->dfu_state = DFU_STATE_dfuDNLOAD_IDLE;
+ break;
+ case DFU_STATE_dfuMANIFEST_SYNC:
+ break;
+ default:
+ break;
+ }
+
+ /* send status response */
+ dstat->bStatus = f_dfu->dfu_status;
+ dstat->bState = f_dfu->dfu_state;
+ dstat->iString = 0;
+}
+
+static void handle_getstate(struct usb_request *req)
+{
+ struct f_dfu *f_dfu = req->context;
+
+ ((u8 *)req->buf)[0] = f_dfu->dfu_state;
+ req->actual = sizeof(u8);
+}
+
+static inline void to_dfu_mode(struct f_dfu *f_dfu)
+{
+ f_dfu->usb_function.strings = dfu_strings;
+ f_dfu->usb_function.hs_descriptors = f_dfu->function;
+}
+
+static inline void to_runtime_mode(struct f_dfu *f_dfu)
+{
+ f_dfu->usb_function.strings = NULL;
+ f_dfu->usb_function.hs_descriptors = dfu_runtime_descs;
+}
+
+static int handle_upload(struct usb_request *req, u16 len)
+{
+ struct f_dfu *f_dfu = req->context;
+
+ return dfu_read(dfu_get_entity(f_dfu->altsetting), req->buf,
+ req->length, f_dfu->blk_seq_num);
+}
+
+static int handle_dnload(struct usb_gadget *gadget, u16 len)
+{
+ struct usb_composite_dev *cdev = get_gadget_data(gadget);
+ struct usb_request *req = cdev->req;
+ struct f_dfu *f_dfu = req->context;
+
+ if (len == 0)
+ f_dfu->dfu_state = DFU_STATE_dfuMANIFEST_SYNC;
+
+ req->complete = dnload_request_complete;
+
+ return len;
+}
+
+/*-------------------------------------------------------------------------*/
+/* DFU state machine */
+static int state_app_idle(struct f_dfu *f_dfu,
+ const struct usb_ctrlrequest *ctrl,
+ struct usb_gadget *gadget,
+ struct usb_request *req)
+{
+ int value = 0;
+
+ switch (ctrl->bRequest) {
+ case USB_REQ_DFU_GETSTATUS:
+ handle_getstatus(req);
+ value = RET_STAT_LEN;
+ break;
+ case USB_REQ_DFU_GETSTATE:
+ handle_getstate(req);
+ break;
+ case USB_REQ_DFU_DETACH:
+ f_dfu->dfu_state = DFU_STATE_appDETACH;
+ to_dfu_mode(f_dfu);
+ f_dfu->dfu_state = DFU_STATE_dfuIDLE;
+ value = RET_ZLP;
+ break;
+ default:
+ value = RET_STALL;
+ break;
+ }
+
+ return value;
+}
+
+static int state_app_detach(struct f_dfu *f_dfu,
+ const struct usb_ctrlrequest *ctrl,
+ struct usb_gadget *gadget,
+ struct usb_request *req)
+{
+ int value = 0;
+
+ switch (ctrl->bRequest) {
+ case USB_REQ_DFU_GETSTATUS:
+ handle_getstatus(req);
+ value = RET_STAT_LEN;
+ break;
+ case USB_REQ_DFU_GETSTATE:
+ handle_getstate(req);
+ break;
+ default:
+ f_dfu->dfu_state = DFU_STATE_appIDLE;
+ value = RET_STALL;
+ break;
+ }
+
+ return value;
+}
+
+static int state_dfu_idle(struct f_dfu *f_dfu,
+ const struct usb_ctrlrequest *ctrl,
+ struct usb_gadget *gadget,
+ struct usb_request *req)
+{
+ u16 w_value = le16_to_cpu(ctrl->wValue);
+ u16 len = le16_to_cpu(ctrl->wLength);
+ int value = 0;
+
+ switch (ctrl->bRequest) {
+ case USB_REQ_DFU_DNLOAD:
+ if (len == 0) {
+ f_dfu->dfu_state = DFU_STATE_dfuERROR;
+ value = RET_STALL;
+ break;
+ }
+ f_dfu->dfu_state = DFU_STATE_dfuDNLOAD_SYNC;
+ f_dfu->blk_seq_num = w_value;
+ value = handle_dnload(gadget, len);
+ break;
+ case USB_REQ_DFU_UPLOAD:
+ f_dfu->dfu_state = DFU_STATE_dfuUPLOAD_IDLE;
+ f_dfu->blk_seq_num = 0;
+ value = handle_upload(req, len);
+ break;
+ case USB_REQ_DFU_ABORT:
+ /* no zlp? */
+ value = RET_ZLP;
+ break;
+ case USB_REQ_DFU_GETSTATUS:
+ handle_getstatus(req);
+ value = RET_STAT_LEN;
+ break;
+ case USB_REQ_DFU_GETSTATE:
+ handle_getstate(req);
+ break;
+ case USB_REQ_DFU_DETACH:
+ /*
+ * Proprietary extension: 'detach' from idle mode and
+ * get back to runtime mode in case of USB Reset. As
+ * much as I dislike this, we just can't use every USB
+ * bus reset to switch back to runtime mode, since at
+ * least the Linux USB stack likes to send a number of
+ * resets in a row :(
+ */
+ f_dfu->dfu_state =
+ DFU_STATE_dfuMANIFEST_WAIT_RST;
+ to_runtime_mode(f_dfu);
+ f_dfu->dfu_state = DFU_STATE_appIDLE;
+ break;
+ default:
+ f_dfu->dfu_state = DFU_STATE_dfuERROR;
+ value = RET_STALL;
+ break;
+ }
+
+ return value;
+}
+
+static int state_dfu_dnload_sync(struct f_dfu *f_dfu,
+ const struct usb_ctrlrequest *ctrl,
+ struct usb_gadget *gadget,
+ struct usb_request *req)
+{
+ int value = 0;
+
+ switch (ctrl->bRequest) {
+ case USB_REQ_DFU_GETSTATUS:
+ handle_getstatus(req);
+ value = RET_STAT_LEN;
+ break;
+ case USB_REQ_DFU_GETSTATE:
+ handle_getstate(req);
+ break;
+ default:
+ f_dfu->dfu_state = DFU_STATE_dfuERROR;
+ value = RET_STALL;
+ break;
+ }
+
+ return value;
+}
+
+static int state_dfu_dnbusy(struct f_dfu *f_dfu,
+ const struct usb_ctrlrequest *ctrl,
+ struct usb_gadget *gadget,
+ struct usb_request *req)
+{
+ int value = 0;
+
+ switch (ctrl->bRequest) {
+ case USB_REQ_DFU_GETSTATUS:
+ handle_getstatus(req);
+ value = RET_STAT_LEN;
+ break;
+ default:
+ f_dfu->dfu_state = DFU_STATE_dfuERROR;
+ value = RET_STALL;
+ break;
+ }
+
+ return value;
+}
+
+static int state_dfu_dnload_idle(struct f_dfu *f_dfu,
+ const struct usb_ctrlrequest *ctrl,
+ struct usb_gadget *gadget,
+ struct usb_request *req)
+{
+ u16 w_value = le16_to_cpu(ctrl->wValue);
+ u16 len = le16_to_cpu(ctrl->wLength);
+ int value = 0;
+
+ switch (ctrl->bRequest) {
+ case USB_REQ_DFU_DNLOAD:
+ f_dfu->dfu_state = DFU_STATE_dfuDNLOAD_SYNC;
+ f_dfu->blk_seq_num = w_value;
+ value = handle_dnload(gadget, len);
+ break;
+ case USB_REQ_DFU_ABORT:
+ f_dfu->dfu_state = DFU_STATE_dfuIDLE;
+ value = RET_ZLP;
+ break;
+ case USB_REQ_DFU_GETSTATUS:
+ handle_getstatus(req);
+ value = RET_STAT_LEN;
+ break;
+ case USB_REQ_DFU_GETSTATE:
+ handle_getstate(req);
+ break;
+ default:
+ f_dfu->dfu_state = DFU_STATE_dfuERROR;
+ value = RET_STALL;
+ break;
+ }
+
+ return value;
+}
+
+static int state_dfu_manifest_sync(struct f_dfu *f_dfu,
+ const struct usb_ctrlrequest *ctrl,
+ struct usb_gadget *gadget,
+ struct usb_request *req)
+{
+ int value = 0;
+
+ switch (ctrl->bRequest) {
+ case USB_REQ_DFU_GETSTATUS:
+ /* We're MainfestationTolerant */
+ f_dfu->dfu_state = DFU_STATE_dfuIDLE;
+ handle_getstatus(req);
+ f_dfu->blk_seq_num = 0;
+ value = RET_STAT_LEN;
+ break;
+ case USB_REQ_DFU_GETSTATE:
+ handle_getstate(req);
+ break;
+ default:
+ f_dfu->dfu_state = DFU_STATE_dfuERROR;
+ value = RET_STALL;
+ break;
+ }
+
+ return value;
+}
+
+static int state_dfu_upload_idle(struct f_dfu *f_dfu,
+ const struct usb_ctrlrequest *ctrl,
+ struct usb_gadget *gadget,
+ struct usb_request *req)
+{
+ u16 w_value = le16_to_cpu(ctrl->wValue);
+ u16 len = le16_to_cpu(ctrl->wLength);
+ int value = 0;
+
+ switch (ctrl->bRequest) {
+ case USB_REQ_DFU_UPLOAD:
+ /* state transition if less data then requested */
+ f_dfu->blk_seq_num = w_value;
+ value = handle_upload(req, len);
+ if (value >= 0 && value < len)
+ f_dfu->dfu_state = DFU_STATE_dfuIDLE;
+ break;
+ case USB_REQ_DFU_ABORT:
+ f_dfu->dfu_state = DFU_STATE_dfuIDLE;
+ /* no zlp? */
+ value = RET_ZLP;
+ break;
+ case USB_REQ_DFU_GETSTATUS:
+ handle_getstatus(req);
+ value = RET_STAT_LEN;
+ break;
+ case USB_REQ_DFU_GETSTATE:
+ handle_getstate(req);
+ break;
+ default:
+ f_dfu->dfu_state = DFU_STATE_dfuERROR;
+ value = RET_STALL;
+ break;
+ }
+
+ return value;
+}
+
+static int state_dfu_error(struct f_dfu *f_dfu,
+ const struct usb_ctrlrequest *ctrl,
+ struct usb_gadget *gadget,
+ struct usb_request *req)
+{
+ int value = 0;
+
+ switch (ctrl->bRequest) {
+ case USB_REQ_DFU_GETSTATUS:
+ handle_getstatus(req);
+ value = RET_STAT_LEN;
+ break;
+ case USB_REQ_DFU_GETSTATE:
+ handle_getstate(req);
+ break;
+ case USB_REQ_DFU_CLRSTATUS:
+ f_dfu->dfu_state = DFU_STATE_dfuIDLE;
+ f_dfu->dfu_status = DFU_STATUS_OK;
+ /* no zlp? */
+ value = RET_ZLP;
+ break;
+ default:
+ f_dfu->dfu_state = DFU_STATE_dfuERROR;
+ value = RET_STALL;
+ break;
+ }
+
+ return value;
+}
+
+static dfu_state_fn dfu_state[] = {
+ state_app_idle, /* DFU_STATE_appIDLE */
+ state_app_detach, /* DFU_STATE_appDETACH */
+ state_dfu_idle, /* DFU_STATE_dfuIDLE */
+ state_dfu_dnload_sync, /* DFU_STATE_dfuDNLOAD_SYNC */
+ state_dfu_dnbusy, /* DFU_STATE_dfuDNBUSY */
+ state_dfu_dnload_idle, /* DFU_STATE_dfuDNLOAD_IDLE */
+ state_dfu_manifest_sync, /* DFU_STATE_dfuMANIFEST_SYNC */
+ NULL, /* DFU_STATE_dfuMANIFEST */
+ NULL, /* DFU_STATE_dfuMANIFEST_WAIT_RST */
+ state_dfu_upload_idle, /* DFU_STATE_dfuUPLOAD_IDLE */
+ state_dfu_error /* DFU_STATE_dfuERROR */
+};
+
+static int
+dfu_handle(struct usb_function *f, const struct usb_ctrlrequest *ctrl)
+{
+ struct usb_gadget *gadget = f->config->cdev->gadget;
+ struct usb_request *req = f->config->cdev->req;
+ struct f_dfu *f_dfu = f->config->cdev->req->context;
+ u16 len = le16_to_cpu(ctrl->wLength);
+ u16 w_value = le16_to_cpu(ctrl->wValue);
+ int value = 0;
+ u8 req_type = ctrl->bRequestType & USB_TYPE_MASK;
+
+ debug("w_value: 0x%x len: 0x%x\n", w_value, len);
+ debug("req_type: 0x%x ctrl->bRequest: 0x%x f_dfu->dfu_state: 0x%x\n",
+ req_type, ctrl->bRequest, f_dfu->dfu_state);
+
+ if (req_type == USB_TYPE_STANDARD) {
+ if (ctrl->bRequest == USB_REQ_GET_DESCRIPTOR &&
+ (w_value >> 8) == DFU_DT_FUNC) {
+ value = min(len, (u16) sizeof(dfu_func));
+ memcpy(req->buf, &dfu_func, value);
+ }
+ } else /* DFU specific request */
+ value = dfu_state[f_dfu->dfu_state] (f_dfu, ctrl, gadget, req);
+
+ if (value >= 0) {
+ req->length = value;
+ req->zero = value < len;
+ value = usb_ep_queue(gadget->ep0, req, 0);
+ if (value < 0) {
+ debug("ep_queue --> %d\n", value);
+ req->status = 0;
+ }
+ }
+
+ return value;
+}
+
+/*-------------------------------------------------------------------------*/
+
+static int
+dfu_prepare_strings(struct f_dfu *f_dfu, int n)
+{
+ struct dfu_entity *de = NULL;
+ int i = 0;
+
+ f_dfu->strings = calloc(sizeof(struct usb_string), n + 1);
+ if (!f_dfu->strings)
+ goto enomem;
+
+ for (i = 0; i < n; ++i) {
+ de = dfu_get_entity(i);
+ f_dfu->strings[i].s = de->name;
+ }
+
+ f_dfu->strings[i].id = 0;
+ f_dfu->strings[i].s = NULL;
+
+ return 0;
+
+enomem:
+ while (i)
+ f_dfu->strings[--i].s = NULL;
+
+ free(f_dfu->strings);
+
+ return -ENOMEM;
+}
+
+static int dfu_prepare_function(struct f_dfu *f_dfu, int n)
+{
+ struct usb_interface_descriptor *d;
+ int i = 0;
+
+ f_dfu->function = calloc(sizeof(struct usb_descriptor_header *), n);
+ if (!f_dfu->function)
+ goto enomem;
+
+ for (i = 0; i < n; ++i) {
+ d = calloc(sizeof(*d), 1);
+ if (!d)
+ goto enomem;
+
+ d->bLength = sizeof(*d);
+ d->bDescriptorType = USB_DT_INTERFACE;
+ d->bAlternateSetting = i;
+ d->bNumEndpoints = 0;
+ d->bInterfaceClass = USB_CLASS_APP_SPEC;
+ d->bInterfaceSubClass = 1;
+ d->bInterfaceProtocol = 2;
+
+ f_dfu->function[i] = (struct usb_descriptor_header *)d;
+ }
+ f_dfu->function[i] = NULL;
+
+ return 0;
+
+enomem:
+ while (i) {
+ free(f_dfu->function[--i]);
+ f_dfu->function[i] = NULL;
+ }
+ free(f_dfu->function);
+
+ return -ENOMEM;
+}
+
+static int dfu_bind(struct usb_configuration *c, struct usb_function *f)
+{
+ struct usb_composite_dev *cdev = c->cdev;
+ struct f_dfu *f_dfu = func_to_dfu(f);
+ int alt_num = dfu_get_alt_number();
+ int rv, id, i;
+
+ id = usb_interface_id(c, f);
+ if (id < 0)
+ return id;
+ dfu_intf_runtime.bInterfaceNumber = id;
+
+ f_dfu->dfu_state = DFU_STATE_appIDLE;
+ f_dfu->dfu_status = DFU_STATUS_OK;
+
+ rv = dfu_prepare_function(f_dfu, alt_num);
+ if (rv)
+ goto error;
+
+ rv = dfu_prepare_strings(f_dfu, alt_num);
+ if (rv)
+ goto error;
+ for (i = 0; i < alt_num; i++) {
+ id = usb_string_id(cdev);
+ if (id < 0)
+ return id;
+ f_dfu->strings[i].id = id;
+ ((struct usb_interface_descriptor *)f_dfu->function[i])
+ ->iInterface = id;
+ }
+
+ stringtab_dfu.strings = f_dfu->strings;
+
+ cdev->req->context = f_dfu;
+
+error:
+ return rv;
+}
+
+static void dfu_unbind(struct usb_configuration *c, struct usb_function *f)
+{
+ struct f_dfu *f_dfu = func_to_dfu(f);
+ int alt_num = dfu_get_alt_number();
+ int i;
+
+ if (f_dfu->strings) {
+ i = alt_num;
+ while (i)
+ f_dfu->strings[--i].s = NULL;
+
+ free(f_dfu->strings);
+ }
+
+ if (f_dfu->function) {
+ i = alt_num;
+ while (i) {
+ free(f_dfu->function[--i]);
+ f_dfu->function[i] = NULL;
+ }
+ free(f_dfu->function);
+ }
+
+ free(f_dfu);
+}
+
+static int dfu_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
+{
+ struct f_dfu *f_dfu = func_to_dfu(f);
+
+ debug("%s: intf:%d alt:%d\n", __func__, intf, alt);
+
+ f_dfu->altsetting = alt;
+
+ return 0;
+}
+
+/* TODO: is this really what we need here? */
+static void dfu_disable(struct usb_function *f)
+{
+ struct f_dfu *f_dfu = func_to_dfu(f);
+ if (f_dfu->config == 0)
+ return;
+
+ debug("%s: reset config\n", __func__);
+
+ f_dfu->config = 0;
+}
+
+static int dfu_bind_config(struct usb_configuration *c)
+{
+ struct f_dfu *f_dfu;
+ int status;
+
+ f_dfu = calloc(sizeof(*f_dfu), 1);
+ if (!f_dfu)
+ return -ENOMEM;
+ f_dfu->usb_function.name = "dfu";
+ f_dfu->usb_function.hs_descriptors = dfu_runtime_descs;
+ f_dfu->usb_function.bind = dfu_bind;
+ f_dfu->usb_function.unbind = dfu_unbind;
+ f_dfu->usb_function.set_alt = dfu_set_alt;
+ f_dfu->usb_function.disable = dfu_disable;
+ f_dfu->usb_function.strings = dfu_generic_strings,
+ f_dfu->usb_function.setup = dfu_handle,
+
+ status = usb_add_function(c, &f_dfu->usb_function);
+ if (status)
+ free(f_dfu);
+
+ return status;
+}
+
+int dfu_add(struct usb_configuration *c)
+{
+ int id;
+
+ id = usb_string_id(c->cdev);
+ if (id < 0)
+ return id;
+ strings_dfu_generic[0].id = id;
+ dfu_intf_runtime.iInterface = id;
+
+ debug("%s: cdev: 0x%p gadget:0x%p gadget->ep0: 0x%p\n", __func__,
+ c->cdev, c->cdev->gadget, c->cdev->gadget->ep0);
+
+ return dfu_bind_config(c);
+}
diff --git a/drivers/usb/gadget/f_dfu.h b/drivers/usb/gadget/f_dfu.h
new file mode 100644
index 0000000000..023e1adacc
--- /dev/null
+++ b/drivers/usb/gadget/f_dfu.h
@@ -0,0 +1,100 @@
+/*
+ * f_dfu.h -- Device Firmware Update gadget
+ *
+ * Copyright (C) 2011-2012 Samsung Electronics
+ * author: Andrzej Pietrasiewicz <andrzej.p@samsung.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 of the License, or
+ * (at your option) any later version.
+ *
+ * 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
+ */
+
+#ifndef __F_DFU_H_
+#define __F_DFU_H_
+
+#include <linux/compiler.h>
+#include <linux/usb/composite.h>
+
+#define DFU_CONFIG_VAL 1
+#define DFU_DT_FUNC 0x21
+
+#define DFU_BIT_WILL_DETACH (0x1 << 3)
+#define DFU_BIT_MANIFESTATION_TOLERANT (0x1 << 2)
+#define DFU_BIT_CAN_UPLOAD (0x1 << 1)
+#define DFU_BIT_CAN_DNLOAD 0x1
+
+/* big enough to hold our biggest descriptor */
+#define DFU_USB_BUFSIZ 4096
+
+#define USB_REQ_DFU_DETACH 0x00
+#define USB_REQ_DFU_DNLOAD 0x01
+#define USB_REQ_DFU_UPLOAD 0x02
+#define USB_REQ_DFU_GETSTATUS 0x03
+#define USB_REQ_DFU_CLRSTATUS 0x04
+#define USB_REQ_DFU_GETSTATE 0x05
+#define USB_REQ_DFU_ABORT 0x06
+
+#define DFU_STATUS_OK 0x00
+#define DFU_STATUS_errTARGET 0x01
+#define DFU_STATUS_errFILE 0x02
+#define DFU_STATUS_errWRITE 0x03
+#define DFU_STATUS_errERASE 0x04
+#define DFU_STATUS_errCHECK_ERASED 0x05
+#define DFU_STATUS_errPROG 0x06
+#define DFU_STATUS_errVERIFY 0x07
+#define DFU_STATUS_errADDRESS 0x08
+#define DFU_STATUS_errNOTDONE 0x09
+#define DFU_STATUS_errFIRMWARE 0x0a
+#define DFU_STATUS_errVENDOR 0x0b
+#define DFU_STATUS_errUSBR 0x0c
+#define DFU_STATUS_errPOR 0x0d
+#define DFU_STATUS_errUNKNOWN 0x0e
+#define DFU_STATUS_errSTALLEDPKT 0x0f
+
+#define RET_STALL -1
+#define RET_ZLP 0
+#define RET_STAT_LEN 6
+
+enum dfu_state {
+ DFU_STATE_appIDLE = 0,
+ DFU_STATE_appDETACH = 1,
+ DFU_STATE_dfuIDLE = 2,
+ DFU_STATE_dfuDNLOAD_SYNC = 3,
+ DFU_STATE_dfuDNBUSY = 4,
+ DFU_STATE_dfuDNLOAD_IDLE = 5,
+ DFU_STATE_dfuMANIFEST_SYNC = 6,
+ DFU_STATE_dfuMANIFEST = 7,
+ DFU_STATE_dfuMANIFEST_WAIT_RST = 8,
+ DFU_STATE_dfuUPLOAD_IDLE = 9,
+ DFU_STATE_dfuERROR = 10,
+};
+
+struct dfu_status {
+ __u8 bStatus;
+ __u8 bwPollTimeout[3];
+ __u8 bState;
+ __u8 iString;
+} __packed;
+
+struct dfu_function_descriptor {
+ __u8 bLength;
+ __u8 bDescriptorType;
+ __u8 bmAttributes;
+ __le16 wDetachTimeOut;
+ __le16 wTransferSize;
+ __le16 bcdDFUVersion;
+} __packed;
+
+/* configuration-specific linkup */
+int dfu_add(struct usb_configuration *c);
+#endif /* __F_DFU_H_ */
diff --git a/drivers/usb/gadget/g_dnl.c b/drivers/usb/gadget/g_dnl.c
new file mode 100644
index 0000000000..7d87050df3
--- /dev/null
+++ b/drivers/usb/gadget/g_dnl.c
@@ -0,0 +1,202 @@
+/*
+ * g_dnl.c -- USB Downloader Gadget
+ *
+ * Copyright (C) 2012 Samsung Electronics
+ * Lukasz Majewski <l.majewski@samsung.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 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#include <errno.h>
+#include <common.h>
+#include <malloc.h>
+
+#include <mmc.h>
+#include <part.h>
+
+#include <g_dnl.h>
+#include "f_dfu.h"
+
+#include "gadget_chips.h"
+#include "composite.c"
+
+/*
+ * One needs to define the following:
+ * CONFIG_G_DNL_VENDOR_NUM
+ * CONFIG_G_DNL_PRODUCT_NUM
+ * CONFIG_G_DNL_MANUFACTURER
+ * at e.g. ./include/configs/<board>.h
+ */
+
+#define STRING_MANUFACTURER 25
+#define STRING_PRODUCT 2
+#define STRING_USBDOWN 2
+#define CONFIG_USBDOWNLOADER 2
+
+#define DRIVER_VERSION "usb_dnl 2.0"
+
+static const char shortname[] = "usb_dnl_";
+static const char product[] = "USB download gadget";
+static const char manufacturer[] = CONFIG_G_DNL_MANUFACTURER;
+
+static struct usb_device_descriptor device_desc = {
+ .bLength = sizeof device_desc,
+ .bDescriptorType = USB_DT_DEVICE,
+
+ .bcdUSB = __constant_cpu_to_le16(0x0200),
+ .bDeviceClass = USB_CLASS_COMM,
+ .bDeviceSubClass = 0x02, /*0x02:CDC-modem , 0x00:CDC-serial*/
+
+ .idVendor = __constant_cpu_to_le16(CONFIG_G_DNL_VENDOR_NUM),
+ .idProduct = __constant_cpu_to_le16(CONFIG_G_DNL_PRODUCT_NUM),
+ .iProduct = STRING_PRODUCT,
+ .bNumConfigurations = 1,
+};
+
+/* static strings, in UTF-8 */
+static struct usb_string g_dnl_string_defs[] = {
+ { 0, manufacturer, },
+ { 1, product, },
+};
+
+static struct usb_gadget_strings g_dnl_string_tab = {
+ .language = 0x0409, /* en-us */
+ .strings = g_dnl_string_defs,
+};
+
+static struct usb_gadget_strings *g_dnl_composite_strings[] = {
+ &g_dnl_string_tab,
+ NULL,
+};
+
+static int g_dnl_unbind(struct usb_composite_dev *cdev)
+{
+ debug("%s\n", __func__);
+ return 0;
+}
+
+static int g_dnl_do_config(struct usb_configuration *c)
+{
+ const char *s = c->cdev->driver->name;
+ int ret = -1;
+
+ debug("%s: configuration: 0x%p composite dev: 0x%p\n",
+ __func__, c, c->cdev);
+
+ printf("GADGET DRIVER: %s\n", s);
+ if (!strcmp(s, "usb_dnl_dfu"))
+ ret = dfu_add(c);
+
+ return ret;
+}
+
+static int g_dnl_config_register(struct usb_composite_dev *cdev)
+{
+ static struct usb_configuration config = {
+ .label = "usb_dnload",
+ .bmAttributes = USB_CONFIG_ATT_ONE | USB_CONFIG_ATT_SELFPOWER,
+ .bConfigurationValue = CONFIG_USBDOWNLOADER,
+ .iConfiguration = STRING_USBDOWN,
+
+ .bind = g_dnl_do_config,
+ };
+
+ return usb_add_config(cdev, &config);
+}
+
+static int g_dnl_bind(struct usb_composite_dev *cdev)
+{
+ struct usb_gadget *gadget = cdev->gadget;
+ int id, ret;
+ int gcnum;
+
+ debug("%s: gadget: 0x%p cdev: 0x%p\n", __func__, gadget, cdev);
+
+ id = usb_string_id(cdev);
+
+ if (id < 0)
+ return id;
+ g_dnl_string_defs[0].id = id;
+ device_desc.iManufacturer = id;
+
+ id = usb_string_id(cdev);
+ if (id < 0)
+ return id;
+
+ g_dnl_string_defs[1].id = id;
+ device_desc.iProduct = id;
+
+ ret = g_dnl_config_register(cdev);
+ if (ret)
+ goto error;
+
+ gcnum = usb_gadget_controller_number(gadget);
+
+ debug("gcnum: %d\n", gcnum);
+ if (gcnum >= 0)
+ device_desc.bcdDevice = cpu_to_le16(0x0200 + gcnum);
+ else {
+ debug("%s: controller '%s' not recognized\n",
+ shortname, gadget->name);
+ device_desc.bcdDevice = __constant_cpu_to_le16(0x9999);
+ }
+
+ return 0;
+
+ error:
+ g_dnl_unbind(cdev);
+ return -ENOMEM;
+}
+
+static struct usb_composite_driver g_dnl_driver = {
+ .name = NULL,
+ .dev = &device_desc,
+ .strings = g_dnl_composite_strings,
+
+ .bind = g_dnl_bind,
+ .unbind = g_dnl_unbind,
+};
+
+int g_dnl_register(const char *type)
+{
+ /* We only allow "dfu" atm, so 3 should be enough */
+ static char name[sizeof(shortname) + 3];
+ int ret;
+
+ if (!strcmp(type, "dfu")) {
+ strcpy(name, shortname);
+ strcat(name, type);
+ } else {
+ printf("%s: unknown command: %s\n", __func__, type);
+ return -EINVAL;
+ }
+
+ g_dnl_driver.name = name;
+
+ debug("%s: g_dnl_driver.name: %s\n", __func__, g_dnl_driver.name);
+ ret = usb_composite_register(&g_dnl_driver);
+
+ if (ret) {
+ printf("%s: failed!, error: %d\n", __func__, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+void g_dnl_unregister(void)
+{
+ usb_composite_unregister(&g_dnl_driver);
+}
diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c
index 2a82a29125..392e286224 100644
--- a/drivers/usb/host/ehci-hcd.c
+++ b/drivers/usb/host/ehci-hcd.c
@@ -22,6 +22,7 @@
*/
#include <common.h>
#include <asm/byteorder.h>
+#include <asm/unaligned.h>
#include <usb.h>
#include <asm/io.h>
#include <malloc.h>
@@ -163,7 +164,7 @@ static int ehci_reset(void)
#ifdef CONFIG_USB_EHCI_TXFIFO_THRESH
cmd = ehci_readl(&hcor->or_txfilltuning);
- cmd &= ~TXFIFO_THRESH(0x3f);
+ cmd &= ~TXFIFO_THRESH_MASK;
cmd |= TXFIFO_THRESH(CONFIG_USB_EHCI_TXFIFO_THRESH);
ehci_writel(&hcor->or_txfilltuning, cmd);
#endif
@@ -183,10 +184,10 @@ static int ehci_td_buffer(struct qTD *td, void *buf, size_t sz)
flush_dcache_range(addr, ALIGN(addr + sz, ARCH_DMA_MINALIGN));
idx = 0;
- while (idx < 5) {
+ while (idx < QT_BUFFER_CNT) {
td->qt_buffer[idx] = cpu_to_hc32(addr);
td->qt_buffer_hi[idx] = 0;
- next = (addr + 4096) & ~4095;
+ next = (addr + EHCI_PAGE_SIZE) & ~(EHCI_PAGE_SIZE - 1);
delta = next - addr;
if (delta >= sz)
break;
@@ -195,7 +196,7 @@ static int ehci_td_buffer(struct qTD *td, void *buf, size_t sz)
idx++;
}
- if (idx == 5) {
+ if (idx == QT_BUFFER_CNT) {
printf("out of buffer pointers (%u bytes left)\n", sz);
return -1;
}
@@ -208,13 +209,14 @@ ehci_submit_async(struct usb_device *dev, unsigned long pipe, void *buffer,
int length, struct devrequest *req)
{
ALLOC_ALIGN_BUFFER(struct QH, qh, 1, USB_DMA_MINALIGN);
- ALLOC_ALIGN_BUFFER(struct qTD, qtd, 3, USB_DMA_MINALIGN);
+ struct qTD *qtd;
+ int qtd_count = 0;
int qtd_counter = 0;
volatile struct qTD *vtd;
unsigned long ts;
uint32_t *tdp;
- uint32_t endpt, token, usbsts;
+ uint32_t endpt, maxpacket, token, usbsts;
uint32_t c, toggle;
uint32_t cmd;
int timeout;
@@ -229,8 +231,73 @@ ehci_submit_async(struct usb_device *dev, unsigned long pipe, void *buffer,
le16_to_cpu(req->value), le16_to_cpu(req->value),
le16_to_cpu(req->index));
+#define PKT_ALIGN 512
+ /*
+ * The USB transfer is split into qTD transfers. Eeach qTD transfer is
+ * described by a transfer descriptor (the qTD). The qTDs form a linked
+ * list with a queue head (QH).
+ *
+ * Each qTD transfer starts with a new USB packet, i.e. a packet cannot
+ * have its beginning in a qTD transfer and its end in the following
+ * one, so the qTD transfer lengths have to be chosen accordingly.
+ *
+ * Each qTD transfer uses up to QT_BUFFER_CNT data buffers, mapped to
+ * single pages. The first data buffer can start at any offset within a
+ * page (not considering the cache-line alignment issues), while the
+ * following buffers must be page-aligned. There is no alignment
+ * constraint on the size of a qTD transfer.
+ */
+ if (req != NULL)
+ /* 1 qTD will be needed for SETUP, and 1 for ACK. */
+ qtd_count += 1 + 1;
+ if (length > 0 || req == NULL) {
+ /*
+ * Determine the qTD transfer size that will be used for the
+ * data payload (not considering the first qTD transfer, which
+ * may be longer or shorter, and the final one, which may be
+ * shorter).
+ *
+ * In order to keep each packet within a qTD transfer, the qTD
+ * transfer size is aligned to PKT_ALIGN, which is a multiple of
+ * wMaxPacketSize (except in some cases for interrupt transfers,
+ * see comment in submit_int_msg()).
+ *
+ * By default, i.e. if the input buffer is aligned to PKT_ALIGN,
+ * QT_BUFFER_CNT full pages will be used.
+ */
+ int xfr_sz = QT_BUFFER_CNT;
+ /*
+ * However, if the input buffer is not aligned to PKT_ALIGN, the
+ * qTD transfer size will be one page shorter, and the first qTD
+ * data buffer of each transfer will be page-unaligned.
+ */
+ if ((uint32_t)buffer & (PKT_ALIGN - 1))
+ xfr_sz--;
+ /* Convert the qTD transfer size to bytes. */
+ xfr_sz *= EHCI_PAGE_SIZE;
+ /*
+ * Approximate by excess the number of qTDs that will be
+ * required for the data payload. The exact formula is way more
+ * complicated and saves at most 2 qTDs, i.e. a total of 128
+ * bytes.
+ */
+ qtd_count += 2 + length / xfr_sz;
+ }
+/*
+ * Threshold value based on the worst-case total size of the allocated qTDs for
+ * a mass-storage transfer of 65535 blocks of 512 bytes.
+ */
+#if CONFIG_SYS_MALLOC_LEN <= 64 + 128 * 1024
+#warning CONFIG_SYS_MALLOC_LEN may be too small for EHCI
+#endif
+ qtd = memalign(USB_DMA_MINALIGN, qtd_count * sizeof(struct qTD));
+ if (qtd == NULL) {
+ printf("unable to allocate TDs\n");
+ return -1;
+ }
+
memset(qh, 0, sizeof(struct QH));
- memset(qtd, 0, 3 * sizeof(*qtd));
+ memset(qtd, 0, qtd_count * sizeof(*qtd));
toggle = usb_gettoggle(dev, usb_pipeendpoint(pipe), usb_pipeout(pipe));
@@ -245,20 +312,18 @@ ehci_submit_async(struct usb_device *dev, unsigned long pipe, void *buffer,
* - qh_overlay.qt_altnext
*/
qh->qh_link = cpu_to_hc32((uint32_t)qh_list | QH_LINK_TYPE_QH);
- c = (usb_pipespeed(pipe) != USB_SPEED_HIGH &&
- usb_pipeendpoint(pipe) == 0) ? 1 : 0;
- endpt = (8 << 28) |
- (c << 27) |
- (usb_maxpacket(dev, pipe) << 16) |
- (0 << 15) |
- (1 << 14) |
- (usb_pipespeed(pipe) << 12) |
- (usb_pipeendpoint(pipe) << 8) |
- (0 << 7) | (usb_pipedevice(pipe) << 0);
+ c = usb_pipespeed(pipe) != USB_SPEED_HIGH && !usb_pipeendpoint(pipe);
+ maxpacket = usb_maxpacket(dev, pipe);
+ endpt = QH_ENDPT1_RL(8) | QH_ENDPT1_C(c) |
+ QH_ENDPT1_MAXPKTLEN(maxpacket) | QH_ENDPT1_H(0) |
+ QH_ENDPT1_DTC(QH_ENDPT1_DTC_DT_FROM_QTD) |
+ QH_ENDPT1_EPS(usb_pipespeed(pipe)) |
+ QH_ENDPT1_ENDPT(usb_pipeendpoint(pipe)) | QH_ENDPT1_I(0) |
+ QH_ENDPT1_DEVADDR(usb_pipedevice(pipe));
qh->qh_endpt1 = cpu_to_hc32(endpt);
- endpt = (1 << 30) |
- (dev->portnr << 23) |
- (dev->parent->devnum << 16) | (0 << 8) | (0 << 0);
+ endpt = QH_ENDPT2_MULT(1) | QH_ENDPT2_PORTNUM(dev->portnr) |
+ QH_ENDPT2_HUBADDR(dev->parent->devnum) |
+ QH_ENDPT2_UFCMASK(0) | QH_ENDPT2_UFSMASK(0);
qh->qh_endpt2 = cpu_to_hc32(endpt);
qh->qh_overlay.qt_next = cpu_to_hc32(QT_NEXT_TERMINATE);
@@ -276,12 +341,13 @@ ehci_submit_async(struct usb_device *dev, unsigned long pipe, void *buffer,
*/
qtd[qtd_counter].qt_next = cpu_to_hc32(QT_NEXT_TERMINATE);
qtd[qtd_counter].qt_altnext = cpu_to_hc32(QT_NEXT_TERMINATE);
- token = (0 << 31) |
- (sizeof(*req) << 16) |
- (0 << 15) | (0 << 12) | (3 << 10) | (2 << 8) | (0x80 << 0);
+ token = QT_TOKEN_DT(0) | QT_TOKEN_TOTALBYTES(sizeof(*req)) |
+ QT_TOKEN_IOC(0) | QT_TOKEN_CPAGE(0) | QT_TOKEN_CERR(3) |
+ QT_TOKEN_PID(QT_TOKEN_PID_SETUP) |
+ QT_TOKEN_STATUS(QT_TOKEN_STATUS_ACTIVE);
qtd[qtd_counter].qt_token = cpu_to_hc32(token);
- if (ehci_td_buffer(&qtd[qtd_counter], req, sizeof(*req)) != 0) {
- printf("unable construct SETUP td\n");
+ if (ehci_td_buffer(&qtd[qtd_counter], req, sizeof(*req))) {
+ printf("unable to construct SETUP TD\n");
goto fail;
}
/* Update previous qTD! */
@@ -291,31 +357,71 @@ ehci_submit_async(struct usb_device *dev, unsigned long pipe, void *buffer,
}
if (length > 0 || req == NULL) {
- /*
- * Setup request qTD (3.5 in ehci-r10.pdf)
- *
- * qt_next ................ 03-00 H
- * qt_altnext ............. 07-04 H
- * qt_token ............... 0B-08 H
- *
- * [ buffer, buffer_hi ] loaded with "buffer".
- */
- qtd[qtd_counter].qt_next = cpu_to_hc32(QT_NEXT_TERMINATE);
- qtd[qtd_counter].qt_altnext = cpu_to_hc32(QT_NEXT_TERMINATE);
- token = (toggle << 31) |
- (length << 16) |
- ((req == NULL ? 1 : 0) << 15) |
- (0 << 12) |
- (3 << 10) |
- ((usb_pipein(pipe) ? 1 : 0) << 8) | (0x80 << 0);
- qtd[qtd_counter].qt_token = cpu_to_hc32(token);
- if (ehci_td_buffer(&qtd[qtd_counter], buffer, length) != 0) {
- printf("unable construct DATA td\n");
- goto fail;
- }
- /* Update previous qTD! */
- *tdp = cpu_to_hc32((uint32_t)&qtd[qtd_counter]);
- tdp = &qtd[qtd_counter++].qt_next;
+ uint8_t *buf_ptr = buffer;
+ int left_length = length;
+
+ do {
+ /*
+ * Determine the size of this qTD transfer. By default,
+ * QT_BUFFER_CNT full pages can be used.
+ */
+ int xfr_bytes = QT_BUFFER_CNT * EHCI_PAGE_SIZE;
+ /*
+ * However, if the input buffer is not page-aligned, the
+ * portion of the first page before the buffer start
+ * offset within that page is unusable.
+ */
+ xfr_bytes -= (uint32_t)buf_ptr & (EHCI_PAGE_SIZE - 1);
+ /*
+ * In order to keep each packet within a qTD transfer,
+ * align the qTD transfer size to PKT_ALIGN.
+ */
+ xfr_bytes &= ~(PKT_ALIGN - 1);
+ /*
+ * This transfer may be shorter than the available qTD
+ * transfer size that has just been computed.
+ */
+ xfr_bytes = min(xfr_bytes, left_length);
+
+ /*
+ * Setup request qTD (3.5 in ehci-r10.pdf)
+ *
+ * qt_next ................ 03-00 H
+ * qt_altnext ............. 07-04 H
+ * qt_token ............... 0B-08 H
+ *
+ * [ buffer, buffer_hi ] loaded with "buffer".
+ */
+ qtd[qtd_counter].qt_next =
+ cpu_to_hc32(QT_NEXT_TERMINATE);
+ qtd[qtd_counter].qt_altnext =
+ cpu_to_hc32(QT_NEXT_TERMINATE);
+ token = QT_TOKEN_DT(toggle) |
+ QT_TOKEN_TOTALBYTES(xfr_bytes) |
+ QT_TOKEN_IOC(req == NULL) | QT_TOKEN_CPAGE(0) |
+ QT_TOKEN_CERR(3) |
+ QT_TOKEN_PID(usb_pipein(pipe) ?
+ QT_TOKEN_PID_IN : QT_TOKEN_PID_OUT) |
+ QT_TOKEN_STATUS(QT_TOKEN_STATUS_ACTIVE);
+ qtd[qtd_counter].qt_token = cpu_to_hc32(token);
+ if (ehci_td_buffer(&qtd[qtd_counter], buf_ptr,
+ xfr_bytes)) {
+ printf("unable to construct DATA TD\n");
+ goto fail;
+ }
+ /* Update previous qTD! */
+ *tdp = cpu_to_hc32((uint32_t)&qtd[qtd_counter]);
+ tdp = &qtd[qtd_counter++].qt_next;
+ /*
+ * Data toggle has to be adjusted since the qTD transfer
+ * size is not always an even multiple of
+ * wMaxPacketSize.
+ */
+ if ((xfr_bytes / maxpacket) & 1)
+ toggle ^= 1;
+ buf_ptr += xfr_bytes;
+ left_length -= xfr_bytes;
+ } while (left_length > 0);
}
if (req != NULL) {
@@ -328,12 +434,11 @@ ehci_submit_async(struct usb_device *dev, unsigned long pipe, void *buffer,
*/
qtd[qtd_counter].qt_next = cpu_to_hc32(QT_NEXT_TERMINATE);
qtd[qtd_counter].qt_altnext = cpu_to_hc32(QT_NEXT_TERMINATE);
- token = (toggle << 31) |
- (0 << 16) |
- (1 << 15) |
- (0 << 12) |
- (3 << 10) |
- ((usb_pipein(pipe) ? 0 : 1) << 8) | (0x80 << 0);
+ token = QT_TOKEN_DT(1) | QT_TOKEN_TOTALBYTES(0) |
+ QT_TOKEN_IOC(1) | QT_TOKEN_CPAGE(0) | QT_TOKEN_CERR(3) |
+ QT_TOKEN_PID(usb_pipein(pipe) ?
+ QT_TOKEN_PID_OUT : QT_TOKEN_PID_IN) |
+ QT_TOKEN_STATUS(QT_TOKEN_STATUS_ACTIVE);
qtd[qtd_counter].qt_token = cpu_to_hc32(token);
/* Update previous qTD! */
*tdp = cpu_to_hc32((uint32_t)&qtd[qtd_counter]);
@@ -346,7 +451,8 @@ ehci_submit_async(struct usb_device *dev, unsigned long pipe, void *buffer,
flush_dcache_range((uint32_t)qh_list,
ALIGN_END_ADDR(struct QH, qh_list, 1));
flush_dcache_range((uint32_t)qh, ALIGN_END_ADDR(struct QH, qh, 1));
- flush_dcache_range((uint32_t)qtd, ALIGN_END_ADDR(struct qTD, qtd, 3));
+ flush_dcache_range((uint32_t)qtd,
+ ALIGN_END_ADDR(struct qTD, qtd, qtd_count));
/* Set async. queue head pointer. */
ehci_writel(&hcor->or_asynclistaddr, (uint32_t)qh_list);
@@ -359,10 +465,10 @@ ehci_submit_async(struct usb_device *dev, unsigned long pipe, void *buffer,
cmd |= CMD_ASE;
ehci_writel(&hcor->or_usbcmd, cmd);
- ret = handshake((uint32_t *)&hcor->or_usbsts, STD_ASS, STD_ASS,
+ ret = handshake((uint32_t *)&hcor->or_usbsts, STS_ASS, STS_ASS,
100 * 1000);
if (ret < 0) {
- printf("EHCI fail timeout STD_ASS set\n");
+ printf("EHCI fail timeout STS_ASS set\n");
goto fail;
}
@@ -377,10 +483,10 @@ ehci_submit_async(struct usb_device *dev, unsigned long pipe, void *buffer,
invalidate_dcache_range((uint32_t)qh,
ALIGN_END_ADDR(struct QH, qh, 1));
invalidate_dcache_range((uint32_t)qtd,
- ALIGN_END_ADDR(struct qTD, qtd, 3));
+ ALIGN_END_ADDR(struct qTD, qtd, qtd_count));
token = hc32_to_cpu(vtd->qt_token);
- if (!(token & 0x80))
+ if (!(QT_TOKEN_GET_STATUS(token) & QT_TOKEN_STATUS_ACTIVE))
break;
WATCHDOG_RESET();
} while (get_timer(ts) < timeout);
@@ -398,50 +504,50 @@ ehci_submit_async(struct usb_device *dev, unsigned long pipe, void *buffer,
ALIGN((uint32_t)buffer + length, ARCH_DMA_MINALIGN));
/* Check that the TD processing happened */
- if (token & 0x80) {
+ if (QT_TOKEN_GET_STATUS(token) & QT_TOKEN_STATUS_ACTIVE)
printf("EHCI timed out on TD - token=%#x\n", token);
- }
/* Disable async schedule. */
cmd = ehci_readl(&hcor->or_usbcmd);
cmd &= ~CMD_ASE;
ehci_writel(&hcor->or_usbcmd, cmd);
- ret = handshake((uint32_t *)&hcor->or_usbsts, STD_ASS, 0,
+ ret = handshake((uint32_t *)&hcor->or_usbsts, STS_ASS, 0,
100 * 1000);
if (ret < 0) {
- printf("EHCI fail timeout STD_ASS reset\n");
+ printf("EHCI fail timeout STS_ASS reset\n");
goto fail;
}
token = hc32_to_cpu(qh->qh_overlay.qt_token);
- if (!(token & 0x80)) {
+ if (!(QT_TOKEN_GET_STATUS(token) & QT_TOKEN_STATUS_ACTIVE)) {
debug("TOKEN=%#x\n", token);
- switch (token & 0xfc) {
+ switch (QT_TOKEN_GET_STATUS(token) &
+ ~(QT_TOKEN_STATUS_SPLITXSTATE | QT_TOKEN_STATUS_PERR)) {
case 0:
- toggle = token >> 31;
+ toggle = QT_TOKEN_GET_DT(token);
usb_settoggle(dev, usb_pipeendpoint(pipe),
usb_pipeout(pipe), toggle);
dev->status = 0;
break;
- case 0x40:
+ case QT_TOKEN_STATUS_HALTED:
dev->status = USB_ST_STALLED;
break;
- case 0xa0:
- case 0x20:
+ case QT_TOKEN_STATUS_ACTIVE | QT_TOKEN_STATUS_DATBUFERR:
+ case QT_TOKEN_STATUS_DATBUFERR:
dev->status = USB_ST_BUF_ERR;
break;
- case 0x50:
- case 0x10:
+ case QT_TOKEN_STATUS_HALTED | QT_TOKEN_STATUS_BABBLEDET:
+ case QT_TOKEN_STATUS_BABBLEDET:
dev->status = USB_ST_BABBLE_DET;
break;
default:
dev->status = USB_ST_CRC_ERR;
- if ((token & 0x40) == 0x40)
+ if (QT_TOKEN_GET_STATUS(token) & QT_TOKEN_STATUS_HALTED)
dev->status |= USB_ST_STALLED;
break;
}
- dev->act_len = length - ((token >> 16) & 0x7fff);
+ dev->act_len = length - QT_TOKEN_GET_TOTALBYTES(token);
} else {
dev->act_len = 0;
debug("dev=%u, usbsts=%#x, p[1]=%#x, p[2]=%#x\n",
@@ -450,9 +556,11 @@ ehci_submit_async(struct usb_device *dev, unsigned long pipe, void *buffer,
ehci_readl(&hcor->or_portsc[1]));
}
+ free(qtd);
return (dev->status != USB_ST_NOT_PROC) ? 0 : -1;
fail:
+ free(qtd);
return -1;
}
@@ -499,12 +607,14 @@ ehci_submit_root(struct usb_device *dev, unsigned long pipe, void *buffer,
case USB_DT_DEVICE:
debug("USB_DT_DEVICE request\n");
srcptr = &descriptor.device;
- srclen = 0x12;
+ srclen = descriptor.device.bLength;
break;
case USB_DT_CONFIG:
debug("USB_DT_CONFIG config\n");
srcptr = &descriptor.config;
- srclen = 0x19;
+ srclen = descriptor.config.bLength +
+ descriptor.interface.bLength +
+ descriptor.endpoint.bLength;
break;
case USB_DT_STRING:
debug("USB_DT_STRING config\n");
@@ -539,7 +649,7 @@ ehci_submit_root(struct usb_device *dev, unsigned long pipe, void *buffer,
case USB_DT_HUB:
debug("USB_DT_HUB config\n");
srcptr = &descriptor.hub;
- srclen = 0x8;
+ srclen = descriptor.hub.bLength;
break;
default:
debug("unknown value %x\n", le16_to_cpu(req->value));
@@ -577,13 +687,13 @@ ehci_submit_root(struct usb_device *dev, unsigned long pipe, void *buffer,
tmpbuf[1] |= USB_PORT_STAT_POWER >> 8;
if (ehci_is_TDI()) {
- switch ((reg >> 26) & 3) {
- case 0:
+ switch (PORTSC_PSPD(reg)) {
+ case PORTSC_PSPD_FS:
break;
- case 1:
+ case PORTSC_PSPD_LS:
tmpbuf[1] |= USB_PORT_STAT_LOW_SPEED >> 8;
break;
- case 2:
+ case PORTSC_PSPD_HS:
default:
tmpbuf[1] |= USB_PORT_STAT_HIGH_SPEED >> 8;
break;
@@ -729,36 +839,40 @@ int usb_lowlevel_init(void)
uint32_t reg;
uint32_t cmd;
- if (ehci_hcd_init() != 0)
+ if (ehci_hcd_init())
return -1;
/* EHCI spec section 4.1 */
- if (ehci_reset() != 0)
+ if (ehci_reset())
return -1;
#if defined(CONFIG_EHCI_HCD_INIT_AFTER_RESET)
- if (ehci_hcd_init() != 0)
+ if (ehci_hcd_init())
return -1;
#endif
/* Set head of reclaim list */
memset(qh_list, 0, sizeof(*qh_list));
qh_list->qh_link = cpu_to_hc32((uint32_t)qh_list | QH_LINK_TYPE_QH);
- qh_list->qh_endpt1 = cpu_to_hc32((1 << 15) | (USB_SPEED_HIGH << 12));
+ qh_list->qh_endpt1 = cpu_to_hc32(QH_ENDPT1_H(1) |
+ QH_ENDPT1_EPS(USB_SPEED_HIGH));
qh_list->qh_curtd = cpu_to_hc32(QT_NEXT_TERMINATE);
qh_list->qh_overlay.qt_next = cpu_to_hc32(QT_NEXT_TERMINATE);
qh_list->qh_overlay.qt_altnext = cpu_to_hc32(QT_NEXT_TERMINATE);
- qh_list->qh_overlay.qt_token = cpu_to_hc32(0x40);
+ qh_list->qh_overlay.qt_token =
+ cpu_to_hc32(QT_TOKEN_STATUS(QT_TOKEN_STATUS_HALTED));
reg = ehci_readl(&hccr->cr_hcsparams);
descriptor.hub.bNbrPorts = HCS_N_PORTS(reg);
printf("Register %x NbrPorts %d\n", reg, descriptor.hub.bNbrPorts);
/* Port Indicators */
if (HCS_INDICATOR(reg))
- descriptor.hub.wHubCharacteristics |= 0x80;
+ put_unaligned(get_unaligned(&descriptor.hub.wHubCharacteristics)
+ | 0x80, &descriptor.hub.wHubCharacteristics);
/* Port Power Control */
if (HCS_PPC(reg))
- descriptor.hub.wHubCharacteristics |= 0x01;
+ put_unaligned(get_unaligned(&descriptor.hub.wHubCharacteristics)
+ | 0x01, &descriptor.hub.wHubCharacteristics);
/* Start the host controller. */
cmd = ehci_readl(&hcor->or_usbcmd);
@@ -808,7 +922,7 @@ submit_control_msg(struct usb_device *dev, unsigned long pipe, void *buffer,
}
if (usb_pipedevice(pipe) == rootdev) {
- if (rootdev == 0)
+ if (!rootdev)
dev->speed = USB_SPEED_HIGH;
return ehci_submit_root(dev, pipe, buffer, length, setup);
}
@@ -819,8 +933,24 @@ int
submit_int_msg(struct usb_device *dev, unsigned long pipe, void *buffer,
int length, int interval)
{
-
debug("dev=%p, pipe=%lu, buffer=%p, length=%d, interval=%d",
dev, pipe, buffer, length, interval);
+
+ /*
+ * Interrupt transfers requiring several transactions are not supported
+ * because bInterval is ignored.
+ *
+ * Also, ehci_submit_async() relies on wMaxPacketSize being a power of 2
+ * <= PKT_ALIGN if several qTDs are required, while the USB
+ * specification does not constrain this for interrupt transfers. That
+ * means that ehci_submit_async() would support interrupt transfers
+ * requiring several transactions only as long as the transfer size does
+ * not require more than a single qTD.
+ */
+ if (length > usb_maxpacket(dev, pipe)) {
+ printf("%s: Interrupt transfers requiring several transactions "
+ "are not supported.\n", __func__);
+ return -1;
+ }
return ehci_submit_async(dev, pipe, buffer, length, NULL);
}
diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h
index cc00ce428b..39acdf9656 100644
--- a/drivers/usb/host/ehci.h
+++ b/drivers/usb/host/ehci.h
@@ -68,7 +68,7 @@ struct ehci_hcor {
#define CMD_RESET (1 << 1) /* reset HC not bus */
#define CMD_RUN (1 << 0) /* start/stop HC */
uint32_t or_usbsts;
-#define STD_ASS (1 << 15)
+#define STS_ASS (1 << 15)
#define STS_HALT (1 << 12)
uint32_t or_usbintr;
#define INTR_UE (1 << 0) /* USB interrupt enable */
@@ -83,11 +83,16 @@ struct ehci_hcor {
uint32_t _reserved_0_;
uint32_t or_burstsize;
uint32_t or_txfilltuning;
+#define TXFIFO_THRESH_MASK (0x3f << 16)
#define TXFIFO_THRESH(p) ((p & 0x3f) << 16)
uint32_t _reserved_1_[6];
uint32_t or_configflag;
#define FLAG_CF (1 << 0) /* true: we'll support "high speed" */
uint32_t or_portsc[CONFIG_SYS_USB_EHCI_MAX_ROOT_PORTS];
+#define PORTSC_PSPD(x) (((x) >> 26) & 0x3)
+#define PORTSC_PSPD_FS 0x0
+#define PORTSC_PSPD_LS 0x1
+#define PORTSC_PSPD_HS 0x2
uint32_t or_systune;
} __attribute__ ((packed, aligned(4)));
@@ -171,16 +176,40 @@ struct usb_linux_config_descriptor {
/* Queue Element Transfer Descriptor (qTD). */
struct qTD {
/* this part defined by EHCI spec */
- uint32_t qt_next; /* see EHCI 3.5.1 */
+ uint32_t qt_next; /* see EHCI 3.5.1 */
#define QT_NEXT_TERMINATE 1
- uint32_t qt_altnext; /* see EHCI 3.5.2 */
- uint32_t qt_token; /* see EHCI 3.5.3 */
- uint32_t qt_buffer[5]; /* see EHCI 3.5.4 */
- uint32_t qt_buffer_hi[5]; /* Appendix B */
+ uint32_t qt_altnext; /* see EHCI 3.5.2 */
+ uint32_t qt_token; /* see EHCI 3.5.3 */
+#define QT_TOKEN_DT(x) (((x) & 0x1) << 31) /* Data Toggle */
+#define QT_TOKEN_GET_DT(x) (((x) >> 31) & 0x1)
+#define QT_TOKEN_TOTALBYTES(x) (((x) & 0x7fff) << 16) /* Total Bytes to Transfer */
+#define QT_TOKEN_GET_TOTALBYTES(x) (((x) >> 16) & 0x7fff)
+#define QT_TOKEN_IOC(x) (((x) & 0x1) << 15) /* Interrupt On Complete */
+#define QT_TOKEN_CPAGE(x) (((x) & 0x7) << 12) /* Current Page */
+#define QT_TOKEN_CERR(x) (((x) & 0x3) << 10) /* Error Counter */
+#define QT_TOKEN_PID(x) (((x) & 0x3) << 8) /* PID Code */
+#define QT_TOKEN_PID_OUT 0x0
+#define QT_TOKEN_PID_IN 0x1
+#define QT_TOKEN_PID_SETUP 0x2
+#define QT_TOKEN_STATUS(x) (((x) & 0xff) << 0) /* Status */
+#define QT_TOKEN_GET_STATUS(x) (((x) >> 0) & 0xff)
+#define QT_TOKEN_STATUS_ACTIVE 0x80
+#define QT_TOKEN_STATUS_HALTED 0x40
+#define QT_TOKEN_STATUS_DATBUFERR 0x20
+#define QT_TOKEN_STATUS_BABBLEDET 0x10
+#define QT_TOKEN_STATUS_XACTERR 0x08
+#define QT_TOKEN_STATUS_MISSEDUFRAME 0x04
+#define QT_TOKEN_STATUS_SPLITXSTATE 0x02
+#define QT_TOKEN_STATUS_PERR 0x01
+#define QT_BUFFER_CNT 5
+ uint32_t qt_buffer[QT_BUFFER_CNT]; /* see EHCI 3.5.4 */
+ uint32_t qt_buffer_hi[QT_BUFFER_CNT]; /* Appendix B */
/* pad struct for 32 byte alignment */
uint32_t unused[3];
};
+#define EHCI_PAGE_SIZE 4096
+
/* Queue Head (QH). */
struct QH {
uint32_t qh_link;
@@ -190,7 +219,26 @@ struct QH {
#define QH_LINK_TYPE_SITD 4
#define QH_LINK_TYPE_FSTN 6
uint32_t qh_endpt1;
+#define QH_ENDPT1_RL(x) (((x) & 0xf) << 28) /* NAK Count Reload */
+#define QH_ENDPT1_C(x) (((x) & 0x1) << 27) /* Control Endpoint Flag */
+#define QH_ENDPT1_MAXPKTLEN(x) (((x) & 0x7ff) << 16) /* Maximum Packet Length */
+#define QH_ENDPT1_H(x) (((x) & 0x1) << 15) /* Head of Reclamation List Flag */
+#define QH_ENDPT1_DTC(x) (((x) & 0x1) << 14) /* Data Toggle Control */
+#define QH_ENDPT1_DTC_IGNORE_QTD_TD 0x0
+#define QH_ENDPT1_DTC_DT_FROM_QTD 0x1
+#define QH_ENDPT1_EPS(x) (((x) & 0x3) << 12) /* Endpoint Speed */
+#define QH_ENDPT1_EPS_FS 0x0
+#define QH_ENDPT1_EPS_LS 0x1
+#define QH_ENDPT1_EPS_HS 0x2
+#define QH_ENDPT1_ENDPT(x) (((x) & 0xf) << 8) /* Endpoint Number */
+#define QH_ENDPT1_I(x) (((x) & 0x1) << 7) /* Inactivate on Next Transaction */
+#define QH_ENDPT1_DEVADDR(x) (((x) & 0x7f) << 0) /* Device Address */
uint32_t qh_endpt2;
+#define QH_ENDPT2_MULT(x) (((x) & 0x3) << 30) /* High-Bandwidth Pipe Multiplier */
+#define QH_ENDPT2_PORTNUM(x) (((x) & 0x7f) << 23) /* Port Number */
+#define QH_ENDPT2_HUBADDR(x) (((x) & 0x7f) << 16) /* Hub Address */
+#define QH_ENDPT2_UFCMASK(x) (((x) & 0xff) << 8) /* Split Completion Mask */
+#define QH_ENDPT2_UFSMASK(x) (((x) & 0xff) << 0) /* Interrupt Schedule Mask */
uint32_t qh_curtd;
struct qTD qh_overlay;
/*
diff --git a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c
index d24f2f1314..9f4735167a 100644
--- a/drivers/usb/host/ohci-hcd.c
+++ b/drivers/usb/host/ohci-hcd.c
@@ -1261,19 +1261,11 @@ static int ohci_submit_rh_msg(struct usb_device *dev, unsigned long pipe,
int leni = transfer_len;
int len = 0;
int stat = 0;
- __u32 datab[4];
- union {
- void *ptr;
- __u8 *u8;
- __u16 *u16;
- __u32 *u32;
- } databuf;
__u16 bmRType_bReq;
__u16 wValue;
__u16 wIndex;
__u16 wLength;
-
- databuf.u32 = (__u32 *)datab;
+ ALLOC_ALIGN_BUFFER(__u8, databuf, 16, sizeof(u32));
#ifdef DEBUG
pkt_print(NULL, dev, pipe, buffer, transfer_len,
@@ -1304,20 +1296,20 @@ pkt_print(NULL, dev, pipe, buffer, transfer_len,
*/
case RH_GET_STATUS:
- databuf.u16[0] = cpu_to_le16(1);
+ *(u16 *)databuf = cpu_to_le16(1);
OK(2);
case RH_GET_STATUS | RH_INTERFACE:
- databuf.u16[0] = cpu_to_le16(0);
+ *(u16 *)databuf = cpu_to_le16(0);
OK(2);
case RH_GET_STATUS | RH_ENDPOINT:
- databuf.u16[0] = cpu_to_le16(0);
+ *(u16 *)databuf = cpu_to_le16(0);
OK(2);
case RH_GET_STATUS | RH_CLASS:
- databuf.u32[0] = cpu_to_le32(
+ *(u32 *)databuf = cpu_to_le32(
RD_RH_STAT & ~(RH_HS_CRWE | RH_HS_DRWE));
OK(4);
case RH_GET_STATUS | RH_OTHER | RH_CLASS:
- databuf.u32[0] = cpu_to_le32(RD_RH_PORTSTAT);
+ *(u32 *)databuf = cpu_to_le32(RD_RH_PORTSTAT);
OK(4);
case RH_CLEAR_FEATURE | RH_ENDPOINT:
@@ -1381,14 +1373,14 @@ pkt_print(NULL, dev, pipe, buffer, transfer_len,
min_t(unsigned int,
sizeof(root_hub_dev_des),
wLength));
- databuf.ptr = root_hub_dev_des; OK(len);
+ databuf = root_hub_dev_des; OK(len);
case (0x02): /* configuration descriptor */
len = min_t(unsigned int,
leni,
min_t(unsigned int,
sizeof(root_hub_config_des),
wLength));
- databuf.ptr = root_hub_config_des; OK(len);
+ databuf = root_hub_config_des; OK(len);
case (0x03): /* string descriptors */
if (wValue == 0x0300) {
len = min_t(unsigned int,
@@ -1396,7 +1388,7 @@ pkt_print(NULL, dev, pipe, buffer, transfer_len,
min_t(unsigned int,
sizeof(root_hub_str_index0),
wLength));
- databuf.ptr = root_hub_str_index0;
+ databuf = root_hub_str_index0;
OK(len);
}
if (wValue == 0x0301) {
@@ -1405,7 +1397,7 @@ pkt_print(NULL, dev, pipe, buffer, transfer_len,
min_t(unsigned int,
sizeof(root_hub_str_index1),
wLength));
- databuf.ptr = root_hub_str_index1;
+ databuf = root_hub_str_index1;
OK(len);
}
default:
@@ -1417,40 +1409,40 @@ pkt_print(NULL, dev, pipe, buffer, transfer_len,
{
__u32 temp = roothub_a(&gohci);
- databuf.u8[0] = 9; /* min length; */
- databuf.u8[1] = 0x29;
- databuf.u8[2] = temp & RH_A_NDP;
+ databuf[0] = 9; /* min length; */
+ databuf[1] = 0x29;
+ databuf[2] = temp & RH_A_NDP;
#ifdef CONFIG_AT91C_PQFP_UHPBUG
- databuf.u8[2] = (databuf.u8[2] == 2) ? 1 : 0;
+ databuf[2] = (databuf[2] == 2) ? 1 : 0;
#endif
- databuf.u8[3] = 0;
+ databuf[3] = 0;
if (temp & RH_A_PSM) /* per-port power switching? */
- databuf.u8[3] |= 0x1;
+ databuf[3] |= 0x1;
if (temp & RH_A_NOCP) /* no overcurrent reporting? */
- databuf.u8[3] |= 0x10;
+ databuf[3] |= 0x10;
else if (temp & RH_A_OCPM)/* per-port overcurrent reporting? */
- databuf.u8[3] |= 0x8;
+ databuf[3] |= 0x8;
- /* corresponds to databuf.u8[4-7] */
- databuf.u8[1] = 0;
- databuf.u8[5] = (temp & RH_A_POTPGT) >> 24;
+ databuf[4] = 0;
+ databuf[5] = (temp & RH_A_POTPGT) >> 24;
+ databuf[6] = 0;
temp = roothub_b(&gohci);
- databuf.u8[7] = temp & RH_B_DR;
- if (databuf.u8[2] < 7) {
- databuf.u8[8] = 0xff;
+ databuf[7] = temp & RH_B_DR;
+ if (databuf[2] < 7) {
+ databuf[8] = 0xff;
} else {
- databuf.u8[0] += 2;
- databuf.u8[8] = (temp & RH_B_DR) >> 8;
- databuf.u8[10] = databuf.u8[9] = 0xff;
+ databuf[0] += 2;
+ databuf[8] = (temp & RH_B_DR) >> 8;
+ databuf[10] = databuf[9] = 0xff;
}
len = min_t(unsigned int, leni,
- min_t(unsigned int, databuf.u8[0], wLength));
+ min_t(unsigned int, databuf[0], wLength));
OK(len);
}
case RH_GET_CONFIGURATION:
- databuf.u8[0] = 0x01;
+ databuf[0] = 0x01;
OK(1);
case RH_SET_CONFIGURATION:
@@ -1469,8 +1461,8 @@ pkt_print(NULL, dev, pipe, buffer, transfer_len,
#endif
len = min_t(int, len, leni);
- if (data != databuf.ptr)
- memcpy(data, databuf.ptr, len);
+ if (data != databuf)
+ memcpy(data, databuf, len);
dev->act_len = len;
dev->status = stat;
diff --git a/drivers/usb/musb/musb_hcd.c b/drivers/usb/musb/musb_hcd.c
index 2df52c1c33..8d44c4657f 100644
--- a/drivers/usb/musb/musb_hcd.c
+++ b/drivers/usb/musb/musb_hcd.c
@@ -1113,7 +1113,7 @@ int usb_lowlevel_init(void)
* should be a usb device connected.
*/
timeout = musb_cfg.timeout;
- while (timeout--)
+ while (--timeout)
if (readb(&musbr->devctl) & MUSB_DEVCTL_HM)
break;
OpenPOWER on IntegriCloud