/* Copyright 2016 The Chromium OS Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "clock.h"
#include "common.h"
#include "config.h"
#include "console.h"
#include "flash.h"
#include "gpio.h"
#include "hooks.h"
#include "link_defs.h"
#include "registers.h"
#include "usb_dwc_hw.h"
#include "system.h"
#include "task.h"
#include "timer.h"
#include "util.h"
#include "usb_descriptor.h"
#include "watchdog.h"


/****************************************************************************/
/* Debug output */

/* Console output macro */
#define CPRINTS(format, args...) cprints(CC_USB, format, ## args)
#define CPRINTF(format, args...) cprintf(CC_USB, format, ## args)

/* TODO: Something unexpected happened. Figure out how to report & fix it. */
#define report_error(val)						\
	CPRINTS("Unhandled USB event at %s line %d: 0x%x",		\
		__FILE__, __LINE__, val)


/****************************************************************************/
/* Standard USB stuff */

#ifdef CONFIG_USB_BOS
/* v2.01 (vs 2.00) BOS Descriptor provided */
#define USB_DEV_BCDUSB 0x0201
#else
#define USB_DEV_BCDUSB 0x0200
#endif

#ifndef USB_DEV_CLASS
#define USB_DEV_CLASS USB_CLASS_PER_INTERFACE
#endif

#ifndef CONFIG_USB_BCD_DEV
#define CONFIG_USB_BCD_DEV 0x0100		/* 1.00 */
#endif

#ifndef USB_BMATTRIBUTES
#ifdef CONFIG_USB_SELF_POWERED
#define USB_BMATTRIBUTES 0xc0  /* Self powered. */
#else
#define USB_BMATTRIBUTES 0x80  /* Bus powered. */
#endif
#endif

#ifndef CONFIG_USB_SERIALNO
#define USB_STR_SERIALNO 0
#else
static int usb_load_serial(void);
#endif


/* USB Standard Device Descriptor */
static const struct usb_device_descriptor dev_desc = {
	.bLength = USB_DT_DEVICE_SIZE,
	.bDescriptorType = USB_DT_DEVICE,
	.bcdUSB = USB_DEV_BCDUSB,
	.bDeviceClass = USB_DEV_CLASS,
	.bDeviceSubClass = 0x00,
	.bDeviceProtocol = 0x00,
	.bMaxPacketSize0 = USB_MAX_PACKET_SIZE,
	.idVendor = USB_VID_GOOGLE,
	.idProduct = CONFIG_USB_PID,
	.bcdDevice = CONFIG_USB_BCD_DEV,
	.iManufacturer = USB_STR_VENDOR,
	.iProduct = USB_STR_PRODUCT,
	.iSerialNumber = USB_STR_SERIALNO,
	.bNumConfigurations = 1
};

/* USB Configuration Descriptor */
const struct usb_config_descriptor USB_CONF_DESC(conf) = {
	.bLength = USB_DT_CONFIG_SIZE,
	.bDescriptorType = USB_DT_CONFIGURATION,
	.wTotalLength = 0x0BAD,	 /* number of returned bytes, set at runtime */
	.bNumInterfaces = USB_IFACE_COUNT,
	.bConfigurationValue = 1,		/* Caution: hard-coded value */
	.iConfiguration = USB_STR_VERSION,
	.bmAttributes = USB_BMATTRIBUTES, /* bus or self powered */
	.bMaxPower = (CONFIG_USB_MAXPOWER_MA / 2),
};

/* Qualifier Descriptor */
static const struct usb_qualifier_descriptor qualifier_desc = {
	.bLength = USB_DT_QUALIFIER_SIZE,
	.bDescriptorType = USB_DT_DEVICE_QUALIFIER,
	.bcdUSB = USB_DEV_BCDUSB,
	.bDeviceClass  = USB_DEV_CLASS,
	.bDeviceSubClass = 0x00,
	.bDeviceProtocol = 0x00,
	.bMaxPacketSize0 = USB_MAX_PACKET_SIZE,
	.bNumConfigurations = 1,
	.bReserved = 0,
};

const uint8_t usb_string_desc[] = {
	4,					/* Descriptor size */
	USB_DT_STRING,
	0x09, 0x04			    /* LangID = 0x0409: U.S. English */
};

/****************************************************************************/
/* Packet-handling stuff, specific to this SoC */

/* Some internal state to keep track of what's going on */
static enum {
	WAITING_FOR_SETUP_PACKET,
	DATA_STAGE_IN,
	NO_DATA_STAGE,
} what_am_i_doing;

#ifdef DEBUG_ME
static const char * const wat[3] = {
	[WAITING_FOR_SETUP_PACKET] = "wait_for_setup",
	[DATA_STAGE_IN] = "data_in",
	[NO_DATA_STAGE] = "no_data",
};
#endif

/* Programmer's Guide, Table 10-7 */
enum table_case {
	BAD_0,
	TABLE_CASE_COMPLETE,
	TABLE_CASE_SETUP,
	TABLE_CASE_WTF,
	TABLE_CASE_D,
	TABLE_CASE_E,
	BAD_6,
	BAD_7,
};

static enum table_case decode_table_10_7(uint32_t doepint)
{
	enum table_case val = BAD_0;

	/* Bits: SI, SPD, IOC */
	if (doepint & DOEPINT_XFERCOMPL)
		val += 1;
	if (doepint & DOEPINT_SETUP)
		val += 2;
	return val;
}

/* For STATUS/OUT: Use two DMA descriptors, each with one-packet buffers */
#define NUM_OUT_BUFFERS 2
static uint8_t ep0_setup_buf[USB_MAX_PACKET_SIZE];

/* For IN: Several DMA descriptors, all pointing into one large buffer, so that
 * we can return the configuration descriptor as one big blob.
 */
#define NUM_IN_PACKETS_AT_ONCE 4
#define IN_BUF_SIZE (NUM_IN_PACKETS_AT_ONCE * USB_MAX_PACKET_SIZE)
static uint8_t ep0_in_buf[IN_BUF_SIZE];

struct dwc_usb_ep ep0_ctl = {
	.max_packet = USB_MAX_PACKET_SIZE,
	.tx_fifo = 0,
	.out_pending = 0,
	.out_expected = 0,
	.out_data = 0,
	.out_databuffer = ep0_setup_buf,
	.out_databuffer_max = sizeof(ep0_setup_buf),
	.rx_deferred = 0,
	.in_packets = 0,
	.in_pending = 0,
	.in_data = 0,
	.in_databuffer = ep0_in_buf,
	.in_databuffer_max = sizeof(ep0_in_buf),
	.tx_deferred = 0,
};

