Re: MAX3421E Linux driver?

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Attached is v0.1 of the max3421 driver.  This one actually is trying to do
(mostly) the right things.  It's event-driven (no busy-waiting anymore).

The URB-scheduling implemented in max3421_next_ep() is still simplistic: it
runs all periodic transfers, then runs non-periodic transfers until there
is no more (useful) work to do or the next frame starts.

With this version of the driver:

   1. A USB-keyboard seems to work reliably (as per evtest anyhow).
   2. Several USB mass-storage devices get recognized correctly.  There are
   still some issues with reliably writing data, though.
   3. Some WIFI dongles are starting to work.

I'd be very interested in getting feedback on this driver as this is the
first time I tried my hands at a USB host-controller driver.

  --david

On Wed, Feb 12, 2014 at 4:56 PM, David Mosberger <davidm@xxxxxxxxxx> wrote:
> Well, here is a quick-and-dirty proof-of-concept.  Warning: it's ugly and
> you might go blind.  Having said that, the code works well enough to detect
> all USB devices I tried and HID devices as well as USB mass storage seem to
> work fine.
>
> I'm not terribly familiar with the details of USB and/or the Linux kernel's
> USB implementation the driver is probably doing a couple of really dumb
> things.  I marked a couple of places with "XXX" where I'm particularly
> interested in feedback.
>
> Also, one thing that seems tricky is NAK-handling: some of the devices we
> test with have large receive buffers and they end up returning NAK for a
> long time.  So the strategy as implemented is to allow up to 4 NAKs and
> after that, it sleeps for 1ms for each NAK.
>
>   --david
>
>
> On Thu, Jan 30, 2014 at 5:48 PM, Felipe Balbi <balbi@xxxxxx> wrote:
>>
>> Hi,
>>
>> On Thu, Jan 30, 2014 at 05:34:59PM -0700, David Mosberger wrote:
>> > We have a need to graft a USB Host controller onto a SPI bus (never
>> > mind the wisdom of doing that, we have little
>> > choice in this particular instance).
>> >
>> > The Cypress CY7C67300 would fit the bill and has a Linux driver, but
>> > is probably too complex and definitely
>> > too expensive for our needs.
>> >
>> > The Maxim MAX3421E is the other option, but it has no Linux driver.
>> > The chip basically implements OHCI over SPI
>> > rather than PCI.  Anybody know of any particular reason why there is
>> > no Linux driver?  Has anyone already written or
>> > attempted to write an OHCI-over-SPI driver for MAX3421E?
>>
>> I don't think anybody has attempted it. If you wanna do it, we're happy
>> to review the driver.
>>
>> cheers
>>
>> --
>> balbi
>
>
>
>
> --
> eGauge Systems LLC, http://egauge.net/, 1.877-EGAUGE1, fax 720.545.9768



-- 
eGauge Systems LLC, http://egauge.net/, 1.877-EGAUGE1, fax 720.545.9768
diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile
index f5ed3d7..2e270cc 100644
--- a/drivers/usb/Makefile
+++ b/drivers/usb/Makefile
@@ -13,6 +13,7 @@ obj-$(CONFIG_USB_DWC3)		+= dwc3/
 obj-$(CONFIG_USB_MON)		+= mon/
 
 obj-$(CONFIG_PCI)		+= host/
+obj-$(CONFIG_USB_MAX3421_HCD)	+= host/
 obj-$(CONFIG_USB_EHCI_HCD)	+= host/
 obj-$(CONFIG_USB_ISP116X_HCD)	+= host/
 obj-$(CONFIG_USB_OHCI_HCD)	+= host/
diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig
index d6bb128..5b226de 100644
--- a/drivers/usb/host/Kconfig
+++ b/drivers/usb/host/Kconfig
@@ -4,6 +4,16 @@
 comment "USB Host Controller Drivers"
 	depends on USB
 
+config USB_MAX3421_HCD
+       tristate "MAX3421 HCD (USB-over-SPI) support"
+       depends on USB
+       help
+         The Maxim MAX3421E Host Controller Interface supports
+	 standard USB 1.1 high-speed hardware.
+
+         To compile this driver as a module, choose M here: the module will
+	 be called max3421.
+
 config USB_C67X00_HCD
 	tristate "Cypress C67x00 HCD support"
 	depends on USB
diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile
index 1eb4c30..1dfb836 100644
--- a/drivers/usb/host/Makefile
+++ b/drivers/usb/host/Makefile
@@ -23,6 +23,8 @@ obj-$(CONFIG_USB_WHCI_HCD)	+= whci/
 
 obj-$(CONFIG_PCI)		+= pci-quirks.o
 
+obj-$(CONFIG_USB_MAX3421_HCD)	+= max3421.o
+
 obj-$(CONFIG_USB_EHCI_HCD)	+= ehci-hcd.o
 obj-$(CONFIG_USB_EHCI_PCI)	+= ehci-pci.o
 obj-$(CONFIG_USB_EHCI_HCD_PLATFORM)	+= ehci-platform.o
