blob: 57adfbce9645aba6570b844a3d7bc3c6331525f8 [file] [log] [blame]
/*
* Copyright (c) 2014-2015, Linaro Ltd and Contributors. All rights reserved.
* Copyright (c) 2014-2015, Hisilicon Ltd and Contributors. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* Neither the name of ARM nor the names of its contributors may be used
* to endorse or promote products derived from this software without specific
* prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <assert.h>
#include <ctype.h>
#include <debug.h>
#include <gpio.h>
#include <hi6220.h>
#include <mmio.h>
#include <partitions.h>
#include <platform_def.h>
#include <sp804_timer.h>
#include <string.h>
#include <usb.h>
#include "hikey_private.h"
#include <bl_common.h>
#define NUM_ENDPOINTS 16
#define USB_BLOCK_HIGH_SPEED_SIZE 512
#define VERSION_BOOTLOADER "0.4"
struct ep_type {
unsigned char active;
unsigned char busy;
unsigned char done;
unsigned int rc;
unsigned int size;
};
struct usb_endpoint {
struct usb_endpoint *next;
unsigned int maxpkt;
struct usb_request *req;
unsigned char num;
unsigned char in;
};
struct usb_config_bundle {
struct usb_config_descriptor config;
struct usb_interface_descriptor interface;
struct usb_endpoint_descriptor ep1;
struct usb_endpoint_descriptor ep2;
} __attribute__ ((packed));
static setup_packet ctrl_req[NUM_ENDPOINTS]
__attribute__ ((section("tzfw_coherent_mem")));
static unsigned char ctrl_resp[2]
__attribute__ ((section("tzfw_coherent_mem")));
static struct ep_type endpoints[NUM_ENDPOINTS]
__attribute__ ((section("tzfw_coherent_mem")));
dwc_otg_dev_dma_desc_t dma_desc
__attribute__ ((section("tzfw_coherent_mem")));
dwc_otg_dev_dma_desc_t dma_desc_ep0
__attribute__ ((section("tzfw_coherent_mem")));
dwc_otg_dev_dma_desc_t dma_desc_in
__attribute__ ((section("tzfw_coherent_mem")));
dwc_otg_dev_dma_desc_t dma_desc_addr
__attribute__ ((section("tzfw_coherent_mem")));
static struct usb_config_bundle config_bundle
__attribute__ ((section("tzfw_coherent_mem")));
static struct usb_device_descriptor device_descriptor
__attribute__ ((section("tzfw_coherent_mem")));
static struct usb_request rx_req
__attribute__ ((section("tzfw_coherent_mem")));
static struct usb_request tx_req
__attribute__ ((section("tzfw_coherent_mem")));
static struct usb_string_descriptor serial_string
__attribute__ ((section("tzfw_coherent_mem")));
static const struct usb_string_descriptor string_devicename = {
24,
USB_DT_STRING,
{'A', 'n', 'd', 'r', 'o', 'i', 'd', ' ', '2', '.', '0'}
};
static const struct usb_string_descriptor serial_string_descriptor = {
34,
USB_DT_STRING,
{'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'}
};
static const struct usb_string_descriptor lang_descriptor = {
4,
USB_DT_STRING,
{0x0409} /* en-US */
};
static void usb_rx_cmd_complete(unsigned actual, int stat);
static void usb_rx_data_complete(unsigned actual, int status);
static unsigned int rx_desc_bytes = 0;
static unsigned long rx_addr;
static unsigned long rx_length;
static unsigned int last_one = 0;
static char *cmdbuf;
static struct usb_endpoint ep1in, ep1out;
static int g_usb_enum_flag = 0;
int usb_need_reset = 0;
static int usb_drv_port_speed(void)
{
/* 2'b00 High speed (PHY clock is at 30MHz or 60MHz) */
return (mmio_read_32(DSTS) & 2) == 0 ? 1 : 0;
}
static void reset_endpoints(void)
{
int i;
unsigned int data;
INFO("enter reset_endpoints.\n");
for (i = 0; i < NUM_ENDPOINTS; i++) {
endpoints[i].active = 0;
endpoints[i].busy = 0;
endpoints[i].rc = -1;
endpoints[i].done = 1;
}
/* EP0 IN ACTIVE NEXT=1 */
mmio_write_32(DIEPCTL0, 0x8800);
/* EP0 OUT ACTIVE */
mmio_write_32(DOEPCTL0, 0x8000);
/* Clear any pending OTG Interrupts */
mmio_write_32(GOTGINT, ~0);
/* Clear any pending interrupts */
mmio_write_32(GINTSTS, ~0);
mmio_write_32(DIEPINT0, ~0);
mmio_write_32(DOEPINT0, ~0);
mmio_write_32(DIEPINT1, ~0);
mmio_write_32(DOEPINT1, ~0);
/* IN EP interrupt mask */
mmio_write_32(DIEPMSK, 0x0D);
/* OUT EP interrupt mask */
mmio_write_32(DOEPMSK, 0x0D);
/* Enable interrupts on Ep0 */
mmio_write_32(DAINTMSK, 0x00010001);
/* EP0 OUT Transfer Size:64 Bytes, 1 Packet, 3 Setup Packet, Read to receive setup packet*/
data = DOEPTSIZ0_SUPCNT(3) | DOEPTSIZ0_PKTCNT |
(64 << DOEPTSIZ0_XFERSIZE_SHIFT);
mmio_write_32(DOEPTSIZ0, data);
//notes that:the compulsive conversion is expectable.
dma_desc_ep0.status.b.bs = 0x3;
dma_desc_ep0.status.b.mtrf = 0;
dma_desc_ep0.status.b.sr = 0;
dma_desc_ep0.status.b.l = 1;
dma_desc_ep0.status.b.ioc = 1;
dma_desc_ep0.status.b.sp = 0;
dma_desc_ep0.status.b.bytes = 64;
dma_desc_ep0.buf = (unsigned long)&ctrl_req;
dma_desc_ep0.status.b.sts = 0;
dma_desc_ep0.status.b.bs = 0x0;
mmio_write_32(DOEPDMA0, ((unsigned long)&(dma_desc_ep0)));
VERBOSE("%s, &ctrl_req:%llx:%x, &dms_desc_ep0:%llx:%x\n",
__func__, (unsigned long)&ctrl_req, (unsigned long)&ctrl_req,
(unsigned long)&dma_desc_ep0, (unsigned long)&dma_desc_ep0);
/* EP0 OUT ENABLE CLEARNAK */
data = mmio_read_32(DOEPCTL0);
mmio_write_32(DOEPCTL0, (data | 0x84000000));
VERBOSE("exit reset_endpoints. \n");
}
static int usb_drv_request_endpoint(int type, int dir)
{
int ep = 1; /*FIXME*/
unsigned int newbits, data;
newbits = (type << 18) | 0x10000000;
/*
* (type << 18):Endpoint Type (EPType)
* 0x10000000:Endpoint Enable (EPEna)
* 0x000C000:Endpoint Type (EPType);Hardcoded to 00 for control.
* (ep<<22):TxFIFO Number (TxFNum)
* 0x20000:NAK Status (NAKSts);The core is transmitting NAK handshakes on this endpoint.
*/
if (dir) { // IN: to host
data = mmio_read_32(DIEPCTL(ep));
data &= ~0x000c0000;
data |= newbits | (ep << 22) | 0x20000;
mmio_write_32(DIEPCTL(ep), data);
} else { // OUT: to device
data = mmio_read_32(DOEPCTL(ep));
data &= ~0x000c0000;
data |= newbits;
mmio_write_32(DOEPCTL(ep), data);
}
endpoints[ep].active = 1; // true
return ep | dir;
}
void usb_drv_release_endpoint(int ep)
{
ep = ep % NUM_ENDPOINTS;
if (ep < 1 || ep > NUM_ENDPOINTS)
return;
endpoints[ep].active = 0;
}
void usb_config(void)
{
unsigned int data;
INFO("enter usb_config\n");
mmio_write_32(GDFIFOCFG, DATA_FIFO_CONFIG);
mmio_write_32(GRXFSIZ, RX_SIZE);
mmio_write_32(GNPTXFSIZ, ENDPOINT_TX_SIZE);
mmio_write_32(DIEPTXF1, DATA_IN_ENDPOINT_TX_FIFO1);
mmio_write_32(DIEPTXF2, DATA_IN_ENDPOINT_TX_FIFO2);
mmio_write_32(DIEPTXF3, DATA_IN_ENDPOINT_TX_FIFO3);
mmio_write_32(DIEPTXF4, DATA_IN_ENDPOINT_TX_FIFO4);
mmio_write_32(DIEPTXF5, DATA_IN_ENDPOINT_TX_FIFO5);
mmio_write_32(DIEPTXF6, DATA_IN_ENDPOINT_TX_FIFO6);
mmio_write_32(DIEPTXF7, DATA_IN_ENDPOINT_TX_FIFO7);
mmio_write_32(DIEPTXF8, DATA_IN_ENDPOINT_TX_FIFO8);
mmio_write_32(DIEPTXF9, DATA_IN_ENDPOINT_TX_FIFO9);
mmio_write_32(DIEPTXF10, DATA_IN_ENDPOINT_TX_FIFO10);
mmio_write_32(DIEPTXF11, DATA_IN_ENDPOINT_TX_FIFO11);
mmio_write_32(DIEPTXF12, DATA_IN_ENDPOINT_TX_FIFO12);
mmio_write_32(DIEPTXF13, DATA_IN_ENDPOINT_TX_FIFO13);
mmio_write_32(DIEPTXF14, DATA_IN_ENDPOINT_TX_FIFO14);
mmio_write_32(DIEPTXF15, DATA_IN_ENDPOINT_TX_FIFO15);
/*Init global csr register.*/
/*
* set Periodic TxFIFO Empty Level,
* Non-Periodic TxFIFO Empty Level,
* Enable DMA, Unmask Global Intr
*/
INFO("USB: DMA mode.\n");
mmio_write_32(GAHBCFG, GAHBCFG_CTRL_MASK);
/*select 8bit UTMI+, ULPI Inerface*/
INFO("USB ULPI PHY\n");
mmio_write_32(GUSBCFG, 0x2400);
/* Detect usb work mode,host or device? */
do {
data = mmio_read_32(GINTSTS);
} while (data & GINTSTS_CURMODE_HOST);
VERBOSE("Enter device mode\n");
udelay(3);
/*Init global and device mode csr register.*/
/*set Non-Zero-Length status out handshake */
data = (0x20 << DCFG_EPMISCNT_SHIFT) | DCFG_NZ_STS_OUT_HSHK;
mmio_write_32(DCFG, data);
/* Interrupt unmask: IN event, OUT event, bus reset */
data = GINTSTS_OEPINT | GINTSTS_IEPINT | GINTSTS_ENUMDONE |
GINTSTS_USBRST | GINTSTS_USBSUSP | GINTSTS_ERLYSUSP |
GINTSTS_GOUTNAKEFF;
mmio_write_32(GINTMSK, data);
do {
data = mmio_read_32(GINTSTS) & GINTSTS_ENUMDONE;
} while (data);
VERBOSE("USB Enum Done.\n");
/* Clear any pending OTG Interrupts */
mmio_write_32(GOTGINT, ~0);
/* Clear any pending interrupts */
mmio_write_32(GINTSTS, ~0);
mmio_write_32(GINTMSK, ~0);
data = mmio_read_32(GOTGINT);
data &= ~0x3000;
mmio_write_32(GOTGINT, data);
/*endpoint settings cfg*/
reset_endpoints();
udelay(1);
/*init finish. and ready to transfer data*/
/* Soft Disconnect */
mmio_write_32(DCTL, 0x802);
udelay(10000);
/* Soft Reconnect */
mmio_write_32(DCTL, 0x800);
VERBOSE("exit usb_config.\n");
}
void usb_drv_set_address(int address)
{
unsigned int cfg;
cfg = mmio_read_32(DCFG);
cfg &= ~0x7F0;
cfg |= address << 4;
mmio_write_32(DCFG, cfg); // 0x7F0: device address
}
static void ep_send(int ep, const void *ptr, int len)
{
unsigned int data;
endpoints[ep].busy = 1; // true
endpoints[ep].size = len;
/* EPx OUT ACTIVE */
data = mmio_read_32(DIEPCTL(ep)) | DXEPCTL_USBACTEP;
mmio_write_32(DIEPCTL(ep), data);
/* set DMA Address */
if (!len) {
/* send one empty packet */
dma_desc_in.buf = 0;
} else {
dma_desc_in.buf = (unsigned long)ptr;
}
dma_desc_in.status.b.bs = 0x3;
dma_desc_in.status.b.l = 1;
dma_desc_in.status.b.ioc = 1;
dma_desc_in.status.b.sp = 1;
dma_desc_in.status.b.sts = 0;
dma_desc_in.status.b.bs = 0x0;
dma_desc_in.status.b.bytes = len;
mmio_write_32(DIEPDMA(ep), (unsigned long)&dma_desc_in);
data = mmio_read_32(DIEPCTL(ep));
data |= DXEPCTL_EPENA | DXEPCTL_CNAK | DXEPCTL_NEXTEP(ep + 1);
mmio_write_32(DIEPCTL(ep), data);
}
void usb_drv_stall(int endpoint, char stall, char in)
{
unsigned int data;
/*
* STALL Handshake (Stall)
*/
data = mmio_read_32(DIEPCTL(endpoint));
if (in) {
if (stall)
mmio_write_32(DIEPCTL(endpoint), data | 0x00200000);
else
mmio_write_32(DIEPCTL(endpoint), data & ~0x00200000);
} else {
if (stall)
mmio_write_32(DOEPCTL(endpoint), data | 0x00200000);
else
mmio_write_32(DOEPCTL(endpoint), data & ~0x00200000);
}
}
int usb_drv_send_nonblocking(int endpoint, const void *ptr, int len)
{
VERBOSE("%s, endpoint = %d, ptr = 0x%x, Len=%d.\n",
__func__, endpoint, ptr, len);
ep_send(endpoint % NUM_ENDPOINTS, ptr, len);
return 0;
}
void usb_drv_cancel_all_transfers(void)
{
reset_endpoints();
}
int hiusb_epx_tx(unsigned ep, void *buf, unsigned len)
{
int blocksize,packets;
unsigned int epints;
unsigned int cycle = 0;
unsigned int data;
endpoints[ep].busy = 1; //true
endpoints[ep].size = len;
while (mmio_read_32(GINTSTS) & 0x40) {
data = mmio_read_32(DCTL);
data |= 0x100;
mmio_write_32(DCTL, data);
}
data = mmio_read_32(DIEPCTL(ep));
data |= 0x08000000;
mmio_write_32(DIEPCTL(ep), data);
/* EPx OUT ACTIVE */
mmio_write_32(DIEPCTL(ep), data | 0x8000);
if (!ep) {
blocksize = 64;
} else {
blocksize = usb_drv_port_speed() ? USB_BLOCK_HIGH_SPEED_SIZE : 64;
}
packets = (len + blocksize - 1) / blocksize;
if (!len) {
/* one empty packet */
mmio_write_32(DIEPTSIZ(ep), 1 << 19);
/* NULL */
dma_desc_in.status.b.bs = 0x3;
dma_desc_in.status.b.l = 1;
dma_desc_in.status.b.ioc = 1;
dma_desc_in.status.b.sp = last_one;
dma_desc_in.status.b.bytes = 0;
dma_desc_in.buf = 0;
dma_desc_in.status.b.sts = 0;
dma_desc_in.status.b.bs = 0x0;
mmio_write_32(DIEPDMA(ep), (unsigned long)&dma_desc_in);
} else {
mmio_write_32(DIEPTSIZ(ep), len | (packets << 19));
dma_desc_in.status.b.bs = 0x3;
dma_desc_in.status.b.l = 1;
dma_desc_in.status.b.ioc = 1;
dma_desc_in.status.b.sp = last_one;
dma_desc_in.status.b.bytes = len;
dma_desc_in.buf = (unsigned long)buf;
dma_desc_in.status.b.sts = 0;
dma_desc_in.status.b.bs = 0x0;
mmio_write_32(DIEPDMA(ep), (unsigned long)&dma_desc_in);
}
cycle = 0;
while(1){
data = mmio_read_32(DIEPINT(ep));
if ((data & 0x2000) || (cycle > 10000)) {
if (cycle > 10000) {
NOTICE("Phase 2:ep(%d) status, DIEPCTL(%d) is [0x%x],"
"DTXFSTS(%d) is [0x%x], DIEPINT(%d) is [0x%x],"
"DIEPTSIZ(%d) is [0x%x] GINTSTS is [0x%x]\n",
ep, ep, data,
ep, mmio_read_32(DTXFSTS(ep)),
ep, mmio_read_32(DIEPINT(ep)),
ep, mmio_read_32(DIEPTSIZ(ep)),
mmio_read_32(GINTSTS));
}
break;
}
cycle++;
udelay(10);
}
VERBOSE("ep(%d) enable, DIEPCTL(%d) is [0x%x], DTXFSTS(%d) is [0x%x],"
"DIEPINT(%d) is [0x%x], DIEPTSIZ(%d) is [0x%x] \n",
ep, ep, mmio_read_32(DIEPCTL(ep)),
ep, mmio_read_32(DTXFSTS(ep)),
ep, mmio_read_32(DIEPINT(ep)),
ep, mmio_read_32(DIEPTSIZ(ep)));
__asm__ volatile("dsb sy\n"
"isb sy\n");
data = mmio_read_32(DIEPCTL(ep));
data |= 0x84000000;
/* epena & cnak*/
mmio_write_32(DIEPCTL(ep), data);
__asm__ volatile("dsb sy\n"
"isb sy\n");
cycle = 0;
while (1) {
epints = mmio_read_32(DIEPINT(ep)) & 1;
if ((mmio_read_32(GINTSTS) & 0x40000) && epints) {
VERBOSE("Tx succ:ep(%d), DTXFSTS(%d) is [0x%x] \n",
ep, ep, mmio_read_32(DTXFSTS(ep)));
mmio_write_32(DIEPINT(ep), epints);
if (endpoints[ep].busy) {
endpoints[ep].busy = 0;//false
endpoints[ep].rc = 0;
endpoints[ep].done = 1;//true
}
break;
}
cycle++;
udelay(10);
VERBOSE("loop for intr: ep(%d), DIEPCTL(%d) is [0x%x], ",
"DTXFSTS(%d) is [0x%x], DIEPINT(%d) is [0x%x] \n",
ep, ep, mmio_read_32(DIEPCTL(ep)),
ep, mmio_read_32(DTXFSTS(ep)),
ep, mmio_read_32(DIEPINT(ep)));
if (cycle > 1000000) {
WARN("Wait IOC intr over 10s! USB will reset\n");
usb_need_reset = 1;
return 1;
}
}
cycle = 0;
while (1) {
if ((mmio_read_32(DIEPINT(ep)) & 0x2000) || (cycle > 100000)) {
if (cycle > 100000){
WARN("all wait cycle is [%d]\n",cycle);
}
break;
}
cycle++;
udelay(10);
}
return 0;
}
int hiusb_epx_rx(unsigned ep, void *buf, unsigned len)
{
unsigned int blocksize = 0, data;
int packets;
VERBOSE("ep%d rx, len = 0x%x, buf = 0x%x.\n", ep, len, buf);
endpoints[ep].busy = 1;//true
/* EPx UNSTALL */
data = mmio_read_32(DOEPCTL(ep)) & ~0x00200000;
mmio_write_32(DOEPCTL(ep), data);
/* EPx OUT ACTIVE */
data = mmio_read_32(DOEPCTL(ep)) | 0x8000;
mmio_write_32(DOEPCTL(ep), data);
blocksize = usb_drv_port_speed() ? USB_BLOCK_HIGH_SPEED_SIZE : 64;
packets = (len + blocksize - 1) / blocksize;
#define MAX_RX_PACKET 0x3FF
/*Max recv packets is 1023*/
if (packets > MAX_RX_PACKET) {
endpoints[ep].size = MAX_RX_PACKET * blocksize;
len = MAX_RX_PACKET * blocksize;
} else {
endpoints[ep].size = len;
}
if (!len) {
/* one empty packet */
mmio_write_32(DOEPTSIZ(ep), 1 << 19);
//NULL /* dummy address */
dma_desc.status.b.bs = 0x3;
dma_desc.status.b.mtrf = 0;
dma_desc.status.b.sr = 0;
dma_desc.status.b.l = 1;
dma_desc.status.b.ioc = 1;
dma_desc.status.b.sp = 0;
dma_desc.status.b.bytes = 0;
dma_desc.buf = 0;
dma_desc.status.b.sts = 0;
dma_desc.status.b.bs = 0x0;
mmio_write_32(DOEPDMA(ep), (unsigned long)&dma_desc);
} else {
if (len >= blocksize * 64) {
rx_desc_bytes = blocksize*64;
} else {
rx_desc_bytes = len;
}
VERBOSE("rx len %d, rx_desc_bytes %d \n",len,rx_desc_bytes);
dma_desc.status.b.bs = 0x3;
dma_desc.status.b.mtrf = 0;
dma_desc.status.b.sr = 0;
dma_desc.status.b.l = 1;
dma_desc.status.b.ioc = 1;
dma_desc.status.b.sp = 0;
dma_desc.status.b.bytes = rx_desc_bytes;
dma_desc.buf = (unsigned long)buf;
dma_desc.status.b.sts = 0;
dma_desc.status.b.bs = 0x0;
mmio_write_32(DOEPDMA(ep), (unsigned long)&dma_desc);
}
/* EPx OUT ENABLE CLEARNAK */
data = mmio_read_32(DOEPCTL(ep));
data |= 0x84000000;
mmio_write_32(DOEPCTL(ep), data);
return 0;
}
int usb_queue_req(struct usb_endpoint *ept, struct usb_request *req)
{
if (ept->in)
hiusb_epx_tx(ept->num, req->buf, req->length);
else
hiusb_epx_rx(ept->num, req->buf, req->length);
return 0;
}
static void rx_cmd(void)
{
struct usb_request *req = &rx_req;
req->buf = cmdbuf;
req->length = RX_REQ_LEN;
req->complete = usb_rx_cmd_complete;
usb_queue_req(&ep1out, req);
}
static void rx_data(void)
{
struct usb_request *req = &rx_req;
req->buf = (void *)((unsigned long) rx_addr);
req->length = rx_length;
req->complete = usb_rx_data_complete;
usb_queue_req(&ep1out, req);
}
void tx_status(const char *status)
{
struct usb_request *req = &tx_req;
int len = strlen(status);
memcpy(req->buf, status, (unsigned int)len);
req->length = (unsigned int)len;
req->complete = 0;
usb_queue_req(&ep1in, req);
}
void fastboot_tx_status(const char *status)
{
tx_status(status);
rx_cmd();
}
void tx_dump_page(const char *ptr, int len)
{
struct usb_request *req = &tx_req;
memcpy(req->buf, ptr, (unsigned int)len);
req->length = (unsigned int)len;
req->complete = 0;
usb_queue_req(&ep1in, req);
}
static void usb_rx_data_complete(unsigned actual, int status)
{
if(status != 0)
return;
if(actual > rx_length) {
actual = rx_length;
}
rx_addr += actual;
rx_length -= actual;
if(rx_length > 0) {
rx_data();
} else {
tx_status("OKAY");
rx_cmd();
}
}
static void usb_status(unsigned online, unsigned highspeed)
{
if (online) {
INFO("usb: online (%s)\n", highspeed ? "highspeed" : "fullspeed");
rx_cmd();
}
}
void usb_handle_control_request(setup_packet* req)
{
const void* addr = NULL;
int size = -1;
int i;
int maxpacket;
unsigned int data;
char *serialno;
struct usb_endpoint_descriptor epx;
struct usb_config_bundle const_bundle = {
.config = {
.bLength = sizeof(struct usb_config_descriptor),
.bDescriptorType = USB_DT_CONFIG,
.wTotalLength = sizeof(struct usb_config_descriptor) +
sizeof(struct usb_interface_descriptor) +
sizeof(struct usb_endpoint_descriptor) *
USB_NUM_ENDPOINTS,
.bNumInterfaces = 1,
.bConfigurationValue = 1,
.iConfiguration = 0,
.bmAttributes = USB_CONFIG_ATT_ONE,
.bMaxPower = 0x80
},
.interface = {
.bLength = sizeof(struct usb_interface_descriptor),
.bDescriptorType = USB_DT_INTERFACE,
.bInterfaceNumber = 0,
.bAlternateSetting = 0,
.bNumEndpoints = USB_NUM_ENDPOINTS,
.bInterfaceClass = USB_CLASS_VENDOR_SPEC,
.bInterfaceSubClass = 0x42,
.bInterfaceProtocol = 0x03,
.iInterface = 0
}
};
/* avoid to hang on accessing unaligned memory */
struct usb_endpoint_descriptor const_ep1 = {
.bLength = sizeof(struct usb_endpoint_descriptor),
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = 0x81,
.bmAttributes = USB_ENDPOINT_XFER_BULK,
.wMaxPacketSize = 0,
.bInterval = 0
};
struct usb_endpoint_descriptor const_ep2 = {
.bLength = sizeof(struct usb_endpoint_descriptor),
.bDescriptorType = USB_DT_ENDPOINT,
.bEndpointAddress = 0x01,
.bmAttributes = USB_ENDPOINT_XFER_BULK,
.wMaxPacketSize = 0,
.bInterval = 1
};
struct usb_device_descriptor const_device = {
.bLength = sizeof(struct usb_device_descriptor),
.bDescriptorType = USB_DT_DEVICE,
.bcdUSB = 0x0200,
.bDeviceClass = 0,
.bDeviceClass = 0,
.bDeviceProtocol = 0,
.bMaxPacketSize0 = 0x40,
.idVendor = 0x18d1,
.idProduct = 0xd00d,
.bcdDevice = 0x0100,
.iManufacturer = 1,
.iProduct = 2,
.iSerialNumber = 3,
.bNumConfigurations = 1
};
memcpy(&config_bundle, &const_bundle, sizeof(struct usb_config_bundle));
memcpy(&config_bundle.ep1, &const_ep1, sizeof(struct usb_endpoint_descriptor));
memcpy(&config_bundle.ep2, &const_ep2, sizeof(struct usb_endpoint_descriptor));
memcpy(&device_descriptor, &const_device,
sizeof(struct usb_device_descriptor));
switch (req->request) {
case USB_REQ_GET_STATUS:
if (req->type == USB_DIR_IN)
ctrl_resp[0] = 1;
else
ctrl_resp[0] = 0;
ctrl_resp[1] = 0;
addr = ctrl_resp;
size = 2;
break;
case USB_REQ_CLEAR_FEATURE:
if ((req->type == USB_RECIP_ENDPOINT) &&
(req->value == USB_ENDPOINT_HALT))
usb_drv_stall(req->index & 0xf, 0, req->index >> 7);
size = 0;
break;
case USB_REQ_SET_FEATURE:
size = 0;
break;
case USB_REQ_SET_ADDRESS:
size = 0;
usb_drv_cancel_all_transfers(); // all endpoints reset
usb_drv_set_address(req->value); // set device address
break;
case USB_REQ_GET_DESCRIPTOR:
VERBOSE("USB_REQ_GET_DESCRIPTOR: 0x%x\n", req->value >> 8);
switch (req->value >> 8) {
case USB_DT_DEVICE:
addr = &device_descriptor;
size = sizeof(device_descriptor);
VERBOSE("Get device descriptor.\n");
break;
case USB_DT_OTHER_SPEED_CONFIG:
case USB_DT_CONFIG:
if ((req->value >> 8) == USB_DT_CONFIG) {
maxpacket = usb_drv_port_speed() ? USB_BLOCK_HIGH_SPEED_SIZE : 64;
config_bundle.config.bDescriptorType = USB_DT_CONFIG;
} else {
maxpacket = usb_drv_port_speed() ? 64 : USB_BLOCK_HIGH_SPEED_SIZE;
config_bundle.config.bDescriptorType = USB_DT_OTHER_SPEED_CONFIG;
}
/* avoid hang when access unaligned structure */
memcpy(&epx, &config_bundle.ep1, sizeof(struct usb_endpoint_descriptor));
epx.wMaxPacketSize = maxpacket;
memcpy(&config_bundle.ep1, &epx, sizeof(struct usb_endpoint_descriptor));
memcpy(&epx, &config_bundle.ep2, sizeof(struct usb_endpoint_descriptor));
epx.wMaxPacketSize = maxpacket;
memcpy(&config_bundle.ep2, &epx, sizeof(struct usb_endpoint_descriptor));
addr = &config_bundle;
size = sizeof(config_bundle);
VERBOSE("Get config descriptor.\n");
break;
case USB_DT_STRING:
switch (req->value & 0xff) {
case 0:
addr = &lang_descriptor;
size = lang_descriptor.bLength;
break;
case 1:
addr = &string_devicename;
size = 14;
break;
case 2:
addr = &string_devicename;
size = string_devicename.bLength;
break;
case 3:
serialno = load_serialno();
if (serialno == NULL) {
addr = &serial_string_descriptor;
size = serial_string_descriptor.bLength;
} else {
i = 0;
memcpy((void *)&serial_string,
(void *)&serial_string_descriptor,
sizeof(serial_string));
while (1) {
serial_string.wString[i] = serialno[i];
if (serialno[i] == '\0')
break;
i++;
}
addr = &serial_string;
size = serial_string.bLength;
}
break;
default:
break;
}
break;
default:
break;
}
break;
case USB_REQ_GET_CONFIGURATION:
ctrl_resp[0] = 1;
addr = ctrl_resp;
size = 1;
break;
case USB_REQ_SET_CONFIGURATION:
usb_drv_cancel_all_transfers(); // call reset_endpoints reset all EPs
usb_drv_request_endpoint(USB_ENDPOINT_XFER_BULK, USB_DIR_OUT);
usb_drv_request_endpoint(USB_ENDPOINT_XFER_BULK, USB_DIR_IN);
/*
* 0x10088800:
* 1:EP enable; 8:EP type:BULK; 8:USB Active Endpoint; 8:Next Endpoint
*/
data = mmio_read_32(DIEPCTL1) | 0x10088800;
mmio_write_32(DIEPCTL1, data);
data = mmio_read_32(DIEPCTL(1)) | 0x08000000;
mmio_write_32(DIEPCTL(1), data);
/* Enable interrupts on all endpoints */
mmio_write_32(DAINTMSK, 0xffffffff);
usb_status(req->value? 1 : 0, usb_drv_port_speed() ? 1 : 0);
size = 0;
VERBOSE("Set config descriptor.\n");
/* USB ö¾Ù³É¹¦µã,ÖÃÉϱêʶ */
g_usb_enum_flag = 1;
break;
default:
break;
}
if (!size) {
usb_drv_send_nonblocking(0, 0, 0); // send an empty packet
} else if (size == -1) { // stall:Applies to non-control, non-isochronous IN and OUT endpoints only.
usb_drv_stall(0, 1, 1); // IN
usb_drv_stall(0, 1, 0); // OUT
} else { // stall:Applies to control endpoints only.
usb_drv_stall(0, 0, 1); // IN
usb_drv_stall(0, 0, 0); // OUT
usb_drv_send_nonblocking(0, addr, size > req->length ? req->length : size);
}
}
/* IRQ handler */
static void usb_poll(void)
{
uint32_t ints;
uint32_t epints, data;
ints = mmio_read_32(GINTSTS); /* interrupt status */
if ((ints & 0xc3010) == 0)
return;
/*
* bus reset
* The core sets this bit to indicate that a reset is detected on the USB.
*/
if (ints & GINTSTS_USBRST) {
VERBOSE("bus reset intr\n");
/*set Non-Zero-Length status out handshake */
/*
* DCFG:This register configures the core in Device mode after power-on
* or after certain control commands or enumeration. Do not make changes
* to this register after initial programming.
* Send a STALL handshake on a nonzero-length status OUT transaction and
* do not send the received OUT packet to the application.
*/
mmio_write_32(DCFG, 0x800004);
reset_endpoints();
}
/*
* enumeration done, we now know the speed
* The core sets this bit to indicate that speed enumeration is complete. The
* application must read the Device Status (DSTS) register to obtain the
* enumerated speed.
*/
if (ints & GINTSTS_ENUMDONE) {
/* Set up the maximum packet sizes accordingly */
uint32_t maxpacket = usb_drv_port_speed() ? USB_BLOCK_HIGH_SPEED_SIZE : 64; // high speed maxpacket=512
VERBOSE("enum done intr. Maxpacket:%d\n", maxpacket);
//Set Maximum In Packet Size (MPS)
data = mmio_read_32(DIEPCTL1) & ~0x000003ff;
mmio_write_32(DIEPCTL1, data | maxpacket);
//Set Maximum Out Packet Size (MPS)
data = mmio_read_32(DOEPCTL1) & ~0x000003ff;
mmio_write_32(DOEPCTL1, data | maxpacket);
}
/*
* IN EP event
* The core sets this bit to indicate that an interrupt is pending on one of the IN
* endpoints of the core (in Device mode). The application must read the
* Device All Endpoints Interrupt (DAINT) register to determine the exact
* number of the IN endpoint on which the interrupt occurred, and then read
* the corresponding Device IN Endpoint-n Interrupt (DIEPINTn) register to
* determine the exact cause of the interrupt. The application must clear the
* appropriate status bit in the corresponding DIEPINTn register to clear this bit.
*/
if (ints & GINTSTS_IEPINT) {
epints = mmio_read_32(DIEPINT0);
mmio_write_32(DIEPINT0, epints);
//VERBOSE("IN EP event,ints:0x%x, DIEPINT0:%x, DAINT:%x, DAINTMSK:%x.\n",
// ints, epints, mmio_read_32(DAINT), mmio_read_32(DAINTMSK));
if (epints & 0x1) { /* Transfer Completed Interrupt (XferCompl) */
VERBOSE("TX completed.DIEPTSIZ(0) = 0x%x.\n", mmio_read_32(DIEPTSIZ0));
/*FIXME,Maybe you can use bytes*/
/*int bytes = endpoints[0].size - (DIEPTSIZ(0) & 0x3FFFF);*/ //actual transfer
if (endpoints[0].busy) {
endpoints[0].busy = 0;//false
endpoints[0].rc = 0;
endpoints[0].done = 1;//true
}
}
if (epints & 0x4) { /* AHB error */
WARN("AHB error on IN EP0.\n");
}
if (epints & 0x8) { /* Timeout */
WARN("Timeout on IN EP0.\n");
if (endpoints[0].busy) {
endpoints[0].busy = 1;//false
endpoints[0].rc = 1;
endpoints[0].done = 1;//true
}
}
}
/*
* OUT EP event
* The core sets this bit to indicate that an interrupt is pending on one of the
* OUT endpoints of the core (in Device mode). The application must read the
* Device All Endpoints Interrupt (DAINT) register to determine the exact
* number of the OUT endpoint on which the interrupt occurred, and then read
* the corresponding Device OUT Endpoint-n Interrupt (DOEPINTn) register
* to determine the exact cause of the interrupt. The application must clear the
* appropriate status bit in the corresponding DOEPINTn register to clear this bit.
*/
if (ints & GINTSTS_OEPINT) {
/* indicates the status of an endpoint
* with respect to USB- and AHB-related events. */
epints = mmio_read_32(DOEPINT(0));
//VERBOSE("OUT EP event,ints:0x%x, DOEPINT0:%x, DAINT:%x, DAINTMSK:%x.\n",
// ints, epints, mmio_read_32(DAINT), mmio_read_32(DAINTMSK));
if (epints) {
mmio_write_32(DOEPINT(0), epints);
/* Transfer completed */
if (epints & DXEPINT_XFERCOMPL) {
/*FIXME,need use bytes*/
VERBOSE("EP0 RX completed. DOEPTSIZ(0) = 0x%x.\n",
mmio_read_32(DOEPTSIZ(0)));
if (endpoints[0].busy) {
endpoints[0].busy = 0;
endpoints[0].rc = 0;
endpoints[0].done = 1;
}
}
if (epints & DXEPINT_AHBERR) { /* AHB error */
WARN("AHB error on OUT EP0.\n");
}
/*
* IN Token Received When TxFIFO is Empty (INTknTXFEmp)
* Indicates that an IN token was received when the associated TxFIFO (periodic/nonperiodic)
* was empty. This interrupt is asserted on the endpoint for which the IN token
* was received.
*/
if (epints & DXEPINT_SETUP) { /* SETUP phase done */
VERBOSE("Setup phase \n");
data = mmio_read_32(DIEPCTL(0)) | DXEPCTL_SNAK;
mmio_write_32(DIEPCTL(0), data);
data = mmio_read_32(DOEPCTL(0)) | DXEPCTL_SNAK;
mmio_write_32(DOEPCTL(0), data);
/*clear IN EP intr*/
mmio_write_32(DIEPINT(0), ~0);
usb_handle_control_request((setup_packet *)&ctrl_req);
}
/* Make sure EP0 OUT is set up to accept the next request */
/* memset(p_ctrlreq, 0, NUM_ENDPOINTS*8); */
data = DOEPTSIZ0_SUPCNT(3) | DOEPTSIZ0_PKTCNT |
(64 << DOEPTSIZ0_XFERSIZE_SHIFT);
mmio_write_32(DOEPTSIZ0, data);
/*
* IN Token Received When TxFIFO is Empty (INTknTXFEmp)
* Indicates that an IN token was received when the associated TxFIFO (periodic/nonperiodic)
* was empty. This interrupt is asserted on the endpoint for which the IN token
* was received.
*/
// notes that:the compulsive conversion is expectable.
// Holds the start address of the external memory for storing or fetching endpoint data.
dma_desc_ep0.status.b.bs = 0x3;
dma_desc_ep0.status.b.mtrf = 0;
dma_desc_ep0.status.b.sr = 0;
dma_desc_ep0.status.b.l = 1;
dma_desc_ep0.status.b.ioc = 1;
dma_desc_ep0.status.b.sp = 0;
dma_desc_ep0.status.b.bytes = 64;
dma_desc_ep0.buf = (uintptr_t)&ctrl_req;
dma_desc_ep0.status.b.sts = 0;
dma_desc_ep0.status.b.bs = 0x0;
mmio_write_32(DOEPDMA0, (uintptr_t)&dma_desc_ep0);
// endpoint enable; clear NAK
mmio_write_32(DOEPCTL0, 0x84000000);
}
epints = mmio_read_32(DOEPINT1);
if(epints) {
mmio_write_32(DOEPINT1, epints);
VERBOSE("OUT EP1: epints :0x%x,DOEPTSIZ1 :0x%x.\n",epints, mmio_read_32(DOEPTSIZ1));
/* Transfer Completed Interrupt (XferCompl);Transfer completed */
if (epints & DXEPINT_XFERCOMPL) {
/* ((readl(DOEPTSIZ(1))) & 0x7FFFF is Transfer Size (XferSize) */
/*int bytes = (p_endpoints + 1)->size - ((readl(DOEPTSIZ(1))) & 0x7FFFF);*/
int bytes = rx_desc_bytes - dma_desc.status.b.bytes;
VERBOSE("OUT EP1: recv %d bytes \n",bytes);
if (endpoints[1].busy) {
endpoints[1].busy = 0;
endpoints[1].rc = 0;
endpoints[1].done = 1;
rx_req.complete(bytes, 0);
}
}
if (epints & DXEPINT_AHBERR) { /* AHB error */
WARN("AHB error on OUT EP1.\n");
}
if (epints & DXEPINT_SETUP) { /* SETUP phase done */
WARN("SETUP phase done on OUT EP1.\n");
}
}
}
/* write to clear interrupts */
mmio_write_32(GINTSTS, ints);
}
#define EYE_PATTERN 0x70533483
/*
* pico phy exit siddq, nano phy enter siddq,
* and open the clock of pico phy and dvc,
*/
static void dvc_and_picophy_init_chip(void)
{
unsigned int data;
/* enable USB clock */
mmio_write_32(PERI_SC_PERIPH_CLKEN0, PERI_CLK0_USBOTG);
do {
data = mmio_read_32(PERI_SC_PERIPH_CLKSTAT0);
} while ((data & PERI_CLK0_USBOTG) == 0);
/* out of reset */
mmio_write_32(PERI_SC_PERIPH_RSTDIS0,
PERI_RST0_USBOTG_BUS | PERI_RST0_POR_PICOPHY |
PERI_RST0_USBOTG | PERI_RST0_USBOTG_32K);
do {
data = mmio_read_32(PERI_SC_PERIPH_RSTSTAT0);
data &= PERI_RST0_USBOTG_BUS | PERI_RST0_POR_PICOPHY |
PERI_RST0_USBOTG | PERI_RST0_USBOTG_32K;
} while (data);
mmio_write_32(PERI_SC_PERIPH_CTRL8, EYE_PATTERN);
/* configure USB PHY */
data = mmio_read_32(PERI_SC_PERIPH_CTRL4);
/* make PHY out of low power mode */
data &= ~PERI_CTRL4_PICO_SIDDQ;
/* detect VBUS by external circuit, switch D+ to 1.5KOhm pullup */
data |= PERI_CTRL4_PICO_VBUSVLDEXTSEL | PERI_CTRL4_PICO_VBUSVLDEXT;
data &= ~PERI_CTRL4_FPGA_EXT_PHY_SEL;
/* select PHY */
data &= ~PERI_CTRL4_OTG_PHY_SEL;
mmio_write_32(PERI_SC_PERIPH_CTRL4, data);
udelay(1000);
data = mmio_read_32(PERI_SC_PERIPH_CTRL5);
data &= ~PERI_CTRL5_PICOPHY_BC_MODE;
mmio_write_32(PERI_SC_PERIPH_CTRL5, data);
udelay(20000);
}
int init_usb(void)
{
static int init_flag = 0;
uint32_t data;
if (init_flag == 0) {
memset(&ctrl_req, 0, sizeof(setup_packet));
memset(&ctrl_resp, 0, 2);
memset(&endpoints, 0, sizeof(struct ep_type) * NUM_ENDPOINTS);
memset(&dma_desc, 0, sizeof(struct dwc_otg_dev_dma_desc));
memset(&dma_desc_ep0, 0, sizeof(struct dwc_otg_dev_dma_desc));
memset(&dma_desc_in, 0, sizeof(struct dwc_otg_dev_dma_desc));
}
VERBOSE("Pico PHY and DVC init start.\n");
dvc_and_picophy_init_chip();
VERBOSE("Pico PHY and DVC init done.\n");
/* wait for OTG AHB master idle */
do {
data = mmio_read_32(GRSTCTL) & GRSTCTL_AHBIDLE;
} while (data == 0);
VERBOSE("Reset usb controller\n");
/* OTG: Assert software reset */
mmio_write_32(GRSTCTL, GRSTCTL_CSFTRST);
/* wait for OTG to ack reset */
while (mmio_read_32(GRSTCTL) & GRSTCTL_CSFTRST);
/* wait for OTG AHB master idle */
while ((mmio_read_32(GRSTCTL) & GRSTCTL_AHBIDLE) == 0);
VERBOSE("Reset usb controller done\n");
usb_config();
VERBOSE("exit usb_init()\n");
return 0;
}
#define LOCK_STATE_LOCKED 0
#define LOCK_STATE_UNLOCKED 1
#define LOCK_STATE_RELOCKED 2
#define FB_MAX_FILE_SIZE (256 * 1024 * 1024)
static struct ptentry *flash_ptn = NULL;
static void fb_getvar(char *cmdbuf)
{
char response[64];
char part_name[32];
int bytes;
struct ptentry *ptn = 0;
if (!strncmp(cmdbuf + 7, "max-download-size", 17)) {
bytes = sprintf(response, "OKAY0x%08x",
FB_MAX_FILE_SIZE);
response[bytes] = '\0';
tx_status(response);
rx_cmd();
} else if (!strncmp(cmdbuf + 7, "partition-type:", 15)) {
bytes = sprintf(part_name, "%s", cmdbuf + 22);
ptn = find_ptn(part_name);
if (ptn == NULL) {
bytes = sprintf(response, "FAIL%s",
"invalid partition");
response[bytes] = '\0';
flash_ptn = NULL;
} else {
flash_ptn = ptn;
if (!strncmp(cmdbuf +22, "system", 6) || !strncmp(cmdbuf +22, "userdata", 8) ||
!strncmp(cmdbuf +22, "cache", 5)) {
bytes = sprintf(response, "OKAYext4");
response[bytes] = '\0';
} else {
bytes = sprintf(response, "OKAYraw");
response[bytes] = '\0';
}
}
tx_status(response);
rx_cmd();
} else if (!strncmp(cmdbuf + 7, "partition-size:", 15)) {
bytes = sprintf(part_name, "%s", cmdbuf + 22);
ptn = find_ptn(part_name);
if (ptn == NULL) {
bytes = sprintf(response, "FAIL%s",
"invalid partition");
response[bytes] = '\0';
flash_ptn = NULL;
} else {
bytes = sprintf(response, "OKAY%llx",ptn->length);
response[bytes] = '\0';
flash_ptn = ptn;
}
tx_status(response);
rx_cmd();
} else if (!strncmp(cmdbuf + 7, "serialno", 8)) {
bytes = sprintf(response, "OKAY%s",
load_serialno());
response[bytes] = '\0';
tx_status(response);
rx_cmd();
} else if (!strncmp(cmdbuf + 7, "version-bootloader", 18)) {
bytes = sprintf(response, "OKAY%s", VERSION_BOOTLOADER);
response[bytes] = '\0';
tx_status(response);
rx_cmd();
} else if (!strncmp(cmdbuf + 7, "version-baseband", 16)) {
bytes = sprintf(response, "OKAYN/A");
response[bytes] = '\0';
tx_status(response);
rx_cmd();
} else if (!strncmp(cmdbuf + 7, "product", 8)) {
bytes = sprintf(response, "OKAYhikey");
response[bytes] = '\0';
tx_status(response);
rx_cmd();
} else {
bytes = sprintf(response, "FAIL%s",
"unknown var");
response[bytes] = '\0';
tx_status(response);
rx_cmd();
}
}
/* FIXME: do not support endptr yet */
static unsigned long strtoul(const char *nptr, char **endptr, int base)
{
unsigned long step, data;
int i;
if (base == 0)
step = 10;
else if ((base < 2) || (base > 36)) {
VERBOSE("%s: invalid base %d\n", __func__, base);
return 0;
} else
step = base;
for (i = 0, data = 0; ; i++) {
if (nptr[i] == '\0')
break;
else if (!isalpha(nptr[i]) && !isdigit(nptr[i])) {
VERBOSE("%s: invalid string %s at %d [%x]\n",
__func__, nptr, i, nptr[i]);
return 0;
} else {
data *= step;
if (isupper(nptr[i]))
data += nptr[i] - 'A' + 10;
else if (islower(nptr[i]))
data += nptr[i] - 'a' + 10;
else if (isdigit(nptr[i]))
data += nptr[i] - '0';
}
}
return data;
}
static void fb_serialno(char *cmdbuf)
{
struct random_serial_num random;
generate_serialno(&random);
flush_random_serialno((unsigned long)&random, sizeof(random));
}
static int fb_assigned_sn(char *cmdbuf)
{
struct random_serial_num random;
int ret;
ret = assign_serialno(cmdbuf, &random);
if (ret < 0)
return ret;
flush_random_serialno((unsigned long)&random, sizeof(random));
return 0;
}
#define FB_DOWNLOAD_BASE 0x20000000
static unsigned long fb_download_base, fb_download_size;
static void fb_download(char *cmdbuf)
{
char response[64];
int bytes;
if (!flash_ptn) {
bytes = sprintf(response, "FAIL%s",
"invalid partition");
response[bytes] = '\0';
tx_status(response);
rx_cmd();
} else {
rx_addr = FB_DOWNLOAD_BASE;
rx_length = strtoul(cmdbuf + 9, NULL, 16);
fb_download_base = rx_addr;
fb_download_size = rx_length;
if (rx_length > FB_MAX_FILE_SIZE) {
bytes = sprintf(response, "FAIL%s",
"file is too large");
response[bytes] = '\0';
tx_status(response);
rx_cmd();
} else {
bytes = sprintf(response, "DATA%08x",
rx_length);
VERBOSE("start:0x%x, length:0x%x, res:%s\n",
rx_addr, rx_length, response);
response[bytes] = '\0';
tx_status(response);
rx_data();
}
}
}
static void fb_flash(char *cmdbuf)
{
flush_user_images(cmdbuf + 6, fb_download_base, fb_download_size);
tx_status("OKAY");
rx_cmd();
}
static void fb_reboot(char *cmdbuf)
{
/* Send the system reset request */
mmio_write_32(AO_SC_SYS_STAT0, 0x48698284);
wfi();
panic();
}
static void usb_rx_cmd_complete(unsigned actual, int stat)
{
if(stat != 0) return;
if(actual > 4095)
actual = 4095;
cmdbuf[actual] = 0;
INFO("cmd :%s\n",cmdbuf);
if(memcmp(cmdbuf, (void *)"reboot", 6) == 0) {
tx_status("OKAY");
fb_reboot(cmdbuf);
return;
} else if (!memcmp(cmdbuf, (void *)"getvar:", 7)) {
fb_getvar(cmdbuf);
return;
} else if (!memcmp(cmdbuf, (void *)"download:", 9)) {
fb_download(cmdbuf);
return;
} else if(memcmp(cmdbuf, (void *)"erase:", 6) == 0) {
/* FIXME erase is not supported but we return success */
tx_status("OKAY");
rx_cmd();
return;
} else if(memcmp(cmdbuf, (void *)"flash:", 6) == 0) {
INFO("recog updatefile\n");
fb_flash(cmdbuf);
return;
} else if(memcmp(cmdbuf, (void *)"boot", 4) == 0) {
INFO(" - OKAY\n");
return;
} else if (memcmp(cmdbuf, (void *)"oem serialno", 12) == 0) {
if (*(cmdbuf + 12) == '\0') {
fb_serialno(cmdbuf);
tx_status("OKAY");
rx_cmd();
return;
} else if (memcmp(cmdbuf + 12, (void *)" set", 4) == 0) {
if (fb_assigned_sn(cmdbuf + 16) == 0) {
tx_status("OKAY");
rx_cmd();
return;
}
}
} else if (memcmp(cmdbuf, (void *)"oem led", 7) == 0) {
if ((*(cmdbuf + 7) >= '1') && (*(cmdbuf + 7) <= '4')) {
int led;
led = *(cmdbuf + 7) - '0';
if (memcmp(cmdbuf + 8, (void *)" on", 3) == 0) {
gpio_set_value(31 + led, 1);
tx_status("OKAY");
rx_cmd();
return;
} else if (memcmp(cmdbuf + 8, (void *)" off", 4) == 0) {
gpio_set_value(31 + led, 0);
tx_status("OKAY");
rx_cmd();
return;
}
}
}
tx_status("FAILinvalid command");
rx_cmd();
}
static void usbloader_init(void)
{
VERBOSE("enter usbloader_init\n");
/*usb sw and hw init*/
init_usb();
/*alloc and init sth for transfer*/
ep1in.num = BULK_IN_EP;
ep1in.in = 1;
ep1in.req = NULL;
ep1in.maxpkt = MAX_PACKET_LEN;
ep1in.next = &ep1in;
ep1out.num = BULK_OUT_EP;
ep1out.in = 0;
ep1out.req = NULL;
ep1out.maxpkt = MAX_PACKET_LEN;
ep1out.next = &ep1out;
cmdbuf = (char *)(rx_req.buf);
VERBOSE("exit usbloader_init\n");
}
void usb_reinit()
{
if (usb_need_reset)
{
usb_need_reset = 0;
init_usb();
}
}
void usb_download(void)
{
usbloader_init();
INFO("Enter downloading mode. Please run fastboot command on Host.\n");
for (;;) {
usb_poll();
usb_reinit();
}
}