/* Overall device state (USB 2.0 spec, section 9.1.1).
 * We only need a few, though.
 */
static enum {
	DS_DEFAULT,
	DS_ADDRESS,
	DS_CONFIGURED,
} device_state;
static uint8_t configuration_value;


/* True if the HW Rx/OUT FIFO is currently listening. */
int rx_ep_is_active(uint32_t ep_num)
{
	return (GR_USB_DOEPCTL(ep_num) & DXEPCTL_EPENA) ? 1 : 0;
}

/* Number of bytes the HW Rx/OUT FIFO has for us.
 *
 * @param ep_num	USB endpoint
 *
 * @returns		number of bytes ready, zero if none.
 */
int rx_ep_pending(uint32_t ep_num)
{
	struct dwc_usb_ep *ep = usb_ctl.ep[ep_num];

	return ep->out_pending;
}

/* True if the Tx/IN FIFO can take some bytes from us. */
int tx_ep_is_ready(uint32_t ep_num)
{
	struct dwc_usb_ep *ep = usb_ctl.ep[ep_num];
	int ready;

	/* Is the tx hw idle? */
	ready = !(GR_USB_DIEPCTL(ep_num) & DXEPCTL_EPENA);

	/* Is there no pending data? */
	ready &= (ep->in_pending == 0);
	return ready;
}

/* Write packets of data IN to the host.
 *
 * This function uses DMA, so the *data write buffer
 * must persist until the write completion event.
 *
 * @param ep_num	USB endpoint to write
 * @param len		number of bytes to write
 * @param data		pointer of data to write
 *
 * @return		bytes written
 */
int usb_write_ep(uint32_t ep_num, int len, void *data)
{
	struct dwc_usb_ep *ep = usb_ctl.ep[ep_num];

	if (GR_USB_DIEPCTL(ep_num) & DXEPCTL_EPENA) {
		CPRINTS("usb_write_ep ep%d: FAIL: tx already in progress!");
		return 0;
	}

	/* We will send as many packets as necessary, including a final
	 * packet of < USB_MAX_PACKET_SIZE (maybe zero length)
	 */
	ep->in_packets = (len + USB_MAX_PACKET_SIZE - 1) / USB_MAX_PACKET_SIZE;
	ep->in_pending = len;
	ep->in_data = data;

	GR_USB_DIEPTSIZ(ep_num) = 0;

	GR_USB_DIEPTSIZ(ep_num) |= DXEPTSIZ_PKTCNT(ep->in_packets);
	GR_USB_DIEPTSIZ(ep_num) |= DXEPTSIZ_XFERSIZE(len);
	GR_USB_DIEPDMA(ep_num) = (uint32_t)(ep->in_data);

	/* We could support longer multi-dma transfers here. */
	ep->in_pending -= len;
	ep->in_packets -= ep->in_packets;
	ep->in_data += len;

	/* We are ready to enable this endpoint to start transferring data. */
	GR_USB_DIEPCTL(ep_num) |= DXEPCTL_CNAK | DXEPCTL_EPENA;
	return len;
}

/* Tx/IN interrupt handler */
void usb_epN_tx(uint32_t ep_num)
{
	struct dwc_usb_ep *ep = usb_ctl.ep[ep_num];
	uint32_t dieptsiz = GR_USB_DIEPTSIZ(ep_num);

	if (GR_USB_DIEPCTL(ep_num) & DXEPCTL_EPENA) {
		CPRINTS("usb_epN_tx ep%d: tx still active.", ep_num);
		return;
	}

	/* clear the Tx/IN interrupts */
	GR_USB_DIEPINT(ep_num) = 0xffffffff;

	/*
	 * Let's assume this is actually true.
	 * We could support multi-dma transfers here.
	 */
	ep->in_packets = 0;
	ep->in_pending = dieptsiz & GC_USB_DIEPTSIZ1_XFERSIZE_MASK;

	if (ep->tx_deferred)
		hook_call_deferred(ep->tx_deferred, 0);
}

/* Read a packet of data OUT from the host.
 *
 * This function uses DMA, so the *data write buffer
 * must persist until the read completion event.
 *
 * @param ep_num	USB endpoint to read
 * @param len		number of bytes to read
 * @param data		pointer of data to read
 *
 * @return		EC_SUCCESS on success
 */
int usb_read_ep(uint32_t ep_num, int len, void *data)
{
	struct dwc_usb_ep *ep = usb_ctl.ep[ep_num];
	int packets = (len + USB_MAX_PACKET_SIZE - 1) / USB_MAX_PACKET_SIZE;

	ep->out_data = data;
	ep->out_pending = 0;
	ep->out_expected = len;

	GR_USB_DOEPTSIZ(ep_num) = 0;
	GR_USB_DOEPTSIZ(ep_num) |= DXEPTSIZ_PKTCNT(packets);
	GR_USB_DOEPTSIZ(ep_num) |= DXEPTSIZ_XFERSIZE(len);
	GR_USB_DOEPDMA(ep_num) = (uint32_t)ep->out_data;

	GR_USB_DOEPCTL(ep_num) |= DXEPCTL_CNAK | DXEPCTL_EPENA;
	return EC_SUCCESS;
}

/* Rx/OUT endpoint interrupt handler */
void usb_epN_rx(uint32_t ep_num)
{
	struct dwc_usb_ep *ep = usb_ctl.ep[ep_num];

	/* Still receiving data. Let's wait. */
	if (rx_ep_is_active(ep_num))
		return;

	/* Bytes received decrement DOEPTSIZ XFERSIZE */
	if (GR_USB_DOEPINT(ep_num) & DOEPINT_XFERCOMPL) {
		if (ep->out_expected > 0) {
			ep->out_pending =
				ep->out_expected -
				(GR_USB_DOEPTSIZ(ep_num) &
				 GC_USB_DOEPTSIZ1_XFERSIZE_MASK);
		} else {
			CPRINTF("usb_ep%d_rx: unexpected RX DOEPTSIZ %08x\n",
				ep_num, GR_USB_DOEPTSIZ(ep_num));
			ep->out_pending = 0;
		}
		ep->out_expected = 0;
		GR_USB_DOEPTSIZ(ep_num) = 0;
	}

	/* clear the RX/OUT interrupts */
	GR_USB_DOEPINT(ep_num) = 0xffffffff;

	if (ep->rx_deferred)
		hook_call_deferred(ep->rx_deferred, 0);
}