diff --git a/drivers/usb/host/max3421.c b/drivers/usb/host/max3421.c
new file mode 100644
index 0000000..6b88f12
--- /dev/null
+++ b/drivers/usb/host/max3421.c
@@ -0,0 +1,1643 @@
+/*
+ * MAX3421 Host Controller driver for USB.
+ *
+ * Maintainer: David Mosberger-Tang <davidm@xxxxxxxxxx>
+ *
+ * (C) Copyright 2014 David Mosberger-Tang <davidm@xxxxxxxxxx>
+ *
+ * MAX3421 is a chip implementing a USB 2.0 Full-/Low-Speed host
+ * controller on a SPI bus.
+ *
+ * Based on:
+ *	o MAX3421E datasheet
+ *		http://datasheets.maximintegrated.com/en/ds/MAX3421E.pdf
+ *	o MAX3421E Programming Guide
+ *		http://www.hdl.co.jp/ftpdata/utl-001/AN3785.pdf
+ *	o gadget/dummy_hcd.c
+ *		For USB HCD implementation.
+ *	o Arduino MAX3421 driver
+ *		https://github.com/felis/USB_Host_Shield_2.0/blob/master/Usb.cpp
+ *
+ * This file is licenced under the GPL.
+ *
+ * Important note on worst-case (full-speed) packet size constraints
+ * (See USB 2.0 Section 5.6.3 and following):
+ *
+ *	- control:	  64 bytes
+ *	- isochronous:	1023 bytes
+ *	- interrupt:	  64 bytes
+ *	- bulk:		  64 bytes
+ *
+ * Since the MAX3421 FIFO size is 64 bytes, we do not have to work about
+ * multi-FIFO writes/reads for a single USB packet *except* for isochronous
+ * transfers.  We don't support isochronous transfers at this time, so we
+ * just assume that a USB packet always fits into a single FIFO buffer.
+ *
+ * NOTE: The June 2006 version of "MAX3421E Programming Guide"
+ * (AN3785) has conflicting info for the RCVDAVIRQ bit:
+ *
+ *	The description of RCVDAVIRQ says "The CPU *must* clear
+ *	this IRQ bit (by writing a 1 to it) before reading the
+ *	RCVFIFO data.
+ *
+ * However, the earlier section on "Programming BULK-IN
+ * Transfers" says * that:
+ *
+ *	After the CPU retrieves the data, it clears the
+ *	RCVDAVIRQ bit.
+ *
+ * The December 2006 version has been corrected and it consistently
+ * states the second behavior is the correct one.
+ */
+
+#define TRACE 1
+
+#include <linux/module.h>
+#include <linux/scatterlist.h>
+#include <linux/spi/spi.h>
+#include <linux/usb.h>
+#include <linux/usb/hcd.h>
+
+#define DRIVER_DESC	"MAX3421 USB Host-Controller Driver"
+#define DRIVER_VERSION	"0.1"
+
+#define USB_MAX_FRAME_NUMBER	0x7ff	// 11-bit counter that wraps around (USB 2.0 Section 8.3.3)
+#define USB_MAX_RETRIES		3	// # of retries before error is reported
+
+#define POWER_BUDGET	500	// in mA; use 8 for low-power port testing
+
+// Port-change mask:
+#define PORT_C_MASK	((USB_PORT_STAT_C_CONNECTION |	\
+			  USB_PORT_STAT_C_ENABLE |	\
+			  USB_PORT_STAT_C_SUSPEND |	\
+			  USB_PORT_STAT_C_OVERCURRENT | \
+			  USB_PORT_STAT_C_RESET) << 16)
+
+enum trace_type {
+	TRACE_CMD = 1,
+	TRACE_RESULT,
+	TRACE_RX_LEN,
+	TRACE_RX_SIZE,
+	TRACE_TX_LEN,
+	TRACE_ADDR,
+	TRACE_TOGGLE
+};
+
+enum max3421_rh_state {
+	MAX3421_RH_RESET,
+	MAX3421_RH_SUSPENDED,
+	MAX3421_RH_RUNNING
+};
+
+enum pkt_state {
+	PKT_STATE_SETUP,		// waiting to send setup packet to a control pipe
+	PKT_STATE_TRANSFER,		// waiting to transfer packet's transfer_buffer
+	PKT_STATE_TERMINATE		// waiting to terminate transfer to a control pipe
+};
+
+enum scheduling_pass {
+	SCHED_PASS_PERIODIC,
+	SCHED_PASS_NON_PERIODIC,
+	SCHED_PASS_DONE
+};
+
+struct max3421_hcd {
+	spinlock_t lock;
+
+	struct max3421_hcd *next;
+
+	enum max3421_rh_state rh_state;
+	// lower 16 bits contain port status, upper 16 bits the change mask:
+	u32 port_status;
+
+	struct list_head ep_list;	// list of EP's with work
+
+	struct usb_device *loaded_dev;	// dev that was loaded into the controller
+	int loaded_epnum;		// epnum that is currently in the toggle bits (or -1)
+
+	struct urb *curr_urb;		// URB we're currently processing
+	int urb_done;			// > 0 -> no errors, < 0: errno
+	size_t curr_len;
+
+	enum scheduling_pass sched_pass;
+	u16 frame_number;
+
+	u8 chip_rev;	// contents of REVISION register
+	u8 hien;
+
+	unsigned active : 1;
+	unsigned resuming : 1;
+
+	unsigned long type_stat[4];
+	unsigned long err_stat[16];
+};
+
+struct max3421_ep {
+	struct usb_host_endpoint *ep;
+	struct list_head ep_list;
+	u16 last_active;		// last frame number this ep was active
+	u8 retries;
+	enum pkt_state pkt_state;
+	unsigned retransmit : 1;	// packet needs retransmission
+};
+
+static struct max3421_hcd *max3421_hcd_list;
+
+#define MAX3421_FIFO_SIZE	64
+
+#define MAX3421_SPI_DIR_RD	0	// read register from MAX3421
+#define MAX3421_SPI_DIR_WR	1	// write register to MAX3421
+
+/* SPI commands: */
+#define MAX3421_SPI_DIR_SHIFT	1
+#define MAX3421_SPI_REG_SHIFT	3
+
+#define MAX3421_REG_RCVFIFO	1
+#define MAX3421_REG_SNDFIFO	2
+#define MAX3421_REG_SUDFIFO	4
+#define MAX3421_REG_RCVBC	6
+#define MAX3421_REG_SNDBC	7
+#define MAX3421_REG_USBIRQ	13
+#define MAX3421_REG_USBIEN	14
+#define MAX3421_REG_USBCTL	15
+#define MAX3421_REG_CPUCTL	16
+#define MAX3421_REG_PINCTL	17
+#define MAX3421_REG_REVISION	18
+#define MAX3421_REG_IOPINS1	20
+#define MAX3421_REG_IOPINS2	21
+#define MAX3421_REG_GPINIRQ	22
+#define MAX3421_REG_GPINIEN	23
+#define MAX3421_REG_GPINPOL	24
+#define MAX3421_REG_HIRQ	25
+#define MAX3421_REG_HIEN	26
+#define MAX3421_REG_MODE	27
+#define MAX3421_REG_PERADDR	28
+#define MAX3421_REG_HCTL	29
+#define MAX3421_REG_HXFR	30
+#define MAX3421_REG_HRSL	31
+
+enum {
+	MAX3421_USBIRQ_OSCOKIRQ_BIT = 0,
+	MAX3421_USBIRQ_NOVBUSIRQ_BIT = 5,
+	MAX3421_USBIRQ_VBUSIRQ_BIT
+};
+
+enum {
+	MAX3421_CPUCTL_IE_BIT = 0,
+	MAX3421_CPUCTL_PULSEWID0_BIT = 6,
+	MAX3421_CPUCTL_PULSEWID1_BIT
+};
+
+enum {
+	MAX3421_USBCTL_PWRDOWN_BIT = 4,
+	MAX3421_USBCTL_CHIPRES_BIT
+};
+
+enum {
+	MAX3421_PINCTL_GPXA_BIT	= 0,
+	MAX3421_PINCTL_GPXB_BIT,
+	MAX3421_PINCTL_POSINT_BIT,
+	MAX3421_PINCTL_INTLEVEL_BIT,
+	MAX3421_PINCTL_FDUPSPI_BIT,
+	MAX3421_PINCTL_EP0INAK_BIT,
+	MAX3421_PINCTL_EP2INAK_BIT,
+	MAX3421_PINCTL_EP3INAK_BIT,
+};
+
+enum {
+	MAX3421_HI_BUSEVENT_BIT = 0,	// bus-reset/-resume
+	MAX3421_HI_RWU_BIT,		// remote wakeup
+	MAX3421_HI_RCVDAV_BIT,		// receive FIFO data available
+	MAX3421_HI_SNDBAV_BIT,		// send buffer available
+	MAX3421_HI_SUSDN_BIT,		// suspend operation done
+	MAX3421_HI_CONDET_BIT,		// peripheral connect/disconnect
+	MAX3421_HI_FRAME_BIT,		// frame generator
+	MAX3421_HI_HXFRDN_BIT,		// host transfer done
+};
+
+enum {
+	MAX3421_HCTL_BUSRST_BIT = 0,
+	MAX3421_HCTL_FRMRST_BIT,
+	MAX3421_HCTL_SAMPLEBUS_BIT,
+	MAX3421_HCTL_SIGRSM_BIT,
+	MAX3421_HCTL_RCVTOG0_BIT,
+	MAX3421_HCTL_RCVTOG1_BIT,
+	MAX3421_HCTL_SNDTOG0_BIT,
+	MAX3421_HCTL_SNDTOG1_BIT
+};
+
+enum {
+	MAX3421_MODE_HOST_BIT = 0,
+	MAX3421_MODE_LOWSPEED_BIT,
+	MAX3421_MODE_HUBPRE_BIT,
+	MAX3421_MODE_SOFKAENAB_BIT,
+	MAX3421_MODE_SEPIRQ_BIT,
+	MAX3421_MODE_DELAYISO_BIT,
+	MAX3421_MODE_DMPULLDN_BIT,
+	MAX3421_MODE_DPPULLDN_BIT
+};
+
+enum {
+	MAX3421_HRSL_OK = 0,
+	MAX3421_HRSL_BUSY,
+	MAX3421_HRSL_BADREQ,
+	MAX3421_HRSL_UNDEF,
+	MAX3421_HRSL_NAK,
+	MAX3421_HRSL_STALL,
+	MAX3421_HRSL_TOGERR,
+	MAX3421_HRSL_WRONGPID,
+	MAX3421_HRSL_BADBC,
+	MAX3421_HRSL_PIDERR,
+	MAX3421_HRSL_PKTERR,
+	MAX3421_HRSL_CRCERR,
+	MAX3421_HRSL_KERR,
+	MAX3421_HRSL_JERR,
+	MAX3421_HRSL_TIMEOUT,
+	MAX3421_HRSL_BABBLE,
+	MAX3421_HRSL_RESULT_MASK = 0xf,
+	MAX3421_HRSL_RCVTOGRD_BIT = 4,
+	MAX3421_HRSL_SNDTOGRD_BIT,
+	MAX3421_HRSL_KSTATUS_BIT,
+	MAX3421_HRSL_JSTATUS_BIT
+};
+
+// Return same error-codes as ohci.h:cc_to_error:
+static const int hrsl_to_error[] = {
+	[MAX3421_HRSL_OK] =		0,
+	[MAX3421_HRSL_BUSY] =		-EINVAL,
+	[MAX3421_HRSL_BADREQ] =		-EINVAL,
+	[MAX3421_HRSL_UNDEF] =		-EINVAL,
+	[MAX3421_HRSL_NAK] =		-EAGAIN,
+	[MAX3421_HRSL_STALL] =		-EPIPE,
+	[MAX3421_HRSL_TOGERR] =		-EILSEQ,
+	[MAX3421_HRSL_WRONGPID] =	-EPROTO,
+	[MAX3421_HRSL_BADBC] =		-EREMOTEIO,
+	[MAX3421_HRSL_PIDERR] =		-EPROTO,
+	[MAX3421_HRSL_PKTERR] =		-EPROTO,
+	[MAX3421_HRSL_CRCERR] =		-EILSEQ,
+	[MAX3421_HRSL_KERR] =		-EIO,
+	[MAX3421_HRSL_JERR] =		-EIO,
+	[MAX3421_HRSL_TIMEOUT] =	-ETIME,
+	[MAX3421_HRSL_BABBLE] =		-EOVERFLOW
+};
+
+#if 0
+static const char *hrsl_string[] = {
+	[MAX3421_HRSL_OK] =		"Successful Transfer",
+	[MAX3421_HRSL_BUSY] =		"SIE is busy, transfer pending",
+	[MAX3421_HRSL_BADREQ] =		"Bad value in HXFR register",
+	[MAX3421_HRSL_UNDEF] =		"(reserved)",
+	[MAX3421_HRSL_NAK] =		"Peripheral returned NAK",
+	[MAX3421_HRSL_STALL] =		"Peripheral returned STALL",
+	[MAX3421_HRSL_TOGERR] =		"Toggle error/ISO over-/under-run",
+	[MAX3421_HRSL_WRONGPID] =	"Received the wrong PID",
+	[MAX3421_HRSL_BADBC] =		"Bad byte count",
+	[MAX3421_HRSL_PIDERR] =		"Receive PID is corrupted",
+	[MAX3421_HRSL_PKTERR] =		"Packet error (stuff, EOP)",
+	[MAX3421_HRSL_CRCERR] =		"CRC error",
+	[MAX3421_HRSL_KERR] =		"K-state instead of response",
+	[MAX3421_HRSL_JERR] =		"J-state instead of response",
+	[MAX3421_HRSL_TIMEOUT] =	"Device did not respond in time",
+	[MAX3421_HRSL_BABBLE] =		"Device talked too long"
+};
+#endif
+
+/*
+ * See http://www.beyondlogic.org/usbnutshell/usb4.shtml#Control for a reasonable overview
+ * of how control transfers use the the IN/OUT tokens.
+ */
+#define MAX3421_HXFR_BULK_IN(ep)	(0x00 | (ep))	// bulk or interrupt
+#define MAX3421_HXFR_SETUP		 0x10
+#define MAX3421_HXFR_BULK_OUT(ep)	(0x20 | (ep))	// bulk or interrupt
+#define MAX3421_HXFR_ISO_IN(ep)		(0x40 | (ep))
+#define MAX3421_HXFR_ISO_OUT(ep)	(0x60 | (ep))
+#define MAX3421_HXFR_HS_IN		 0x80		// handshake in
+#define MAX3421_HXFR_HS_OUT		 0xa0		// handshake out
+
+#define field(val, bit)	((val) << (bit))
+
+static u8
+spi_rd8 (struct spi_device *spi, unsigned int reg, u8 *status)
+{
+	struct spi_transfer transfer;
+	u8 tx_data[2], rx_data[2];
+	struct spi_message msg;
+
+	memset (&transfer, 0, sizeof (transfer));
+
+	spi_message_init (&msg);
+
+	tx_data[0] = (field (reg, MAX3421_SPI_REG_SHIFT) |
+		      field (MAX3421_SPI_DIR_RD, MAX3421_SPI_DIR_SHIFT));
+
+	transfer.tx_buf = tx_data;
+	transfer.rx_buf = rx_data;
+	transfer.len = 2;
+
+	spi_message_add_tail (&transfer, &msg);
+	spi_sync (spi, &msg);
+
+	if (status)
+		*status = rx_data[0];
+
+	return rx_data[1];
+}
+
+static void
+spi_wr8 (struct spi_device *spi, unsigned int reg, u8 val, u8 *status)
+{
+	struct spi_transfer transfer;
+	u8 tx_data[2], rx_data[2];
+	struct spi_message msg;
+
+	memset (&transfer, 0, sizeof (transfer));
+
+	spi_message_init (&msg);
+
+	tx_data[0] = (field (reg, MAX3421_SPI_REG_SHIFT) |
+		      field (MAX3421_SPI_DIR_WR, MAX3421_SPI_DIR_SHIFT));
+	tx_data[1] = val;
+
+	transfer.tx_buf = tx_data;
+	if (status)
+		transfer.rx_buf = rx_data;
+	transfer.len = 2;
+
+	spi_message_add_tail (&transfer, &msg);
+	spi_sync (spi, &msg);
+
+	if (status)
+		*status = rx_data[0];
+}
+
+static void
+spi_rd_fifo (struct spi_device *spi, unsigned int reg, void *buf, size_t len, u8 *status)
+{
+	struct spi_transfer transfer[2];
+	struct spi_message msg;
+	u8 cmd;
+
+	memset (transfer, 0, sizeof (transfer));
+
+	spi_message_init (&msg);
+
+	cmd = (field (reg, MAX3421_SPI_REG_SHIFT) |
+	       field (MAX3421_SPI_DIR_RD, MAX3421_SPI_DIR_SHIFT));
+
+	transfer[0].tx_buf = &cmd;
+	transfer[0].rx_buf = status;
+	transfer[0].len = 1;
+
+	transfer[1].rx_buf = buf;
+	transfer[1].len = len;
+
+	spi_message_add_tail (&transfer[0], &msg);
+	spi_message_add_tail (&transfer[1], &msg);
+	spi_sync (spi, &msg);
+}
+
+static void
+spi_wr_fifo (struct spi_device *spi, unsigned int reg, void *buf, size_t len, u8 *status)
+{
+	struct spi_transfer transfer[2];
+	struct spi_message msg;
+	u8 cmd;
+
+	memset (transfer, 0, sizeof (transfer));
+
+	spi_message_init (&msg);
+
+	cmd = (field (reg, MAX3421_SPI_REG_SHIFT) |
+	       field (MAX3421_SPI_DIR_WR, MAX3421_SPI_DIR_SHIFT));
+
+	transfer[0].tx_buf = &cmd;
+	transfer[0].rx_buf = status;
+	transfer[0].len = 1;
+
+	transfer[1].tx_buf = buf;
+	transfer[1].len = len;
+
+	spi_message_add_tail (&transfer[0], &msg);
+	spi_message_add_tail (&transfer[1], &msg);
+	spi_sync (spi, &msg);
+}
+
+static inline s16
+frame_diff (u16 left, u16 right)
+{
+	return ((unsigned) (left - right)) % (USB_MAX_FRAME_NUMBER + 1);
+}
+
+static inline struct max3421_hcd *
+hcd_to_max3421 (struct usb_hcd *hcd)
+{
+	return (struct max3421_hcd *) hcd->hcd_priv;
+}
+
+static inline struct usb_hcd *
+max3421_to_hcd (struct max3421_hcd *max3421_hcd)
+{
+	return container_of ((void *) max3421_hcd, struct usb_hcd, hcd_priv);
+}
+
+#if TRACE
+
+struct trace_entry {
+	u16 timestamp;
+	u8 type;
+	u8 data;
+};
+
+static struct trace_buffer {
+	unsigned int stop_counter;
+	unsigned int stopped;
+	int index;
+	struct trace_entry e[256];
+} trace_buffer;
+
+static void
+trace_stop (unsigned int delay_count)
+{
+	if (delay_count > 0)
+		trace_buffer.stop_counter = delay_count;
+	else
+		trace_buffer.stopped = 1;
+}
+
+static void
+trace_record (struct usb_hcd *hcd, u8 type, u8 data)
+{
+	struct max3421_hcd *max3421_hcd = hcd_to_max3421 (hcd);
+	struct trace_entry *e = &trace_buffer.e[trace_buffer.index % ARRAY_SIZE (trace_buffer.e)];
+
+	if (trace_buffer.stopped)
+		return;
+
+	e->timestamp = max3421_hcd->frame_number;
+	e->type = type;
+	e->data = data;
+
+	++trace_buffer.index;
+
+	if (trace_buffer.stop_counter > 0) {
+		if (--trace_buffer.stop_counter == 0)
+			trace_buffer.stopped = 1;
+	}
+}
+
+static void
+trace_dump (void)
+{
+	int i, rd, num_entries = trace_buffer.index;
+	struct trace_entry *e;
+	const char *type_str;
+
+	if (!trace_buffer.stopped)
+		return;
+
+	if (num_entries > ARRAY_SIZE (trace_buffer.e))
+		num_entries = ARRAY_SIZE (trace_buffer.e);
+	rd = (trace_buffer.index - num_entries) % ARRAY_SIZE (trace_buffer.e);
+	for (i = 0; i < num_entries; ++i) {
+		e = &trace_buffer.e[rd];
+		switch (e->type) {
+		case TRACE_CMD:		type_str = "CMD"; break;
+		case TRACE_RESULT:	type_str = "RES"; break;
+		case TRACE_RX_LEN:	type_str = "#RL"; break;
+		case TRACE_RX_SIZE:	type_str = "#RS"; break;
+		case TRACE_TX_LEN:	type_str = "#TL"; break;
+		case TRACE_ADDR:	type_str = "ADR"; break;
+		case TRACE_TOGGLE:	type_str = "TOG"; break;
+		default:		type_str = "UNK"; break;
+		}
+		printk ("%6u %05u %s 0x%02x\n", rd, e->timestamp, type_str, e->data);
+		rd = (rd + 1) % ARRAY_SIZE (trace_buffer.e);
+	}
+	trace_buffer.index = 0;
+	trace_buffer.stop_counter = trace_buffer.stopped = 0;
+}
+
+#else  /* !TRACE */
+
+static inline void trace_stop (unsigned int delay_count) {}
+static inline void trace_record (struct usb_hcd *hcd, u8 type, u8 data) {}
+static inline void trace_dump (void) {}
+
+#endif /* !TRACE */
+
+/*
+ * Caller must be holding HCD spinlock.
+ */
+static void
+max3421_set_address (struct usb_hcd *hcd, struct usb_device *dev, int epnum, int force_toggles)
+{
+	struct spi_device *spi = to_spi_device (hcd->self.controller);
+	struct max3421_hcd *max3421_hcd = hcd_to_max3421 (hcd);
+	struct usb_device *old_dev;
+	int old_epnum, same_ep;
+	u8 hctl;
+
+	old_dev = max3421_hcd->loaded_dev;
+	old_epnum = max3421_hcd->loaded_epnum;
+
+	same_ep = (dev == old_dev && epnum == old_epnum);
+	if (same_ep && !force_toggles)
+		return;
+
+	if (old_dev && !same_ep) {
+		// save the old end-points toggles:
+		u8 hrsl = spi_rd8 (spi, MAX3421_REG_HRSL, NULL);
+		usb_settoggle (old_dev, old_epnum, 0, (hrsl >> MAX3421_HRSL_RCVTOGRD_BIT) & 1);
+		usb_settoggle (old_dev, old_epnum, 1, (hrsl >> MAX3421_HRSL_SNDTOGRD_BIT) & 1);
+	}
+	// setup new endpoint's toggle bits:
+	hctl = (BIT (usb_gettoggle (dev, epnum, 0) + MAX3421_HCTL_RCVTOG0_BIT) |
+		BIT (usb_gettoggle (dev, epnum, 1) + MAX3421_HCTL_SNDTOG0_BIT));
+
+	max3421_hcd->loaded_epnum = epnum;
+	spi_wr8 (spi, MAX3421_REG_HCTL, hctl, NULL);
+	trace_record (hcd, TRACE_TOGGLE, (hctl >> MAX3421_HCTL_RCVTOG0_BIT) & 0xf);
+
+	/*
+	 * Note: devnum for one and the same device can change during
+	 * address-assignment so it's best to just always load the
+	 * address whenever the end-point changed/was forced.
+	 */
+	max3421_hcd->loaded_dev = dev;
+	spi_wr8 (spi, MAX3421_REG_PERADDR, dev->devnum, NULL);
+	trace_record (hcd, TRACE_ADDR, dev->devnum);
+}
+
+/*
+ * Issue the next host-transfer command.
+ * Caller must be holding HCD spinlock.
+ */
+static void
+max3421_next_transfer (struct usb_hcd *hcd)
+{
+	struct spi_device *spi = to_spi_device (hcd->self.controller);
+	struct max3421_hcd *max3421_hcd = hcd_to_max3421 (hcd);
+	struct urb *urb = max3421_hcd->curr_urb;
+	struct max3421_ep *max3421_ep = urb->ep->hcpriv;
+	u8 cmd = 0xff, epnum, hirq;
+
+
+	switch (max3421_ep->pkt_state) {
+	case PKT_STATE_SETUP:
+		if (0) {
+			int i;
+			printk ("%s: setup_packet =", __FUNCTION__);
+			for (i = 0; i < 8; ++i)
+				printk (" %02x", urb->setup_packet[i]);
+			printk ("\n");
+		}
+		spi_wr_fifo (spi, MAX3421_REG_SUDFIFO, urb->setup_packet, 8, NULL);
+		cmd = MAX3421_HXFR_SETUP;
+		break;
+
+	case PKT_STATE_TRANSFER:
+		epnum = usb_pipeendpoint (urb->pipe);
+		if (usb_pipein (urb->pipe)) {
+			cmd = MAX3421_HXFR_BULK_IN (epnum);
+			max3421_hcd->hien |= BIT (MAX3421_HI_RCVDAV_BIT);
+		} else {
+			size_t max_packet = usb_maxpacket (urb->dev, urb->pipe, 1);
+			void *src = urb->transfer_buffer + urb->actual_length;
+
+			if (max_packet > MAX3421_FIFO_SIZE) {
+				// we do not support isochronous transfers at this time
+				dev_err (&spi->dev,
+					 "%s: packet-size of %zu too big (limit is %zu bytes)",
+					 __FUNCTION__, max_packet, MAX3421_FIFO_SIZE);
+				max3421_hcd->urb_done = -EMSGSIZE;
+				return;
+			}
+			max3421_hcd->curr_len = min ((urb->transfer_buffer_length -
+						      urb->actual_length), max_packet);
+			trace_record (hcd, TRACE_TX_LEN, max3421_hcd->curr_len);
+
+			if (0) {
+				int i;
+				printk ("%s: tx", __FUNCTION__);
+				for (i = 0; i < max3421_hcd->curr_len; ++i)
+					printk (" %02x", ((u8*) src)[i]);
+				printk ("\n");
+			}
+
+			spi_wr_fifo (spi, MAX3421_REG_SNDFIFO, src, max3421_hcd->curr_len, &hirq);
+			/*
+			 * SNDBAV may not be set if an OUT transfer
+			 * was NAK'd, but the only recourse then is to
+			 * rewrite the FIFO anyhow (possibly with the
+			 * same data, if there hasn't been another
+			 * transfer in the meantime).
+			 */
+			spi_wr8 (spi, MAX3421_REG_SNDBC, max3421_hcd->curr_len, NULL);
+			cmd = MAX3421_HXFR_BULK_OUT (epnum);
+		}
+		break;
+
+	case PKT_STATE_TERMINATE:
+		if (usb_pipein (urb->pipe))
+			cmd = MAX3421_HXFR_HS_OUT;	// IN transfers use OUT token
+		else
+			cmd = MAX3421_HXFR_HS_IN;	// and vice versa
+		break;
+	}
+
+	spi_wr8 (spi, MAX3421_REG_HXFR, cmd, NULL);
+	trace_record (hcd, TRACE_CMD, cmd);
+
+	// issue the command and wait for host-xfer-done interrupt:
+	max3421_hcd->hien |= BIT (MAX3421_HI_HXFRDN_BIT);
+	spi_wr8 (spi, MAX3421_REG_HIEN, max3421_hcd->hien, NULL);
+}
+
+/*
+ * Process endpoint MAX3421_EP.  The URB-list must not be empty.
+ * Caller must be holding HCD spinlock.
+ */
+static void
+max3421_process_ep (struct usb_hcd *hcd, struct usb_host_endpoint *ep)
+{
+	struct max3421_hcd *max3421_hcd = hcd_to_max3421 (hcd);
+	struct max3421_ep *max3421_ep = ep->hcpriv;
+	int epnum = usb_endpoint_num (&ep->desc);
+	int force_toggles = 0;
+	struct urb *urb;
+
+	urb = list_first_entry (&ep->urb_list, struct urb, urb_list);
+	max3421_hcd->curr_urb = urb;
+
+	if (urb->unlinked) {
+		max3421_hcd->urb_done = 1;
+		return;
+	}
+
+	if (max3421_ep->retransmit)
+		// restart (part of) a USB transaction:
+		max3421_ep->retransmit = 0;
+	else {
+		// start a new USB transaction:
+		if (usb_endpoint_xfer_control (&ep->desc)) {
+			max3421_ep->pkt_state = PKT_STATE_SETUP;
+			// See USB 2.0 spec section 8.6.1 Initialization via SETUP Token:
+			usb_settoggle (urb->dev, epnum, 0, 1);
+			usb_settoggle (urb->dev, epnum, 1, 1);
+			force_toggles = 1;
+		} else
+			max3421_ep->pkt_state = PKT_STATE_TRANSFER;
+		max3421_ep->retries = 0;
+	}
+	max3421_ep->last_active = max3421_hcd->frame_number;
+	max3421_set_address (hcd, urb->dev, epnum, force_toggles);
+	max3421_next_transfer (hcd);
+}
+
+/*
+ * Find the next end-point to process.  Since we do not anticipate
+ * ever connecting a USB hub to the MAX3421 chip, at most USB device
+ * can be connected and we can use a simplistic scheduler: at the
+ * start of a frame, schedule all periodic transfers.  Once that is
+ * done, use the remainder of the frame to process non-periodic (bulk
+ * & control) transfers.
+ *
+ * Caller must be holding HCD spinlock.
+ */
+static void
+max3421_next_ep (struct usb_hcd *hcd)
+{
+	struct max3421_hcd *max3421_hcd = hcd_to_max3421 (hcd);
+	struct max3421_ep *max3421_ep;
+	struct usb_host_endpoint *ep;
+	struct list_head *pos;
+	struct urb *urb;
+
+	max3421_hcd->curr_urb = NULL;
+
+	for ( ; max3421_hcd->sched_pass < SCHED_PASS_DONE; ++max3421_hcd->sched_pass)
+		list_for_each (pos, &max3421_hcd->ep_list) {
+			max3421_ep = container_of (pos, struct max3421_ep, ep_list);
+			ep = max3421_ep->ep;
+
+			switch (usb_endpoint_type (&ep->desc)) {
+			case USB_ENDPOINT_XFER_ISOC:
+			case USB_ENDPOINT_XFER_INT:
+				if (max3421_hcd->sched_pass != SCHED_PASS_PERIODIC)
+					continue;
+				break;
+
+			case USB_ENDPOINT_XFER_CONTROL:
+			case USB_ENDPOINT_XFER_BULK:
+				if (max3421_hcd->sched_pass != SCHED_PASS_NON_PERIODIC)
+					continue;
+				break;
+			}
+
+			if (list_empty (&ep->urb_list))
+				continue;	// nothing to do
+			urb = list_first_entry (&ep->urb_list, struct urb, urb_list);
+
+			switch (usb_endpoint_type (&ep->desc)) {
+			case USB_ENDPOINT_XFER_CONTROL:
+				// allow one control transaction per frame per endpoint:
+				if (frame_diff (max3421_ep->last_active,
+						max3421_hcd->frame_number) == 0)
+					continue;
+				break;
+
+			case USB_ENDPOINT_XFER_BULK:
+				if (max3421_ep->retransmit
+				    && frame_diff (max3421_ep->last_active,
+						   max3421_hcd->frame_number) == 0)
+					// we already tried this EP
+					// during this frame and got a NAK; wait for next frame
+					continue;
+				break;
+
+			case USB_ENDPOINT_XFER_ISOC:
+			case USB_ENDPOINT_XFER_INT:
+				if (frame_diff (max3421_hcd->frame_number, max3421_ep->last_active)
+				    < urb->interval)
+					// we already processed this end-point in the current frame
+					continue;
+				break;
+			}
+
+			// move current ep to tail:
+			list_move_tail (pos, &max3421_hcd->ep_list);
+
+			max3421_process_ep (hcd, ep);
+			return;
+		}
+}
+
+static void
+max3421_retransmit (struct usb_hcd *hcd)
+{
+	struct max3421_hcd *max3421_hcd = hcd_to_max3421 (hcd);
+	struct urb *urb = max3421_hcd->curr_urb;
+	struct max3421_ep *max3421_ep;
+
+	max3421_ep = urb->ep->hcpriv;
+	max3421_ep->retransmit = 1;
+	max3421_next_ep (hcd);
+}
+
+/*
+ * Caller must be holding HCD spinlock.
+ */
+static void
+max3421_recv_data_available (struct usb_hcd *hcd)
+{
+	struct spi_device *spi = to_spi_device (hcd->self.controller);
+	struct max3421_hcd *max3421_hcd = hcd_to_max3421 (hcd);
+	size_t max_packet, remaining, transfer_size;
+	struct urb *urb = max3421_hcd->curr_urb;
+	u8 rcvbc;
+
+	rcvbc = spi_rd8 (spi, MAX3421_REG_RCVBC, NULL);
+	trace_record (hcd, TRACE_RX_LEN, rcvbc);
+
+	if (!urb) {printk("%s: no URB!", __FUNCTION__); trace_stop (0); trace_dump (); return;}
+
+	if (rcvbc > MAX3421_FIFO_SIZE)
+		rcvbc = MAX3421_FIFO_SIZE;
+	if (urb->actual_length >= urb->transfer_buffer_length)
+		remaining = 0;
+	else
+		remaining = urb->transfer_buffer_length - urb->actual_length;
+	transfer_size = rcvbc;
+	if (transfer_size > remaining)
+		transfer_size = remaining;
+	if (transfer_size > 0) {
+		void *dst = urb->transfer_buffer + urb->actual_length;
+		spi_rd_fifo (spi, MAX3421_REG_RCVFIFO, dst, transfer_size, NULL);
+		urb->actual_length += transfer_size;
+		remaining -= transfer_size;
+		if (0) {
+			int i;
+			printk ("%s: rx", __FUNCTION__);
+			for (i = 0; i < min (urb->actual_length, 32u); ++i)
+				printk (" %02x", ((u8*)urb->transfer_buffer)[i]);
+			printk ("\n");
+		}
+	}
+
+	// ack the RCVDAV irq now that the FIFO has been read:
+	spi_wr8 (spi, MAX3421_REG_HIRQ, BIT (MAX3421_HI_RCVDAV_BIT), NULL);
+
+	// USB 2.0 Section 5.3.2 Pipes: packets must be full size except for last one
+	max_packet = usb_maxpacket (urb->dev, urb->pipe, 0);
+	if (max_packet > MAX3421_FIFO_SIZE) {
+		// we do not support isochronous transfers at this time
+		max3421_hcd->urb_done = -EINVAL;
+		dev_err (&spi->dev, "%s: packet-size of %zu too big (limit is %zu bytes)",
+			 __FUNCTION__, max_packet, MAX3421_FIFO_SIZE);
+		return;
+	}
+
+	if (remaining > 0) {
+		if (rcvbc < max_packet) {
+			if (urb->transfer_flags & URB_SHORT_NOT_OK) {
+				// remaining > 0 and received an unexpected partial packet -> error
+				trace_record (hcd, TRACE_RX_SIZE, urb->transfer_buffer_length);
+				max3421_hcd->urb_done = -EREMOTEIO;
+			} else
+				max3421_hcd->urb_done = 1;	// short read OK
+		}
+	} else
+		max3421_hcd->urb_done = 1;
+}
+
+/*
+ * Caller must be holding HCD spinlock.
+ */
+static void
+max3421_host_transfer_done (struct usb_hcd *hcd)
+{
+	struct spi_device *spi = to_spi_device (hcd->self.controller);
+	struct max3421_hcd *max3421_hcd = hcd_to_max3421 (hcd);
+	struct urb *urb = max3421_hcd->curr_urb;
+	struct max3421_ep *max3421_ep;
+	u8 result_code, hrsl;
+	int urb_done = 0;
+
+	max3421_hcd->hien &= ~(BIT (MAX3421_HI_HXFRDN_BIT) | BIT (MAX3421_HI_RCVDAV_BIT));
+	spi_wr8 (spi, MAX3421_REG_HIEN, max3421_hcd->hien, NULL);
+
+	if (!urb) {printk("%s: no URB!", __FUNCTION__); trace_stop (0); trace_dump (); return;}
+
+	if (urb->unlinked) {
+		max3421_hcd->urb_done = 1;
+		return;
+	}
+
+	max3421_ep = urb->ep->hcpriv;
+
+	hrsl = spi_rd8 (spi, MAX3421_REG_HRSL, NULL);
+	result_code = hrsl & MAX3421_HRSL_RESULT_MASK;
+	trace_record (hcd, TRACE_RESULT, result_code);
+
+	++max3421_hcd->err_stat[result_code];
+
+	switch (result_code) {
+	case MAX3421_HRSL_OK:
+		break;
+
+	case MAX3421_HRSL_WRONGPID:	// received wrong PID
+	case MAX3421_HRSL_BUSY:		// SIE busy
+	case MAX3421_HRSL_BADREQ:	// bad val in HXFR
+	case MAX3421_HRSL_UNDEF:	// reserved
+	case MAX3421_HRSL_KERR:		// K-state instead of response
+	case MAX3421_HRSL_JERR:		// J-state instead of response
+#if 0
+		dev_err (&spi->dev, "%s: unexpected error HRSL=0x%02x (%s)",
+			 __FUNCTION__, hrsl, hrsl_string[result_code]);
+		trace_stop (0);
+#endif
+		// packet experience an error that we cannot recover from; report error
+		max3421_hcd->urb_done = hrsl_to_error[result_code];
+		return;
+
+	case MAX3421_HRSL_TOGERR:
+		if (usb_pipein (urb->pipe))
+			; // don't do anything (peripheral will switch its toggle)
+		else
+			// flip the send toggle bit:
+			spi_wr8 (spi, MAX3421_REG_HCTL,
+				 BIT ((((hrsl >> MAX3421_HRSL_SNDTOGRD_BIT) & 1) ^ 1)
+				       + MAX3421_HCTL_SNDTOG0_BIT), NULL);
+		// FALL THROUGH
+	case MAX3421_HRSL_BADBC:	// bad byte count
+	case MAX3421_HRSL_PIDERR:	// received PID is corrupted
+	case MAX3421_HRSL_PKTERR:	// packet error (stuff, EOP)
+	case MAX3421_HRSL_CRCERR:	// CRC error
+	case MAX3421_HRSL_BABBLE:	// device talked too long
+	case MAX3421_HRSL_TIMEOUT:
+		if (max3421_ep->retries++ < USB_MAX_RETRIES)
+			// retry the packet again in the next frame
+			max3421_retransmit (hcd);
+		else
+			// Based on ohci.h cc_to_err[]:
+			max3421_hcd->urb_done = hrsl_to_error[result_code];
+		return;
+
+	case MAX3421_HRSL_STALL:
+		max3421_hcd->urb_done = hrsl_to_error[result_code];
+		return;
+
+	case MAX3421_HRSL_NAK:		// device wasn't ready for data or has no data available
+		// retry the packet again in the next frame
+		max3421_retransmit (hcd);
+		return;
+	}
+
+	switch (max3421_ep->pkt_state) {
+
+	case PKT_STATE_SETUP:
+		if (urb->transfer_buffer_length > 0)
+			max3421_ep->pkt_state = PKT_STATE_TRANSFER;
+		else
+			max3421_ep->pkt_state = PKT_STATE_TERMINATE;
+		break;
+
+	case PKT_STATE_TRANSFER:
+		if (usb_pipein (urb->pipe))
+			// pick up result from max3421_recv_data_available():
+			urb_done = max3421_hcd->urb_done;
+		else {
+			urb->actual_length += max3421_hcd->curr_len;
+			if (urb->actual_length >= urb->transfer_buffer_length)
+				urb_done = 1;
+		}
+
+		if (urb_done && usb_pipetype (urb->pipe) == PIPE_CONTROL) {
+			max3421_ep->pkt_state = PKT_STATE_TERMINATE;
+			max3421_hcd->urb_done = urb_done = 0;	// we aren't really done...
+		}
+		break;
+
+	case PKT_STATE_TERMINATE:
+		urb_done = 1;
+		break;
+	}
+
+	if (urb_done) {
+		max3421_hcd->urb_done = 1;
+		return;
+	}
+	max3421_next_transfer (hcd);
+}
+
+static void
+max3421_detect_conn (struct usb_hcd *hcd)
+{
+	struct spi_device *spi = to_spi_device (hcd->self.controller);
+	struct max3421_hcd *max3421_hcd = hcd_to_max3421 (hcd);
+	u8 hrsl, mode;
+	unsigned jk;
+
+	hrsl = spi_rd8 (spi, MAX3421_REG_HRSL, NULL);
+
+	jk = ((((hrsl >> MAX3421_HRSL_JSTATUS_BIT) & 1) << 0) |
+	      (((hrsl >> MAX3421_HRSL_KSTATUS_BIT) & 1) << 1));
+
+	mode = spi_rd8 (spi, MAX3421_REG_MODE, NULL);
+
+	switch (jk) {
+	case 0x0: // SE0: disconnect
+		// turn off SOFKAENAB bit to avoid getting interrupt every milli-second:
+		mode &= ~BIT (MAX3421_MODE_SOFKAENAB_BIT);
+		spi_wr8 (spi, MAX3421_REG_MODE, mode, NULL);
+		max3421_hcd->port_status &= ~USB_PORT_STAT_CONNECTION;
+		break;
+
+	case 0x1: // J=0, K=1 => low-speed (in full-speed mode or vice versa)
+	case 0x2: // J=1, K=0 => full-speed (in full-speed mode or vice versa)
+		if (jk == 0x2)
+			// need to switch to the other speed:
+			mode ^= BIT (MAX3421_MODE_LOWSPEED_BIT);
+#if 0
+		// XXX This only needs to be turned on when talking to a low-speed device through
+		// a USB hub.  But how to tell?
+		if (mode & BIT (MAX3421_MODE_LOWSPEED_BIT))
+			// preceide every low-speed packet with a full-speed PRE PID
+			mode |= BIT (MAX3421_MODE_HUBPRE_BIT);
+#endif
+		// turn on SOFKAENAB bit:
+		mode |= BIT (MAX3421_MODE_SOFKAENAB_BIT);
+		spi_wr8 (spi, MAX3421_REG_MODE, mode, NULL);
+		if ((mode >> MAX3421_MODE_LOWSPEED_BIT) & 1)
+			max3421_hcd->port_status |=  USB_PORT_STAT_LOW_SPEED;
+		else
+			max3421_hcd->port_status &= ~USB_PORT_STAT_LOW_SPEED;
+		max3421_hcd->port_status |= USB_PORT_STAT_CONNECTION;
+		break;
+
+	case 0x3: // illegal
+		break;
+	}
+}
+
+/*
+ * This executes as a separate thread with MAX3421 interrupt masked.
+ * We have to run in a separate thread because it's impossible to
+ * communicate with a SPI device in a non-blocking fashion.
+ */
+static irqreturn_t
+max3421_irq_thread (int irq, void *dev_id)
+{
+	struct usb_hcd *hcd = dev_id;
+	struct spi_device *spi = to_spi_device (hcd->self.controller);
+	struct max3421_hcd *max3421_hcd = hcd_to_max3421 (hcd);
+	struct urb *urb_to_give_back = NULL;
+	u32 chg, old_port_status;
+	unsigned long flags;
+	int status = 0;
+	u8 hirq;
+
+	spin_lock_irqsave (&max3421_hcd->lock, flags);
+
+	old_port_status = max3421_hcd->port_status;
+
+	// read pending interrupts:
+	hirq = spi_rd8 (spi, MAX3421_REG_HIRQ, NULL) & max3421_hcd->hien;
+
+	// ack pending interrupts (but CPU must never clear SNDBAV directly and RCVDAV must be
+	// cleared by max3421_recv_data_available()!):
+	spi_wr8 (spi, MAX3421_REG_HIRQ, hirq & ~(BIT (MAX3421_HI_SNDBAV_BIT) |
+						 BIT (MAX3421_HI_RCVDAV_BIT)), NULL);
+
+	if (hirq & BIT (MAX3421_HI_BUSEVENT_BIT))
+		printk ("%s: BUSEVENT\n", __FUNCTION__);
+	if (hirq & BIT (MAX3421_HI_RWU_BIT))
+		printk ("%s: RWU\n", __FUNCTION__);
+	if (hirq & BIT (MAX3421_HI_SUSDN_BIT))
+		printk ("%s: SUSDN\n", __FUNCTION__);
+	if (hirq & BIT (MAX3421_HI_CONDET_BIT))
+		max3421_detect_conn (hcd);
+
+	if (hirq & BIT (MAX3421_HI_FRAME_BIT)) {
+		max3421_hcd->frame_number = (max3421_hcd->frame_number + 1) & USB_MAX_FRAME_NUMBER;
+		if (max3421_hcd->port_status & USB_PORT_STAT_RESET) {
+			max3421_hcd->port_status &= ~USB_PORT_STAT_RESET;
+			max3421_hcd->port_status |=  USB_PORT_STAT_ENABLE;
+		}
+		max3421_hcd->sched_pass = SCHED_PASS_PERIODIC;
+		if (!max3421_hcd->curr_urb)
+			max3421_next_ep (hcd);
+	}
+
+	if (hirq & BIT (MAX3421_HI_RCVDAV_BIT))
+		max3421_recv_data_available (hcd);
+
+	if (hirq & BIT (MAX3421_HI_HXFRDN_BIT))
+		max3421_host_transfer_done (hcd);
+
+	if (max3421_hcd->urb_done) {
+		status = max3421_hcd->urb_done;
+		if (status > 0)
+			status = 0;
+		max3421_hcd->urb_done = 0;
+		urb_to_give_back = max3421_hcd->curr_urb;
+
+		if (urb_to_give_back) {
+			max3421_hcd->curr_urb = NULL;
+			usb_hcd_unlink_urb_from_ep (hcd, urb_to_give_back);
+
+			spin_unlock_irqrestore (&max3421_hcd->lock, flags);
+			//if (urb_to_give_back) printk("urb %p done! (actual %d, status %d)\n", urb_to_give_back, urb_to_give_back->actual_length, status);
+			// must be called without holding the HCD spinlock:
+			usb_hcd_giveback_urb (hcd, urb_to_give_back, status);
+			spin_lock_irqsave (&max3421_hcd->lock, flags);
+		}
+		max3421_next_ep (hcd);
+	}
+
+	if (0) {
+		static unsigned long last_time;
+		if (jiffies - last_time > 5*HZ) {
+			int i;
+			printk ("%s: fnum %5u urb types", __FUNCTION__, max3421_hcd->frame_number);
+			for (i = 0; i < 4; ++i) {
+				printk (" %3lu", max3421_hcd->type_stat[i]);
+				max3421_hcd->type_stat[i] = 0;
+			}
+			printk (" errcnt");
+			for (i = 0; i < 16; ++i) {
+				printk (" %lu", max3421_hcd->err_stat[i]);
+				max3421_hcd->err_stat[i] = 0;
+			}
+			printk ("\n");
+			last_time = jiffies;
+
+			trace_dump ();
+		}
+	}
+
+	spin_unlock_irqrestore (&max3421_hcd->lock, flags);
+
+	chg = (old_port_status ^ max3421_hcd->port_status);
+	if (chg) {
+		max3421_hcd->port_status |= chg << 16;
+		usb_hcd_poll_rh_status (hcd);
+	}
+	return IRQ_HANDLED;
+}
+
+static int
+max3421_reset_port (struct usb_hcd *hcd)
+{
+	struct max3421_hcd *max3421_hcd = hcd_to_max3421 (hcd);
+	struct spi_device *spi;
+
+	max3421_hcd->port_status &= ~(USB_PORT_STAT_ENABLE |
+				      USB_PORT_STAT_LOW_SPEED |
+				      USB_PORT_STAT_HIGH_SPEED);
+
+	spi = to_spi_device (hcd->self.controller);
+
+	// perform a USB bus reset:
+	spi_wr8 (spi, MAX3421_REG_HCTL, BIT (MAX3421_HCTL_BUSRST_BIT), NULL);
+	return 0;
+}
+
+static int
+max3421_reset (struct usb_hcd *hcd)
+{
+	struct max3421_hcd *max3421_hcd = hcd_to_max3421 (hcd);
+	struct spi_device *spi;
+	int timeout;
+
+	hcd->self.sg_tablesize = 0;
+	hcd->speed = HCD_USB2;
+	hcd->self.root_hub->speed = USB_SPEED_FULL;
+
+	spi = to_spi_device (hcd->self.controller);
+
+	// perform a chip reset and wait for OSCIRQ signal to appear:
+	spi_wr8 (spi, MAX3421_REG_USBCTL, BIT (MAX3421_USBCTL_CHIPRES_BIT), NULL);
+	// clear reset:
+	spi_wr8 (spi, MAX3421_REG_USBCTL, 0, NULL);
+	timeout = 1000;
+	while (!(spi_rd8 (spi, MAX3421_REG_USBIRQ, NULL) & BIT (MAX3421_USBIRQ_OSCOKIRQ_BIT))) {
+		if (--timeout < 0) {
+			dev_err (&spi->dev, "timed out waiting for oscillator OK signal");
+			return -ENODEV;
+		}
+		cond_resched ();
+	}
+
+	// turn on host mode, automatic generation of SOF packets, and
+	// enable pull-down registers on DM/DP:
+	spi_wr8 (spi, MAX3421_REG_MODE,
+		 (BIT (MAX3421_MODE_HOST_BIT) |
+		  BIT (MAX3421_MODE_SOFKAENAB_BIT) |
+		  BIT (MAX3421_MODE_DMPULLDN_BIT) |
+		  BIT (MAX3421_MODE_DPPULLDN_BIT)), NULL);
+
+#if 0
+	// reset frame-number:
+	max3421_hcd->frame_number = 0;
+	spi_wr8 (spi, MAX3421_REG_HCTL, BIT (MAX3421_HCTL_FRMRST_BIT), NULL);
+#endif
+
+	// sample the state of the D+ and D- lines
+	spi_wr8 (spi, MAX3421_REG_HCTL, BIT (MAX3421_HCTL_SAMPLEBUS_BIT), NULL);
+	max3421_detect_conn (hcd);
+
+	// enable frame, connection-detected, and bus-event interrupts:
+	max3421_hcd->hien = (BIT (MAX3421_HI_FRAME_BIT) |
+			     BIT (MAX3421_HI_CONDET_BIT) |
+			     BIT (MAX3421_HI_BUSEVENT_BIT));
+	spi_wr8 (spi, MAX3421_REG_HIEN, max3421_hcd->hien, NULL);
+
+	// enable interrupts:
+	spi_wr8 (spi, MAX3421_REG_CPUCTL, BIT (MAX3421_CPUCTL_IE_BIT), NULL);
+	return 0;
+}
+
+
+static int
+max3421_start (struct usb_hcd *hcd)
+{
+	struct max3421_hcd *max3421_hcd = hcd_to_max3421 (hcd);
+
+	spin_lock_init (&max3421_hcd->lock);
+	max3421_hcd->rh_state = MAX3421_RH_RUNNING;
+
+	INIT_LIST_HEAD (&max3421_hcd->ep_list);
+
+	hcd->power_budget = POWER_BUDGET;
+	hcd->state = HC_STATE_RUNNING;
+	hcd->uses_new_polling = 1;
+	return 0;
+}
+
+static void
+max3421_stop (struct usb_hcd *hcd)
+{
+	printk ("%s()\n", __FUNCTION__);
+}
+
+static int
+max3421_urb_enqueue (struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flags)
+{
+	struct spi_device *spi = to_spi_device (hcd->self.controller);
+	struct max3421_hcd *max3421_hcd = hcd_to_max3421 (hcd);
+	struct max3421_ep *max3421_ep;
+	unsigned long flags;
+	int retval;
+
+	//printk ("%s(urb=%p,type=%d,epnum=%d,len=%zu,interval=%d)\n", __FUNCTION__, urb, usb_pipetype (urb->pipe), usb_pipeendpoint (urb->pipe), urb->transfer_buffer_length, urb->interval);
+
+	if ((usb_pipetype (urb->pipe) == PIPE_INTERRUPT ||
+	     usb_pipetype (urb->pipe) == PIPE_ISOCHRONOUS) && urb->interval < 1)
+		dev_err (&spi->dev,
+			 "%s: interval for interrupt-/iso-pipes expected to be > 0, not %d\n",
+			 __FUNCTION__, urb->interval);
+
+	spin_lock_irqsave (&max3421_hcd->lock, flags);
+
+	max3421_ep = urb->ep->hcpriv;
+	if (!max3421_ep) {
+		// gets freed in max3421_endpoint_disable:
+		max3421_ep = kzalloc (sizeof (struct max3421_ep), mem_flags);
+		if (!max3421_ep)
+			return -ENOMEM;
+		max3421_ep->ep = urb->ep;
+		max3421_ep->last_active = max3421_hcd->frame_number;
+		urb->ep->hcpriv = max3421_ep;
+
+		list_add_tail (&max3421_ep->ep_list, &max3421_hcd->ep_list);
+	}
+
+	++max3421_hcd->type_stat[usb_pipetype (urb->pipe)];
+
+	if ((retval = usb_hcd_link_urb_to_ep (hcd, urb)) == 0) {
+		// Since we added to the queue, restart scheduling:
+		max3421_hcd->sched_pass = SCHED_PASS_PERIODIC;
+		/*
+		 * We can't call spi_wr8() from here since this may be
+		 * called from an interrupt context and spi_wr8() may
+		 * sleep.  This could cause processing of the new URB
+		 * to be delayed up to the next frame interrupt (1ms).
+		 * In practice, that doesn't seem to matter much.
+		 * Alternatively, we could wake up a helper-thread
+		 * here whose sole purpose is to wake up the
+		 * irq_thread.
+		 */
+	}
+
+	spin_unlock_irqrestore (&max3421_hcd->lock, flags);
+	return retval;
+}
+
+static int
+max3421_urb_dequeue (struct usb_hcd *hcd, struct urb *urb, int status)
+{
+	struct max3421_hcd *max3421_hcd = hcd_to_max3421 (hcd);
+	unsigned long flags;
+	int retval;
+
+	printk ("%s(urb=%p, status=%d)\n", __FUNCTION__, urb, status);
+
+	spin_lock_irqsave (&max3421_hcd->lock, flags);
+
+	/*
+	 * This will set urb->unlinked which in turn causes the entry to be dropped
+	 * at the next opportunity.
+	 */
+	retval = usb_hcd_check_unlink_urb (hcd, urb, status);
+
+	spin_unlock_irqrestore (&max3421_hcd->lock, flags);
+	return retval;
+}
+
+static void
+max3421_endpoint_disable (struct usb_hcd *hcd, struct usb_host_endpoint *ep)
+{
+	struct max3421_hcd *max3421_hcd = hcd_to_max3421 (hcd);
+	unsigned long flags;
+
+	printk ("%s()\n", __FUNCTION__);
+
+	spin_lock_irqsave (&max3421_hcd->lock, flags);
+
+	if (ep->hcpriv) {
+		struct max3421_ep *max3421_ep = ep->hcpriv;
+
+		// remove myself from the ep_list:
+		if (!list_empty (&max3421_ep->ep_list))
+			list_del (&max3421_ep->ep_list);
+		kfree (max3421_ep);
+		ep->hcpriv = NULL;
+	}
+
+	spin_unlock_irqrestore (&max3421_hcd->lock, flags);
+
+	printk("%s: done!\n", __FUNCTION__);
+}
+
+static int
+max3421_get_frame_number (struct usb_hcd *hcd)
+{
+	struct max3421_hcd *max3421_hcd = hcd_to_max3421 (hcd);
+	return max3421_hcd->frame_number;
+}
+
+/*
+ * Should return a non-zero value when any port is undergoing a resume transition while the
+ * root hub is suspended.
+ */
+static int
+max3421_hub_status_data (struct usb_hcd *hcd, char *buf)
+{
+	struct max3421_hcd *max3421_hcd = hcd_to_max3421 (hcd);
+	unsigned long flags;
+	int retval = 0;
+
+	spin_lock_irqsave (&max3421_hcd->lock, flags);
+	if (!HCD_HW_ACCESSIBLE (hcd))
+		goto done;
+
+	*buf = 0;
+	if ((max3421_hcd->port_status & PORT_C_MASK) != 0) {
+		*buf = (1 << 1);	// a hub over-current condition exists
+		dev_dbg (hcd->self.controller, "port status 0x%08x has changes\n",
+			 max3421_hcd->port_status);
+		retval = 1;
+		if (max3421_hcd->rh_state == MAX3421_RH_SUSPENDED)
+			usb_hcd_resume_root_hub (hcd);
+	}
+done:
+	printk ("%s(ret=%d,port_status_data=0x%x)\n", __FUNCTION__, retval, ((u8*)buf)[0]);
+	spin_unlock_irqrestore (&max3421_hcd->lock, flags);
+	return retval;
+}
+
+static inline void
+hub_descriptor (struct usb_hub_descriptor *desc)
+{
+	memset (desc, 0, sizeof (*desc));
+	/*
+	 * See Table 11-13: Hub Descriptor in USB 2.0 spec.
+	 */
+	desc->bDescriptorType = 0x29;	// hub descriptor
+	desc->bDescLength = 9;
+	desc->wHubCharacteristics = cpu_to_le16 (0x0001);
+	desc->bNbrPorts = 1;
+}
+
+static int
+max3421_hub_control (struct usb_hcd *hcd, u16 type_req, u16 value, u16 index,
+		     char *buf, u16 length)
+{
+	struct max3421_hcd *max3421_hcd = hcd_to_max3421 (hcd);
+	unsigned long flags;
+	int retval = 0;
+
+	spin_lock_irqsave (&max3421_hcd->lock, flags);
+
+	switch (type_req) {
+	case ClearHubFeature:
+		printk ("%s: ClearHubFeature\n", __FUNCTION__);
+		break;
+	case ClearPortFeature:
+		printk ("%s: ClearPortFeature(%d)\n", __FUNCTION__, value);
+		switch (value) {
+		case USB_PORT_FEAT_SUSPEND:
+			if (max3421_hcd->port_status & USB_PORT_STAT_SUSPEND) {
+				/* 20msec resume signaling */
+				max3421_hcd->resuming = 1;
+			}
+			break;
+		case USB_PORT_FEAT_POWER:
+			if (max3421_hcd->port_status & USB_SS_PORT_STAT_POWER)
+				dev_dbg (hcd->self.controller, "power-off\n");
+			/* FALLS THROUGH */
+		default:
+			max3421_hcd->port_status &= ~(1 << value);
+		}
+		break;
+	case GetHubDescriptor:
+		hub_descriptor ((struct usb_hub_descriptor *) buf);
+		break;
+
+	case DeviceRequest | USB_REQ_GET_DESCRIPTOR:
+	case GetPortErrorCount:
+	case SetHubDepth:
+		// USB3 only
+		goto error;
+
+	case GetHubStatus:
+		*(__le32 *) buf = cpu_to_le32 (0);
+		break;
+
+	case GetPortStatus:
+		if (index != 1) {
+			retval = -EPIPE;
+			goto error;
+		}
+		printk ("%s: GetPortStatus(%x) -> 0x%04x (chg 0x%04x)\n", __FUNCTION__, value, max3421_hcd->port_status & 0xffff, max3421_hcd->port_status >> 16);
+		((__le16 *) buf)[0] = cpu_to_le16(max3421_hcd->port_status);
+		((__le16 *) buf)[1] = cpu_to_le16(max3421_hcd->port_status >> 16);
+		break;
+
+	case SetHubFeature:
+		retval = -EPIPE;
+		break;
+
+	case SetPortFeature:
+		printk ("%s: SetPortFeature(%x)\n", __FUNCTION__, value);
+
+		switch (value) {
+		case USB_PORT_FEAT_LINK_STATE:
+		case USB_PORT_FEAT_U1_TIMEOUT:
+		case USB_PORT_FEAT_U2_TIMEOUT:
+		case USB_PORT_FEAT_BH_PORT_RESET:
+			goto error;
+		case USB_PORT_FEAT_SUSPEND:
+			if (max3421_hcd->active)
+				max3421_hcd->port_status |= USB_PORT_STAT_SUSPEND;
+			break;
+		case USB_PORT_FEAT_POWER:
+			max3421_hcd->port_status |= USB_PORT_STAT_POWER;
+			break;
+		case USB_PORT_FEAT_RESET:
+			/* if it's already enabled, disable */
+			max3421_reset_port (hcd);
+			/* FALLS THROUGH */
+		default:
+			if ((max3421_hcd->port_status & USB_PORT_STAT_POWER) != 0)
+				max3421_hcd->port_status |= (1 << value);
+		}
+		break;
+
+	default:
+		dev_dbg(hcd->self.controller,
+			"hub control req%04x v%04x i%04x l%d\n",
+			type_req, value, index, length);
+	error:
+		/* "protocol stall" on error */
+		retval = -EPIPE;
+	}
+
+	spin_unlock_irqrestore (&max3421_hcd->lock, flags);
+
+	if ((max3421_hcd->port_status & PORT_C_MASK) != 0)
+		usb_hcd_poll_rh_status (hcd);
+	return retval;
+}
+
+static int
+max3421_bus_suspend (struct usb_hcd *hcd)
+{
+	printk ("%s()\n", __FUNCTION__);
+	return -1;
+}
+
+static int
+max3421_bus_resume (struct usb_hcd *hcd)
+{
+	printk ("%s()\n", __FUNCTION__);
+	return -1;
+}
+
+/*
+ * The SPI driver already takes care of DMA-mapping/unmapping, so no reason to do it
+ * twice.
+ */
+static int
+max3421_map_urb_for_dma (struct usb_hcd *hcd, struct urb *urb, gfp_t mem_flags)
+{
+	return 0;
+}
+
+static void
+max3421_unmap_urb_for_dma (struct usb_hcd *hcd, struct urb *urb)
+{
+}
+
+static struct hc_driver max3421_hcd_desc = {
+	.description =		"max3421",
+	.product_desc =		DRIVER_DESC,
+	.hcd_priv_size =	sizeof (struct max3421_hcd),
+	.flags =		HCD_USB11,
+	.reset =		max3421_reset,
+	.start =		max3421_start,
+	.stop =			max3421_stop,
+	.get_frame_number =	max3421_get_frame_number,
+	.urb_enqueue =		max3421_urb_enqueue,
+	.urb_dequeue =		max3421_urb_dequeue,
+	.map_urb_for_dma =	max3421_map_urb_for_dma,
+	.unmap_urb_for_dma =	max3421_unmap_urb_for_dma,
+	.endpoint_disable =	max3421_endpoint_disable,
+	.hub_status_data =	max3421_hub_status_data,
+	.hub_control =		max3421_hub_control,
+	.bus_suspend =		max3421_bus_suspend,
+	.bus_resume =		max3421_bus_resume,
+};
+
+static int __devinit
+max3421_probe (struct spi_device *spi)
+{
+	struct max3421_hcd *max3421_hcd;
+	struct usb_hcd *hcd;
+	u8 rev, status;
+	int retval;
+
+	hcd = usb_create_hcd (&max3421_hcd_desc, &spi->dev, dev_name (&spi->dev));
+	if (!hcd) {
+		printk (KERN_ERR "%s: failed to create HCD structure\n",
+			__FUNCTION__);
+		return -ENOMEM;
+	}
+
+	if (spi_setup (spi) < 0) {
+		dev_err (&spi->dev, "Unable to setup SPI bus");
+		return -EFAULT;
+	}
+
+	// set full-duplex SPI mode, low-active interrupt pin:
+	spi_wr8 (spi, MAX3421_REG_PINCTL,
+		 (BIT (MAX3421_PINCTL_FDUPSPI_BIT) |	// full-duplex
+		  BIT (MAX3421_PINCTL_INTLEVEL_BIT)),	// low-level active irq
+		 NULL);
+
+	rev = spi_rd8 (spi, MAX3421_REG_REVISION, &status);
+	if (rev != 0x12 && rev != 0x13) {
+		dev_err (&spi->dev, "bad rev 0x%x (status 0x%x)", rev, status);
+		return -ENODEV;
+	}
+
+	printk (KERN_INFO "%s: chip rev 0x%x, SPI clock %dHz, bpw %u, irq %d\n",
+		__FUNCTION__, rev, spi->max_speed_hz, spi->bits_per_word, spi->irq);
+
+	max3421_hcd = hcd_to_max3421 (hcd);
+	max3421_hcd->next = max3421_hcd_list;
+	max3421_hcd_list = max3421_hcd;
+	max3421_hcd->chip_rev = rev;
+
+	retval = usb_add_hcd (hcd, 0, 0);
+	if (retval) {
+		printk (KERN_ERR "%s: failed to add HCD\n", __FUNCTION__);
+		usb_put_hcd (hcd);
+		return retval;
+	}
+
+	retval = request_threaded_irq (spi->irq, NULL, max3421_irq_thread,
+				       IRQF_TRIGGER_LOW | IRQF_ONESHOT, "max3421", hcd);
+	if (retval < 0) {
+		usb_put_hcd (hcd);
+		dev_err (&spi->dev, "failed to request irq %d", spi->irq);
+		return retval;
+	}
+	return 0;
+}
+
+static int __devexit
+max3421_remove (struct spi_device *spi)
+{
+	struct max3421_hcd *max3421_hcd = NULL, **prev;
+	struct usb_hcd *hcd = NULL;
+	unsigned long flags;
+
+	printk ("%s()\n", __FUNCTION__);
+
+	for (prev = &max3421_hcd_list; *prev; prev = &(*prev)->next) {
+		max3421_hcd = *prev;
+		hcd = max3421_to_hcd (max3421_hcd);
+		if (hcd->self.controller == &spi->dev)
+			break;
+	}
+	if (!max3421_hcd) {
+		printk (KERN_ERR "%s: no MAX3421 HCD found for SPI device %p\n",
+			__FUNCTION__, spi);
+		return -ENODEV;
+	}
+
+	spin_lock_irqsave (&max3421_hcd->lock, flags);
+
+	*prev = max3421_hcd->next;
+
+	spin_unlock_irqrestore (&max3421_hcd->lock, flags);
+
+	free_irq (spi->irq, hcd);
+
+	printk("calling usb_remove_hcd()\n");
+	usb_remove_hcd (hcd);
+	printk("calling usb_put_hcd()\n");
+	usb_put_hcd (hcd);
+	printk("back from usb_put_hcd()\n");
+	return 0;
+}
+
+static struct spi_driver max3421_driver = {
+	.probe		= max3421_probe,
+	.remove		= __devexit_p (max3421_remove),
+	.driver		= {
+		.name	= "max3421",
+		.owner	= THIS_MODULE,
+	},
+};
+
+static int __init
+max3421_mod_init (void)
+{
+	return spi_register_driver (&max3421_driver);
+}
+
+static void __exit
+max3421_mod_exit (void)
+{
+	spi_unregister_driver (&max3421_driver);
+}
+
+module_init(max3421_mod_init);
+module_exit(max3421_mod_exit);
+
+MODULE_DESCRIPTION (DRIVER_DESC);
+MODULE_AUTHOR ("eGauge Systems LLC");
+MODULE_LICENSE ("GPL");

[Index of Archives]     [Linux Media]     [Linux Input]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Old Linux USB Devel Archive]

  Powered by Linux