/* Reset endpoint HW block. */
void epN_reset(uint32_t ep_num)
{
	GR_USB_DOEPCTL(ep_num) = DXEPCTL_MPS(USB_MAX_PACKET_SIZE) |
		DXEPCTL_USBACTEP | DXEPCTL_EPTYPE_BULK;
	GR_USB_DIEPCTL(ep_num) = DXEPCTL_MPS(USB_MAX_PACKET_SIZE) |
		DXEPCTL_USBACTEP | DXEPCTL_EPTYPE_BULK |
		DXEPCTL_TXFNUM(ep_num);
	GR_USB_DAINTMSK |= DAINT_INEP(ep_num) |
			   DAINT_OUTEP(ep_num);
}


/******************************************************************************
 * Internal and EP0 functions.
 */


static void flush_all_fifos(void)
{
	/* Flush all FIFOs according to Section 2.1.1.2 */
	GR_USB_GRSTCTL = GRSTCTL_TXFNUM(0x10) | GRSTCTL_TXFFLSH
		| GRSTCTL_RXFFLSH;
	while (GR_USB_GRSTCTL & (GRSTCTL_TXFFLSH | GRSTCTL_RXFFLSH))
		;
}

int send_in_packet(uint32_t ep_num)
{
	struct dwc_usb *usb = &usb_ctl;
	struct dwc_usb_ep *ep = usb->ep[ep_num];
	int len = MIN(USB_MAX_PACKET_SIZE, ep->in_pending);

	if (ep->in_packets == 0) {
		report_error(ep_num);
		return -1;
	}

	GR_USB_DIEPTSIZ(ep_num) = 0;

	GR_USB_DIEPTSIZ(ep_num) |= DXEPTSIZ_PKTCNT(1);
	GR_USB_DIEPTSIZ(0) |= DXEPTSIZ_XFERSIZE(len);
	GR_USB_DIEPDMA(0) = (uint32_t)ep->in_data;


	/* We're sending this much. */
	ep->in_pending -= len;
	ep->in_packets -= 1;
	ep->in_data += len;

	/* We are ready to enable this endpoint to start transferring data. */
	return len;
}


/* Load the EP0 IN FIFO buffer with some data (zero-length works too). Returns
 * len, or negative on error.
 */
int initialize_in_transfer(const void *source, uint32_t len)
{
	struct dwc_usb *usb = &usb_ctl;
	struct dwc_usb_ep *ep = usb->ep[0];

#ifdef CONFIG_USB_DWC_FS
	/* FS OTG port does not support DMA or external phy */
	ASSERT(!(usb->dma_en));
	ASSERT(usb->phy_type == USB_PHY_INTERNAL);
	ASSERT(usb->speed == USB_SPEED_FS);
	ASSERT(usb->irq == STM32_IRQ_OTG_FS);
#else
	/* HS OTG port requires an external phy to support HS */
	ASSERT(!((usb->phy_type == USB_PHY_INTERNAL) &&
		(usb->speed == USB_SPEED_HS)));
	ASSERT(usb->irq == STM32_IRQ_OTG_HS);
#endif

	/* Copy the data into our FIFO buffer */
	if (len >= IN_BUF_SIZE) {
		report_error(len);
		return -1;
	}

	/* Stage data in DMA buffer. */
	memcpy(ep->in_databuffer, source, len);
	ep->in_data = ep->in_databuffer;

	/* We will send as many packets as necessary, including a final
	 * packet of < USB_MAX_PACKET_SIZE (maybe zero length)
	 */
	ep->in_packets = (len + USB_MAX_PACKET_SIZE)/USB_MAX_PACKET_SIZE;
	ep->in_pending = len;

	send_in_packet(0);
	return len;
}

/* Prepare the EP0 OUT FIFO buffer to accept some data. Returns len, or
 * negative on error.
 */
int accept_out_fifo(uint32_t len)
{
	/* TODO: This is not yet implemented */
	report_error(len);
	return -1;
}

/* The next packet from the host should be a Setup packet. Get ready for it. */
static void expect_setup_packet(void)
{
	struct dwc_usb *usb = &usb_ctl;
	struct dwc_usb_ep *ep = usb->ep[0];

	what_am_i_doing = WAITING_FOR_SETUP_PACKET;
	ep->out_data = ep->out_databuffer;

	/* We don't care about IN packets right now, only OUT. */
	GR_USB_DAINTMSK |= DAINT_OUTEP(0);
	GR_USB_DAINTMSK &= ~DAINT_INEP(0);

	GR_USB_DOEPTSIZ(0) = 0;
	GR_USB_DOEPTSIZ(0) |= DXEPTSIZ_PKTCNT(1);
	GR_USB_DOEPTSIZ(0) |= DXEPTSIZ_XFERSIZE(0x18);
	GR_USB_DOEPTSIZ(0) |= DXEPTSIZ_SUPCNT(1);
	GR_USB_DOEPCTL(0) = DXEPCTL_USBACTEP | DXEPCTL_EPENA;
	GR_USB_DOEPDMA(0) = (uint32_t)ep->out_data;
}

/* We're complaining about something by stalling both IN and OUT packets,
 * but a SETUP packet will get through anyway, so prepare for it.
 */
static void stall_both_fifos(void)
{
	what_am_i_doing = WAITING_FOR_SETUP_PACKET;
	/* We don't care about IN packets right now, only OUT. */
	GR_USB_DAINTMSK |= DAINT_OUTEP(0);
	GR_USB_DAINTMSK &= ~DAINT_INEP(0);

	GR_USB_DOEPCTL(0) |= DXEPCTL_STALL;
	GR_USB_DIEPCTL(0) |= DXEPCTL_STALL;
	expect_setup_packet();
}

/* The TX FIFO buffer is loaded. Start the Data phase. */
static void expect_data_phase_in(enum table_case tc)
{
	what_am_i_doing = DATA_STAGE_IN;

	/* Send the reply (data phase in) */
	if (tc == TABLE_CASE_SETUP)
		GR_USB_DIEPCTL(0) |= DXEPCTL_USBACTEP |
					DXEPCTL_CNAK | DXEPCTL_EPENA;
	else
		GR_USB_DIEPCTL(0) |= DXEPCTL_EPENA;

	/* We'll receive an empty packet back as a ack, I guess. */
	if (tc == TABLE_CASE_SETUP)
		GR_USB_DOEPCTL(0) |= DXEPCTL_CNAK | DXEPCTL_EPENA;
	else
		GR_USB_DOEPCTL(0) |= DXEPCTL_EPENA;

	/* Get an interrupt when either IN or OUT arrives */
	GR_USB_DAINTMSK |= (DAINT_OUTEP(0) | DAINT_INEP(0));

}

static void expect_data_phase_out(enum table_case tc)
{
	/* TODO: This is not yet supported */
	report_error(tc);
	expect_setup_packet();
}

/* No Data phase, just Status phase (which is IN, since Setup is OUT) */
static void expect_status_phase_in(enum table_case tc)
{
	what_am_i_doing = NO_DATA_STAGE;

	/* Expect a zero-length IN for the Status phase */
	(void) initialize_in_transfer(0, 0);

	/* Blindly following instructions here, too. */
	if (tc == TABLE_CASE_SETUP)
		GR_USB_DIEPCTL(0) |= DXEPCTL_USBACTEP
			| DXEPCTL_CNAK | DXEPCTL_EPENA;
	else
		GR_USB_DIEPCTL(0) |= DXEPCTL_EPENA;

	/* Get an interrupt when either IN or OUT arrives */
	GR_USB_DAINTMSK |= (DAINT_OUTEP(0) | DAINT_INEP(0));
}

/* Handle a Setup packet that expects us to send back data in reply. Return the
 * length of the data we're returning, or negative to indicate an error.
 */
static int handle_setup_with_in_stage(enum table_case tc,
				      struct usb_setup_packet *req)
{
	struct dwc_usb *usb = &usb_ctl;
	struct dwc_usb_ep *ep = usb->ep[0];

	const void *data = 0;
	uint32_t len = 0;
	int ugly_hack = 0;
	static const uint16_t zero;		/* == 0 */

	switch (req->bRequest) {
	case USB_REQ_GET_DESCRIPTOR: {
		uint8_t type = req->wValue >> 8;
		uint8_t idx = req->wValue & 0xff;

		switch (type) {
		case USB_DT_DEVICE:
			data = &dev_desc;
			len = sizeof(dev_desc);
			break;
		case USB_DT_CONFIGURATION:
			data = __usb_desc;
			len = USB_DESC_SIZE;
			ugly_hack = 1;		/* see below */
			break;
#ifdef CONFIG_USB_BOS
		case USB_DT_BOS:
			data = bos_ctx.descp;
			len = bos_ctx.size;
			break;
#endif
		case USB_DT_STRING:
			if (idx >= USB_STR_COUNT)
				return -1;
#ifdef CONFIG_USB_SERIALNO
			if (idx == USB_STR_SERIALNO)
				data = (uint8_t *)usb_serialno_desc;
			else
#endif
				data = usb_strings[idx];
			len = *(uint8_t *)data;
			break;
		case USB_DT_DEVICE_QUALIFIER:
			/* We're not high speed */
			return -1;
		case USB_DT_DEBUG:
			/* Not supported */
			return -1;
		default:
			report_error(type);
			return -1;
		}
		break;
	}
	case USB_REQ_GET_STATUS: {
		/* TODO: Device Status: Remote Wakeup? Self Powered? */
		data = &zero;
		len = sizeof(zero);
		break;
	}
	case USB_REQ_GET_CONFIGURATION:
		data = &configuration_value;
		len = sizeof(configuration_value);
		break;

	case USB_REQ_SYNCH_FRAME:
		/* Unimplemented */
		return -1;

	default:
		report_error(req->bRequest);
		return -1;
	}

	/* Don't send back more than we were asked for. */
	len = MIN(req->wLength, len);

	/* Prepare the TX FIFO. If we haven't preallocated enough room in the
	 * TX FIFO for the largest reply, we'll have to stall. This is a bug in
	 * our code, but detecting it easily at compile time is related to the
	 * ugly_hack directly below.
	 */
	if (initialize_in_transfer(data, len) < 0)
		return -1;

	if (ugly_hack) {
		/*
		 * TODO: Somebody figure out how to fix this, please.
		 *
		 * The USB configuration descriptor request is unique in that
		 * it not only returns the configuration descriptor, but also
		 * all the interface descriptors and all their endpoint
		 * descriptors as one enormous blob. We've set up some macros
		 * so we can declare and implement separate interfaces in
		 * separate files just by compiling them, and all the relevant
		 * descriptors are sorted and bundled up by the linker. But the
		 * total length of the entire blob needs to appear in the first
		 * configuration descriptor struct and because we don't know
		 * that value until after linking, it can't be initialized as a
		 * constant. So we have to compute it at run-time and shove it
		 * in here, which also means that we have to copy the whole
		 * blob into our TX FIFO buffer so that it's mutable. Otherwise
		 * we could just point at it (or pretty much any other constant
		 * struct that we wanted to send to the host). Bah.
		 */
		struct usb_config_descriptor *cfg =
			(struct usb_config_descriptor *)ep->in_databuffer;
		/* set the real descriptor size */
		cfg->wTotalLength = USB_DESC_SIZE;
	}

	return len;
}

/* Handle a Setup that comes with additional data for us. */
static int handle_setup_with_out_stage(enum table_case tc,
					struct usb_setup_packet *req)
{
	/* TODO: We don't support any of these. We should. */
	report_error(-1);
	return -1;
}

/* Some Setup packets don't have a data stage at all. */
static int handle_setup_with_no_data_stage(enum table_case tc,
					   struct usb_setup_packet *req)
{
	uint8_t set_addr;

	switch (req->bRequest) {
	case USB_REQ_SET_ADDRESS:
		/*
		 * Set the address after the IN packet handshake.
		 *
		 * From the USB 2.0 spec, section 9.4.6:
		 *
		 * As noted elsewhere, requests actually may result in
		 * up to three stages. In the first stage, the Setup
		 * packet is sent to the device. In the optional second
		 * stage, data is transferred between the host and the
		 * device. In the final stage, status is transferred
		 * between the host and the device. The direction of
		 * data and status transfer depends on whether the host
		 * is sending data to the device or the device is
		 * sending data to the host. The Status stage transfer
		 * is always in the opposite direction of the Data
		 * stage. If there is no Data stage, the Status stage
		 * is from the device to the host.
		 *
		 * Stages after the initial Setup packet assume the
		 * same device address as the Setup packet. The USB
		 * device does not change its device address until
		 * after the Status stage of this request is completed
		 * successfully. Note that this is a difference between
		 * this request and all other requests. For all other
		 * requests, the operation indicated must be completed
		 * before the Status stage
		 */
		set_addr = req->wValue & 0xff;
		/*
		 * NOTE: Now that we've said that, we don't do it. The
		 * hardware for this SoC knows that an IN packet will
		 * be following the SET ADDRESS, so it waits until it
		 * sees that happen before the address change takes
		 * effect. If we wait until after the IN packet to
		 * change the register, the hardware gets confused and
		 * doesn't respond to anything.
		 */
		GWRITE_FIELD(USB, DCFG, DEVADDR, set_addr);
		CPRINTS("SETAD 0x%02x (%d)", set_addr, set_addr);
		device_state = DS_ADDRESS;
		break;

	case USB_REQ_SET_CONFIGURATION:
		switch (req->wValue) {
		case 0:
			configuration_value = req->wValue;
			device_state = DS_ADDRESS;
			break;
		case 1:	    /* Caution: Only one config descriptor TODAY */
			/* TODO: All endpoints set to DATA0 toggle state */
			configuration_value = req->wValue;
			device_state = DS_CONFIGURED;
			break;
		default:
			/* Nope. That's a paddlin. */
			report_error(-1);
			return -1;
		}
		break;

	case USB_REQ_CLEAR_FEATURE:
	case USB_REQ_SET_FEATURE:
		/* TODO: Handle DEVICE_REMOTE_WAKEUP, ENDPOINT_HALT? */
		break;

	default:
		/* Anything else is unsupported */
		report_error(-1);
		return -1;
	}

	/* No data to transfer, go straight to the Status phase. */
	return 0;
}

/* Dispatch an incoming Setup packet according to its type */
static void handle_setup(enum table_case tc)
{
	struct dwc_usb *usb = &usb_ctl;
	struct dwc_usb_ep *ep = usb->ep[0];
	struct usb_setup_packet *req =
		(struct usb_setup_packet *)ep->out_databuffer;
	int data_phase_in = req->bmRequestType & USB_DIR_IN;
	int data_phase_out = !data_phase_in && req->wLength;
	int bytes = -1;				/* default is to stall */

	if (0 == (req->bmRequestType & (USB_TYPE_MASK | USB_RECIP_MASK))) {
		/* Standard Device requests */
		if (data_phase_in)
			bytes = handle_setup_with_in_stage(tc, req);
		else if (data_phase_out)
			bytes = handle_setup_with_out_stage(tc, req);
		else
			bytes = handle_setup_with_no_data_stage(tc, req);
	} else if (USB_RECIP_INTERFACE ==
		   (req->bmRequestType & USB_RECIP_MASK)) {
		/* Interface-specific requests */
		uint8_t iface = req->wIndex & 0xff;

		if (iface < USB_IFACE_COUNT)
			bytes = usb_iface_request[iface](req);
	} else {
		/* Something we need to add support for? */
		report_error(-1);
	}

	/* We say "no" to unsupported and intentionally unhandled requests by
	 * stalling the Data and/or Status stage.
	 */
	if (bytes < 0) {
		/* Stall both IN and OUT. SETUP will come through anyway. */
		stall_both_fifos();
	} else {
		if (data_phase_in)
			expect_data_phase_in(tc);
		else if (data_phase_out)
			expect_data_phase_out(tc);
		else
			expect_status_phase_in(tc);
	}
}

/* This handles both IN and OUT interrupts for EP0 */
static void ep0_interrupt(uint32_t intr_on_out, uint32_t intr_on_in)
{
	struct dwc_usb *usb = &usb_ctl;
	struct dwc_usb_ep *ep = usb->ep[0];
	uint32_t doepint, diepint;
	enum table_case tc;
	int out_complete, out_setup, in_complete;

	/* Determine the interrupt cause and clear the bits quickly, but only
	 * if they really apply. I don't think they're trustworthy if we didn't
	 * actually get an interrupt.
	 */
	doepint = GR_USB_DOEPINT(0) & GR_USB_DOEPMSK;
	if (intr_on_out)
		GR_USB_DOEPINT(0) = doepint;
	diepint = GR_USB_DIEPINT(0) & GR_USB_DIEPMSK;
	if (intr_on_in)
		GR_USB_DIEPINT(0) = diepint;

	out_complete = doepint & DOEPINT_XFERCOMPL;
	out_setup = doepint & DOEPINT_SETUP;
	in_complete = diepint & DIEPINT_XFERCOMPL;

	/* Decode the situation according to Table 10-7 */
	tc = decode_table_10_7(doepint);

	switch (what_am_i_doing) {
	case WAITING_FOR_SETUP_PACKET:
		if (out_setup)
			handle_setup(tc);
		else
			report_error(-1);
		break;

	case DATA_STAGE_IN:
		if (intr_on_in && in_complete) {
			/* A packet is sent. Should we send another? */
			if (ep->in_packets > 0) {
				/* Send another packet. */
				send_in_packet(0);
				expect_data_phase_in(tc);
			}
		}

		/* But we should ignore the OUT endpoint if we didn't actually
		 * get an OUT interrupt.
		 */
		if (!intr_on_out)
			break;

		if (out_setup) {
			/* The first IN packet has been seen. Keep going. */
			break;
		}
		if (out_complete) {
			/* We've handled the Status phase. All done. */
			expect_setup_packet();
			break;
		}

		/* Anything else should be ignorable. Right? */
		break;

	case NO_DATA_STAGE:
		if (intr_on_in && in_complete) {
			/* We are not expecting an empty packet in
			 * return for our empty packet.
			 */
			expect_setup_packet();
		}

		/* Done unless we got an OUT interrupt */
		if (!intr_on_out)
			break;

		if (out_setup) {
			report_error(-1);
			break;
		}

		/* Anything else means get ready for a Setup packet */
		report_error(-1);
		expect_setup_packet();
		break;
	}
}

/****************************************************************************/
/* USB device initialization and shutdown routines */

/*
 * DATA FIFO Setup. There is an internal SPRAM used to buffer the IN/OUT
 * packets and track related state without hammering the AHB and system RAM
 * during USB transactions. We have to specify where and how much of that SPRAM
 * to use for what.
 *
 * See Programmer's Guide chapter 2, "Calculating FIFO Size".
 * We're using Dedicated TxFIFO Operation, without enabling thresholding.
 *
 * Section 2.1.1.2, page 30: RXFIFO size is the same as for Shared FIFO, which
 * is Section 2.1.1.1, page 28. This is also the same as Method 2 on page 45.
 *
 * We support up to 3 control EPs, no periodic IN EPs, up to 16 TX EPs. Max
 * data packet size is 64 bytes. Total SPRAM available is 1024 slots.
 */
#define MAX_CONTROL_EPS   3
#define MAX_NORMAL_EPS    16
#define FIFO_RAM_DEPTH    1024
/*
 * Device RX FIFO size is thus:
 *   (4 * 3 + 6) + 2 * ((64 / 4) + 1) + (2 * 16) + 1 == 85
 */
#define RXFIFO_SIZE  ((4 * MAX_CONTROL_EPS + 6) + \
		      2 * ((USB_MAX_PACKET_SIZE / 4) + 1) + \
		      (2 * MAX_NORMAL_EPS) + 1)
/*
 * Device TX FIFO size is 2 * (64 / 4) == 32 for each IN EP (Page 46).
 */
#define TXFIFO_SIZE  (2 * (USB_MAX_PACKET_SIZE / 4))
/*
 * We need 4 slots per endpoint direction for endpoint status stuff (Table 2-1,
 * unconfigurable).
 */
#define EP_STATUS_SIZE (4 * MAX_NORMAL_EPS * 2)
/*
 * Make sure all that fits.
 */
BUILD_ASSERT(RXFIFO_SIZE + TXFIFO_SIZE * MAX_NORMAL_EPS + EP_STATUS_SIZE <
	     FIFO_RAM_DEPTH);


/* Now put those constants into the correct registers */
static void setup_data_fifos(void)
{
	int i;

	/* Programmer's Guide, p31 */
	GR_USB_GRXFSIZ = RXFIFO_SIZE;			      /* RXFIFO */
	GR_USB_GNPTXFSIZ = (TXFIFO_SIZE << 16) | RXFIFO_SIZE; /* TXFIFO 0 */

	/* TXFIFO 1..15 */
	for (i = 1; i < MAX_NORMAL_EPS; i++)
		GR_USB_DIEPTXF(i) = ((TXFIFO_SIZE << 16) |
				     (RXFIFO_SIZE + i * TXFIFO_SIZE));

	/*
	 * TODO: The Programmer's Guide is confusing about when or whether to
	 * flush the FIFOs. Section 2.1.1.2 (p31) just says to flush. Section
	 * 2.2.2 (p55) says to stop all the FIFOs first, then flush. Section
	 * 7.5.4 (p162) says that flushing the RXFIFO at reset is not
	 * recommended at all.
	 *
	 * I'm also unclear on whether or not the individual EPs are expected
	 * to be disabled already (DIEPCTLn/DOEPCTLn.EPENA == 0), and if so,
	 * whether by firmware or hardware.
	 */

	/* Flush all FIFOs according to Section 2.1.1.2 */
	GR_USB_GRSTCTL = GRSTCTL_TXFNUM(0x10) | GRSTCTL_TXFFLSH
		| GRSTCTL_RXFFLSH;
	while (GR_USB_GRSTCTL & (GRSTCTL_TXFFLSH | GRSTCTL_RXFFLSH))
		;				/* TODO: timeout 100ms */
}

static void usb_init_endpoints(void)
{
	int ep;

	/* Prepare to receive packets on EP0 */
	expect_setup_packet();

	/* Reset the other endpoints */
	for (ep = 1; ep < USB_EP_COUNT; ep++)
		usb_ep_reset[ep]();
}

static void usb_reset(void)
{
	/* Clear our internal state */
	device_state = DS_DEFAULT;
	configuration_value = 0;

	/* Clear the device address */
	GWRITE_FIELD(USB, DCFG, DEVADDR, 0);

	/* Reinitialize all the endpoints */
	usb_init_endpoints();
}

static void usb_resetdet(void)
{
	/* TODO: Same as normal reset, right? I think we only get this if we're
	 * suspended (sleeping) and the host resets us. Try it and see.
	 */
	usb_reset();
}

static void usb_enumdone(void)
{
	/* We can change to HS here. We will not go to HS today */
	GR_USB_DCTL |= DCTL_CGOUTNAK;
}


void usb_interrupt(void)
{
	uint32_t status = GR_USB_GINTSTS & GR_USB_GINTMSK;
	uint32_t oepint = status & GINTSTS(OEPINT);
	uint32_t iepint = status & GINTSTS(IEPINT);
	int ep;

	if (status & GINTSTS(ENUMDONE))
		usb_enumdone();

	if (status & GINTSTS(RESETDET))
		usb_resetdet();

	if (status & GINTSTS(USBRST))
		usb_reset();

	/* Endpoint interrupts */
	if (oepint || iepint) {
		/* Note: It seems that the DAINT bits are only trustworthy for
		 * identifying interrupts when selected by the corresponding
		 * OEPINT and IEPINT bits from GINTSTS.
		 */
		uint32_t daint = GR_USB_DAINT;

		/* EP0 has a combined IN/OUT handler. Only call it once, but
		 * let it know which direction(s) had an interrupt.
		 */
		if (daint & (DAINT_OUTEP(0) | DAINT_INEP(0))) {
			uint32_t intr_on_out = (oepint &&
						(daint & DAINT_OUTEP(0)));
			uint32_t intr_on_in = (iepint &&
						(daint & DAINT_INEP(0)));
			ep0_interrupt(intr_on_out, intr_on_in);
		}

		/* Invoke the unidirectional IN and OUT functions for the other
		 * endpoints. Each handler must clear their own bits in
		 * DIEPINTn/DOEPINTn.
		 */
		for (ep = 1; ep < USB_EP_COUNT; ep++) {
			if (oepint && (daint & DAINT_OUTEP(ep)))
				usb_ep_rx[ep]();
			if (iepint && (daint & DAINT_INEP(ep)))
				usb_ep_tx[ep]();
		}
	}

	GR_USB_GINTSTS = status;
}
DECLARE_IRQ(STM32_IRQ_OTG_FS, usb_interrupt, 1);
DECLARE_IRQ(STM32_IRQ_OTG_HS, usb_interrupt, 1);

static void usb_softreset(void)
{
	int timeout;

	CPRINTS("%s", __func__);

	/* Wait for bus idle */
	timeout = 10000;
	while (!(GR_USB_GRSTCTL & GRSTCTL_AHBIDLE) && timeout-- > 0)
		;

	/* Reset and wait for clear */
	GR_USB_GRSTCTL = GRSTCTL_CSFTRST;
	timeout = 10000;
	while ((GR_USB_GRSTCTL & GRSTCTL_CSFTRST) && timeout-- > 0)
		;
	if (GR_USB_GRSTCTL & GRSTCTL_CSFTRST) {
		CPRINTF("USB: reset failed\n");
		return;
	}

	/* Some more idle? */
	timeout = 10000;
	while (!(GR_USB_GRSTCTL & GRSTCTL_AHBIDLE) && timeout-- > 0)
		;

	if (!timeout) {
		CPRINTF("USB: reset timeout\n");
		return;
	}
	/* TODO: Wait 3 PHY clocks before returning */
}

void usb_connect(void)
{
	GR_USB_DCTL &= ~DCTL_SFTDISCON;
}

void usb_disconnect(void)
{
	GR_USB_DCTL |= DCTL_SFTDISCON;

	device_state = DS_DEFAULT;
	configuration_value = 0;
}

void usb_reset_init_phy(void)
{
	struct dwc_usb *usb = &usb_ctl;

	if (usb->phy_type == USB_PHY_ULPI) {
		GR_USB_GCCFG &= ~GCCFG_PWRDWN;
		GR_USB_GUSBCFG &= ~(GUSBCFG_TSDPS |
			GUSBCFG_ULPIFSLS | GUSBCFG_PHYSEL);
		GR_USB_GUSBCFG &= ~(GUSBCFG_ULPIEVBUSD | GUSBCFG_ULPIEVBUSI);
		/* No suspend */
		GR_USB_GUSBCFG |= GUSBCFG_ULPICSM | GUSBCFG_ULPIAR;

		usb_softreset();
	} else {
		GR_USB_GUSBCFG |= GUSBCFG_PHYSEL;
		usb_softreset();
		GR_USB_GCCFG |= GCCFG_PWRDWN;
	}
}

void usb_init(void)
{
	int i;
	struct dwc_usb *usb = &usb_ctl;

	CPRINTS("%s", __func__);

#ifdef CONFIG_USB_SERIALNO
	usb_load_serial();
#endif

	/* USB is in use */
	disable_sleep(SLEEP_MASK_USB_DEVICE);

	/* Enable clocks */
	clock_enable_module(MODULE_USB, 0);
	clock_enable_module(MODULE_USB, 1);

	/* TODO(crbug.com/496888): set up pinmux */
	gpio_config_module(MODULE_USB, 1);

	/* Make sure interrupts are disabled */
	GR_USB_GINTMSK = 0;
	GR_USB_DAINTMSK = 0;
	GR_USB_DIEPMSK = 0;
	GR_USB_DOEPMSK = 0;

	/* Full-Speed Serial PHY */
	usb_reset_init_phy();

	/* Global + DMA configuration */
	GR_USB_GAHBCFG = GAHBCFG_GLB_INTR_EN;
	GR_USB_GAHBCFG |= GAHBCFG_HBSTLEN_INCR4;
	if (usb->dma_en)
		GR_USB_GAHBCFG |= GAHBCFG_DMA_EN;

	/* Device only, no SRP */
	GR_USB_GUSBCFG |= GUSBCFG_FDMOD;
	GR_USB_GUSBCFG |= GUSBCFG_SRPCAP | GUSBCFG_HNPCAP;

	GR_USB_GCCFG &= ~GCCFG_VBDEN;
	GR_USB_GOTGCTL |= GOTGCTL_BVALOEN;
	GR_USB_GOTGCTL |= GOTGCTL_BVALOVAL;

	GR_USB_PCGCCTL = 0;

	if (usb->phy_type == USB_PHY_ULPI) {
		/* TODO(nsanders): add HS support like so.
		 * GR_USB_DCFG = (GR_USB_DCFG & ~GC_USB_DCFG_DEVSPD_MASK)
		 * | DCFG_DEVSPD_HSULPI;
		 */
		GR_USB_DCFG = (GR_USB_DCFG & ~GC_USB_DCFG_DEVSPD_MASK)
			| DCFG_DEVSPD_FSULPI;
	} else {
		GR_USB_DCFG = (GR_USB_DCFG & ~GC_USB_DCFG_DEVSPD_MASK)
			| DCFG_DEVSPD_FS48;
	}

	GR_USB_DCFG |= DCFG_NZLSOHSK;

	flush_all_fifos();

	/* Clear pending interrupts again */
	GR_USB_GINTMSK = 0;
	GR_USB_DIEPMSK = 0;
	GR_USB_DOEPMSK = 0;
	GR_USB_DAINT = 0xffffffff;
	GR_USB_DAINTMSK = 0;

	/* TODO: What about the AHB Burst Length Field? It's 0 now. */
	GR_USB_GAHBCFG |= GAHBCFG_TXFELVL | GAHBCFG_PTXFELVL;

	/* Device only, no SRP */
	GR_USB_GUSBCFG |= GUSBCFG_FDMOD
		| GUSBCFG_TOUTCAL(7)
		/* FIXME: Magic number! 14 is for 15MHz! Use 9 for 30MHz */
		| GUSBCFG_USBTRDTIM(14);

	/* Be in disconnected state until we are ready */
	usb_disconnect();

	/* If we've restored a nonzero device address, update our state. */
	if (GR_USB_DCFG & GC_USB_DCFG_DEVADDR_MASK) {
		/* Caution: We only have one config TODAY, so there's no real
		 * difference between DS_CONFIGURED and DS_ADDRESS.
		 */
		device_state = DS_CONFIGURED;
		configuration_value = 1;
	} else {
		device_state = DS_DEFAULT;
		configuration_value = 0;
	}

	/* Now that DCFG.DesDMA is accurate, prepare the FIFOs */
	setup_data_fifos();

	usb_init_endpoints();

	/* Clear any pending interrupts */
	for (i = 0; i < 16; i++) {
		GR_USB_DIEPINT(i) = 0xffffffff;
		GR_USB_DIEPTSIZ(i) = 0;
		GR_USB_DOEPINT(i) = 0xffffffff;
		GR_USB_DOEPTSIZ(i) = 0;
	}

	if (usb->dma_en) {
		GR_USB_DTHRCTL = DTHRCTL_TXTHRLEN_6 | DTHRCTL_RXTHRLEN_6;
		GR_USB_DTHRCTL |= DTHRCTL_RXTHREN | DTHRCTL_ISOTHREN
			| DTHRCTL_NONISOTHREN;
		i = GR_USB_DTHRCTL;
	}

	GR_USB_GINTSTS = 0xFFFFFFFF;

	GR_USB_GAHBCFG |= GAHBCFG_GLB_INTR_EN | GAHBCFG_TXFELVL
		| GAHBCFG_PTXFELVL;

	if (!(usb->dma_en))
		GR_USB_GINTMSK |= GINTMSK(RXFLVL);

	/* Unmask some endpoint interrupt causes */
	GR_USB_DIEPMSK = DIEPMSK_EPDISBLDMSK | DIEPMSK_XFERCOMPLMSK;
	GR_USB_DOEPMSK = DOEPMSK_EPDISBLDMSK | DOEPMSK_XFERCOMPLMSK |
		DOEPMSK_SETUPMSK;

	/* Enable interrupt handlers */
	task_enable_irq(usb->irq);

	/* Allow USB interrupts to come in */
	GR_USB_GINTMSK |=
		/* NAK bits that must be cleared by the DCTL register */
		GINTMSK(GOUTNAKEFF) | GINTMSK(GINNAKEFF) |
		/* Initialization events */
		GINTMSK(USBRST) | GINTMSK(ENUMDONE) |
		/* Reset detected while suspended. Need to wake up. */
		GINTMSK(RESETDET) |		/* TODO: Do we need this? */
		/* Idle, Suspend detected. Should go to sleep. */
		GINTMSK(ERLYSUSP) | GINTMSK(USBSUSP);

	GR_USB_GINTMSK |=
		/* Endpoint activity, cleared by the DOEPINT/DIEPINT regs */
		GINTMSK(OEPINT) | GINTMSK(IEPINT);

	/* Device registers have been setup */
	GR_USB_DCTL |= DCTL_PWRONPRGDONE;
	udelay(10);
	GR_USB_DCTL &= ~DCTL_PWRONPRGDONE;

	/* Clear global NAKs */
	GR_USB_DCTL |= DCTL_CGOUTNAK | DCTL_CGNPINNAK;

#ifndef CONFIG_USB_INHIBIT_CONNECT
	/* Indicate our presence to the USB host */
	usb_connect();
#endif
}
#ifndef CONFIG_USB_INHIBIT_INIT
DECLARE_HOOK(HOOK_INIT, usb_init, HOOK_PRIO_DEFAULT);
#endif

void usb_release(void)
{
	struct dwc_usb *usb = &usb_ctl;

	/* signal disconnect to host */
	usb_disconnect();

	/* disable interrupt handlers */
	task_disable_irq(usb->irq);

	/* disable clocks */
	clock_enable_module(MODULE_USB, 0);
	/* TODO: pin-mux */

	/* USB is off, so sleep whenever */
	enable_sleep(SLEEP_MASK_USB_DEVICE);
}

/* Print USB info and stats */
static void usb_info(void)
{
	struct dwc_usb *usb = &usb_ctl;
	int i;

	CPRINTF("USB settings: %s%s%s\n",
		usb->speed == USB_SPEED_FS ? "FS " : "HS ",
		usb->phy_type == USB_PHY_INTERNAL ? "Internal Phy " : "ULPI ",
		usb->dma_en ? "DMA " : "");

	for (i = 0; i < USB_EP_COUNT; i++) {
		CPRINTF("Endpoint %d activity: %s%s\n", i,
			rx_ep_is_active(i) ? "RX " : "",
			tx_ep_is_ready(i) ? "" : "TX ");
	}
}

static int command_usb(int argc, char **argv)
{
	if (argc > 1) {
		if (!strcasecmp("on", argv[1]))
			usb_init();
		else if (!strcasecmp("off", argv[1]))
			usb_release();
		else if (!strcasecmp("info", argv[1]))
			usb_info();
		return EC_SUCCESS;
	}

	return EC_ERROR_PARAM1;
}
DECLARE_CONSOLE_COMMAND(usb, command_usb,
			"[on|off|info]",
			"Get/set the USB connection state and PHY selection");

#ifdef CONFIG_USB_SERIALNO
/* This will be subbed into USB_STR_SERIALNO. */
struct usb_string_desc *usb_serialno_desc =
	USB_WR_STRING_DESC(DEFAULT_SERIALNO);

/* Update serial number */
static int usb_set_serial(const char *serialno)
{
	struct usb_string_desc *sd = usb_serialno_desc;
	int i;

	if (!serialno)
		return EC_ERROR_INVAL;

	/* Convert into unicode usb string desc. */
	for (i = 0; i < USB_STRING_LEN; i++) {
		sd->_data[i] = serialno[i];
		if (serialno[i] == 0)
			break;
	}
	/* Count wchars (w/o null terminator) plus size & type bytes. */
	sd->_len = (i * 2) + 2;
	sd->_type = USB_DT_STRING;

	return EC_SUCCESS;
}

/* Retrieve serial number from pstate flash. */
static int usb_load_serial(void)
{
	const char *serialno;
	int rv;

	serialno = flash_read_serial();
	if (!serialno)
		return EC_ERROR_ACCESS_DENIED;

	rv = usb_set_serial(serialno);
	return rv;
}


/* Save serial number into pstate region. */
static int usb_save_serial(const char *serialno)
{
	int rv;

	if (!serialno)
		return EC_ERROR_INVAL;

	/* Save this new serial number to flash. */
	rv = flash_write_serial(serialno);
	if (rv)
		return rv;

	/* Load this new serial number to memory. */
	rv = usb_load_serial();
	return rv;
}

static int command_serialno(int argc, char **argv)
{
	struct usb_string_desc *sd = usb_serialno_desc;
	char buf[USB_STRING_LEN];
	int rv = EC_SUCCESS;
	int i;

	if (argc != 1) {
		if ((strcasecmp(argv[1], "set") == 0) &&
		    (argc == 3)) {
			ccprintf("Saving serial number\n");
			rv = usb_save_serial(argv[2]);
		} else if ((strcasecmp(argv[1], "load") == 0) &&
			   (argc == 2)) {
			ccprintf("Loading serial number\n");
			rv = usb_load_serial();
		} else
			return EC_ERROR_INVAL;
	}

	for (i = 0; i < USB_STRING_LEN; i++)
		buf[i] = sd->_data[i];
	ccprintf("Serial number: %s\n", buf);
	return rv;
}

DECLARE_CONSOLE_COMMAND(serialno, command_serialno,
	"load/set [value]",
	"Read and write USB serial number");
#endif

