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 so 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 tested 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. I'm not sure how fancier OHCDs handle such cases. --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
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..61df0e6 --- /dev/null +++ b/drivers/usb/host/max3421.c @@ -0,0 +1,1406 @@ +/* + * 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. + */ + +#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.0" + +#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 max3421_rh_state { + MAX3421_RH_RESET, + MAX3421_RH_SUSPENDED, + MAX3421_RH_RUNNING +}; + +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 urbp_list; + + struct max3421_ep *last_ep; + + u8 chip_rev; // contents of REVISION register + u8 hien; + + unsigned active : 1; + unsigned resuming : 1; +}; + +struct max3421_ep { + unsigned recv_toggle : 1; // saved receive toggle + unsigned send_toggle : 1; // saved send toggle +}; + +/* XXX Fix me: we need this because the URB's list_head is already + being used to link the URB to the end-point. However, we shouldn't + need this list in the first place: it'd be better for the + max3421_irq_thread to iterate over active endpoints and then use the + URB-lists associated with the endpoints. */ +struct max3421_urbp { + struct urb *urb; + struct list_head urbp_list; +}; + +struct max3421_errata_workaround { + size_t len; + u8 *data; +}; + +static unsigned long qlen; +static unsigned long qint; +static unsigned long nakcnt; +static unsigned long dsptchcnt; + +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_MASK = 0xf, + MAX3421_HRSL_RCVTOGRD_BIT = 4, + MAX3421_HRSL_SNDTOGRD_BIT, + MAX3421_HRSL_KSTATUS_BIT, + MAX3421_HRSL_JSTATUS_BIT +}; + +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" +}; + +/* + * 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_SETUP 0x10 +#define MAX3421_HXFR_HS_IN 0x80 // handshake in +#define MAX3421_HXFR_HS_OUT 0xa0 // handshake out +#define MAX3421_HXFR_BULK_IN(ep) (0x00 | (ep)) // bulk or interrupt +#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 mask(bit) (1u << (bit)) +#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]; +// printk ("wr8: tx[%02x]=%02x\n", tx_data[0], tx_data[1]); +} + +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 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); +} + +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_set_address (struct usb_hcd *hcd, struct urb *urb) +{ + struct spi_device *spi = to_spi_device (hcd->self.controller); + struct max3421_hcd *max3421_hcd = hcd_to_max3421 (hcd); + struct max3421_ep *max3421_ep = urb->ep->hcpriv; + u8 addr; + + // set address: + addr = usb_pipedevice (urb->pipe); + spi_wr8 (spi, MAX3421_REG_PERADDR, addr, NULL); + + if (max3421_ep != max3421_hcd->last_ep) { + // end-point changed + u8 hctl; + if (max3421_hcd->last_ep) { + // save the old end-points toggles: + u8 hrsl = spi_rd8 (spi, MAX3421_REG_HRSL, NULL); + max3421_hcd->last_ep->recv_toggle = hrsl >> MAX3421_HRSL_RCVTOGRD_BIT; + max3421_hcd->last_ep->send_toggle = hrsl >> MAX3421_HRSL_SNDTOGRD_BIT; +// printk ("%s: switching from ep %p (R%dS%d) to ep %p (R%dS%d)\n", __FUNCTION__, max3421_hcd->last_ep, max3421_hcd->last_ep->recv_toggle, max3421_hcd->last_ep->send_toggle, max3421_ep, max3421_ep->recv_toggle, max3421_ep->send_toggle); + } + // restore new endpoint's toggle bits: + hctl = (mask (max3421_ep->recv_toggle + MAX3421_HCTL_RCVTOG0_BIT) | + mask (max3421_ep->send_toggle + MAX3421_HCTL_SNDTOG0_BIT)); + spi_wr8 (spi, MAX3421_REG_HCTL, hctl, NULL); + max3421_hcd->last_ep = max3421_ep; + } + return 0; +} + +static int +max3421_dispatch_packet (struct usb_hcd *hcd, struct urb *urb, u8 cmd, + struct max3421_errata_workaround *tx) +{ + struct spi_device *spi = to_spi_device (hcd->self.controller); + int retval = -ETIMEDOUT, timeout, retries; + u8 hrsl, hirq, result_code, m; + + ++dsptchcnt; + + for (retries = 0; retries < 8; ++retries) { + // start the transfer: +//printk ("%s: hxfr cmd 0x%02x\n", __FUNCTION__, cmd); + spi_wr8 (spi, MAX3421_REG_HXFR, cmd, NULL); + + // wait for HXFRDNIRQ XXX Fix me: this is a random timeout...: + m = mask (MAX3421_HI_HXFRDN_BIT); + for (timeout = 1000; timeout >= 0; --timeout) { + hrsl = spi_rd8 (spi, MAX3421_REG_HRSL, &hirq); + if (hirq & m) { + // ack host-transfer-done irq: + spi_wr8 (spi, MAX3421_REG_HIRQ, m, NULL); + break; + } + } +// printk("D %d\n", timeout); + if (timeout < 0) { + result_code = MAX3421_HRSL_TIMEOUT; + dev_err (&spi->dev, "%s: timeout waiting for host-transfer-done\n", + __FUNCTION__); + } else + result_code = hrsl & 0xf; + +#if 0 + if (result_code != MAX3421_HRSL_OK && result_code != MAX3421_HRSL_NAK) + dev_err (&spi->dev, "%s: HRSL=0x%02x (%s)\n", + __FUNCTION__, hrsl, hrsl_string[result_code]); +#endif + + switch (result_code) { + case MAX3421_HRSL_OK: return 0; + case MAX3421_HRSL_BUSY: return -EINVAL; // should never happen + case MAX3421_HRSL_BADREQ: return -EINVAL; // bad val in HXFR + case MAX3421_HRSL_UNDEF: return -EINVAL; // reserved + case MAX3421_HRSL_STALL: return -EPIPE; // peripheral returned stall + case MAX3421_HRSL_WRONGPID: return -EBADR; // received wrong PID + case MAX3421_HRSL_BADBC: return -EINVAL; // bad byte count + case MAX3421_HRSL_PIDERR: return -EBADR; // received PID is corrupted + case MAX3421_HRSL_PKTERR: return -EINVAL; // packet error (stuff, EOP) + case MAX3421_HRSL_CRCERR: return -EINVAL; // CRC error + case MAX3421_HRSL_KERR: return -EINVAL; // K-state instead of response + case MAX3421_HRSL_JERR: return -EINVAL; // J-state instead of response + case MAX3421_HRSL_BABBLE: return -EINVAL; // device talked too long + + case MAX3421_HRSL_TOGERR: +#if 0 + dev_err (&spi->dev, "%s: TOGGLE error on %s\n", __FUNCTION__, + usb_pipein (urb->pipe) ? "input" : "output"); +#endif + 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, + mask ((((hrsl >> MAX3421_HRSL_SNDTOGRD_BIT) & 1) ^ 1) + + MAX3421_HCTL_SNDTOG0_BIT), NULL); + break; + + case MAX3421_HRSL_NAK: + if (usb_pipeint (urb->pipe)) + break; + if (tx) { + /* + * Before revision 0x13, the first byte of the SNDFIFO gets + * corrupted when peripheral NAKs a send. Follow the procedure + * given in: + * + * http://datasheets.maximintegrated.com/en/errata/MAX3421E_REV1.pdf + * + * to work around this bug. + */ + spi_wr8 (spi, MAX3421_REG_SNDBC, 0, NULL); + spi_wr8 (spi, MAX3421_REG_SNDFIFO, tx->data[0], NULL); + spi_wr8 (spi, MAX3421_REG_SNDBC, tx->len, NULL); + } + ++nakcnt; + if (retries > 4) { + retries = 0; + msleep (1); + } + break; + + case MAX3421_HRSL_TIMEOUT: + // retry request + break; + } + } + return retval; +} + +static int +max3421_transfer_in (struct usb_hcd *hcd, struct urb *urb, u8 ep_addr) +{ + struct spi_device *spi = to_spi_device (hcd->self.controller); + u8 hirq, rcvbc, m_rcvdav = mask (MAX3421_HI_RCVDAV_BIT); + size_t remaining; + u16 max_packet; + int retval; + void *dst; + + max_packet = usb_maxpacket (urb->dev, urb->pipe, 0); + if (max_packet > MAX3421_FIFO_SIZE) + max_packet = MAX3421_FIFO_SIZE; + + remaining = urb->transfer_buffer_length; + dst = urb->transfer_buffer; + while (remaining > 0) { + // tell the device to send the data and wait for it to arrive: + if ((retval = max3421_dispatch_packet (hcd, urb, MAX3421_HXFR_BULK_IN (ep_addr), + NULL)) < 0) + return retval; + + hirq = spi_rd8 (spi, MAX3421_REG_HIRQ, NULL); + if (!(hirq & m_rcvdav)) { + // transfer completed but without receiving data... + dev_err (&spi->dev, "%s: transfer completed without data", __FUNCTION__); + return -ENODATA; + } + + /* + * The MAX3421E programming guide has conflicting info here: + * + * 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 cleares the RCVDAVIRQ bit. + * + * It appears the latter description is the correct one. Of course, it's easy + * to be off by one because if yoy get out of sync, there is no way to tell. + */ + do { + rcvbc = spi_rd8 (spi, MAX3421_REG_RCVBC, NULL); + if (rcvbc > MAX3421_FIFO_SIZE) + rcvbc = MAX3421_FIFO_SIZE; // shouldn't happen + if (rcvbc > remaining) + rcvbc = remaining; // probably should never happen... + spi_rd_fifo (spi, MAX3421_REG_RCVFIFO, dst, rcvbc, NULL); + + dst += rcvbc; + remaining -= rcvbc; + + // clear receive-data-available irq: + spi_wr8 (spi, MAX3421_REG_HIRQ, m_rcvdav, NULL); + hirq = spi_rd8 (spi, MAX3421_REG_HIRQ, NULL); + } while (hirq & m_rcvdav); + + if (rcvbc < max_packet/* && !(urb->transfer_flags & URB_SHORT_NOT_OK)*/) + // short reads are usually OK on USB... + break; + } + + urb->actual_length = urb->transfer_buffer_length - remaining; + + if (0) { + int i, m; + + printk ("%s: received %u bytes\n", __FUNCTION__, urb->actual_length); + printk ("data:"); + m = urb->actual_length; + if (m > 64) + m = 64; + for (i = 0; i < m; ++i) + printk (" %02x", ((u8*)urb->transfer_buffer)[i]); + printk ("\n"); + } + +#if 0 + if (remaining > 0 && urb->transfer_flags & URB_SHORT_NOT_OK) { + // This makes USB mass-storage fail; handled at higher level? + dev_err (&spi->dev, + "%s: %zu bytes remaining but short xfers not OK; returning EREMOTEIO\n", + __FUNCTION__, remaining); + return -EREMOTEIO; + } +#endif + + return 0; +} + +static int +max3421_transfer_out (struct usb_hcd *hcd, struct urb *urb, u8 ep_addr) +{ + struct spi_device *spi = to_spi_device (hcd->self.controller); + struct max3421_errata_workaround w, *errata_workaround = NULL; + struct max3421_hcd *max3421_hcd = hcd_to_max3421 (hcd); + size_t remaining, len; + int retval, timeout; + u16 max_packet; + u8 hirq, m; + void *src; + + max_packet = usb_maxpacket (urb->dev, urb->pipe, 1); + if (max_packet > MAX3421_FIFO_SIZE) + max_packet = MAX3421_FIFO_SIZE; + + remaining = urb->transfer_buffer_length; + src = urb->transfer_buffer; + while (remaining > 0) { + len = remaining; + if (len > max_packet) + len = max_packet; + + // wait for SNDBAV irq to be set + m = mask (MAX3421_HI_SNDBAV_BIT); + for (timeout = 1000; timeout >= 0; --timeout) { + hirq = spi_rd8 (spi, MAX3421_REG_HIRQ, NULL); + if (hirq & m) + break; + cond_resched (); + } +// printk("O %d\n", timeout); + if (timeout < 0) { + dev_err (&spi->dev, "%s: timed out waiting for send-buffer-available\n", + __FUNCTION__); + return -ETIMEDOUT; + } + + spi_wr_fifo (spi, MAX3421_REG_SNDFIFO, src, len, NULL); + spi_wr8 (spi, MAX3421_REG_SNDBC, len, NULL); + + if (max3421_hcd->chip_rev < 0x13) { + errata_workaround = &w; + w.len = len; + w.data = src; + } + + // send the data to the device and wait for it to arrive: + if ((retval = max3421_dispatch_packet (hcd, urb, MAX3421_HXFR_BULK_OUT (ep_addr), + errata_workaround)) < 0) + return retval; + + src += len; + remaining -= len; + } + urb->actual_length = urb->transfer_buffer_length; + return 0; +} + +static int +max3421_transfer (struct usb_hcd *hcd, struct urb *urb) +{ + u8 ep_addr = usb_pipeendpoint (urb->pipe); + int dir_in; + + dir_in = usb_pipein (urb->pipe); + +// printk ("%s: transfer %d bytes %s EP %d\n", __FUNCTION__, urb->transfer_buffer_length, dir_in ? "from" : "to", ep_addr); + + if (dir_in) + return max3421_transfer_in (hcd, urb, ep_addr); + else + return max3421_transfer_out (hcd, urb, ep_addr); +} + +static int +max3421_control_req (struct usb_hcd *hcd, struct urb *urb) +{ + struct spi_device *spi = to_spi_device (hcd->self.controller); + struct max3421_hcd *max3421_hcd = hcd_to_max3421 (hcd); + struct max3421_ep *max3421_ep = urb->ep->hcpriv; + int retval; + u8 cmd; + + // See USB 2.0 spec section 8.6.1 Initialization via SETUP Token + max3421_ep->recv_toggle = 1; + max3421_ep->send_toggle = 1; + max3421_hcd->last_ep = NULL; // force reload + + if ((retval = max3421_set_address (hcd, urb)) < 0) + return retval; + + if (0) { + int i; + printk ("%s: setup_packet =", __FUNCTION__); + for (i = 0; i < 8; ++i) + printk (" %02x", urb->setup_packet[i]); + printk ("\n"); + } + + // write setup packet to setup-FIFO: + spi_wr_fifo (spi, MAX3421_REG_SUDFIFO, urb->setup_packet, 8, NULL); + + // transfer the setup-packet: + if ((retval = max3421_dispatch_packet (hcd, urb, MAX3421_HXFR_SETUP, NULL)) < 0) + return retval; + + if (urb->transfer_buffer_length > 0) + if ((retval = max3421_transfer (hcd, urb)) < 0) + return retval; + + // terminate the control transfer with a handshake IN or OUT command: + if (usb_pipein (urb->pipe)) + cmd = MAX3421_HXFR_HS_OUT; // IN transfers use OUT token + else + cmd = MAX3421_HXFR_HS_IN; // and vice versa + return max3421_dispatch_packet (hcd, urb, cmd, NULL); +} + +static int +max3421_bulk_req (struct usb_hcd *hcd, struct urb *urb) +{ + int retval; + + if (urb->transfer_buffer_length == 0) + return 0; + + if ((retval = max3421_set_address (hcd, urb)) < 0) + return retval; + + return max3421_transfer (hcd, urb); +} + +static int +max3421_interrupt_req (struct usb_hcd *hcd, struct urb *urb) +{ + int retval; + + if (urb->transfer_buffer_length == 0) + return 0; + + if ((retval = max3421_set_address (hcd, urb)) < 0) + return retval; + + return max3421_transfer (hcd, urb); +} + +static int +max3421_isochronous_req (struct usb_hcd *hcd, struct urb *urb) +{ + printk("%s: implement me\n", __FUNCTION__); + return 0; +} + +static void +max3421_process_queue (struct usb_hcd *hcd) +{ + struct max3421_hcd *max3421_hcd = hcd_to_max3421 (hcd); + struct max3421_urbp *urbp, *tmp_urbp; + struct urb *urb; + int total = 64 /* bytes */ * 19 /* packets */; // USB_SPEED_FULL + int type, status; + + ++qint; + + // XXX should iterate over end-points and their URB-lists + list_for_each_entry_safe (urbp, tmp_urbp, &max3421_hcd->urbp_list, urbp_list) { + status = -EINPROGRESS; + + ++qlen; + + if (max3421_hcd->rh_state != MAX3421_RH_RUNNING) + return; + + urb = urbp->urb; + type = usb_pipetype (urb->pipe); + if (total <= 0 && type == PIPE_BULK) + // used up this frame's non-periodic bandwidth + continue; + + // We are commited to this URB now. Unlink it so we can release the spinlock. + list_del (&urbp->urbp_list); + kfree (urbp); + usb_hcd_unlink_urb_from_ep (hcd, urb); + spin_unlock (&max3421_hcd->lock); + + if (!urb->unlinked) { + switch (type) { + + case PIPE_CONTROL: +if (urb->number_of_packets > 1) printk ("number_of_packets %d interval %d\n", urb->number_of_packets, urb->interval); + status = max3421_control_req (hcd, urb); + break; + + case PIPE_BULK: + status = max3421_bulk_req (hcd, urb); + total -= urb->actual_length; + break; + + case PIPE_INTERRUPT: + status = max3421_interrupt_req (hcd, urb); + break; + + case PIPE_ISOCHRONOUS: + status = max3421_isochronous_req (hcd, urb); + break; + } + } +//printk ("type %d %s len %u buffer=%p EP=%u -> %d (actlen %d)\n", type, usb_pipein (urb->pipe) ? "IN " : "OUT", urb->transfer_buffer_length, urb->transfer_buffer, usb_pipeendpoint (urb->pipe), status, urb->actual_length); + usb_hcd_giveback_urb (hcd, urb, status); + spin_lock (&max3421_hcd->lock); + } +} + +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)); + + printk ("%s: JK=%x\n", __FUNCTION__, jk); + + 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 &= ~mask (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 ^= mask (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 & mask (MAX3421_MODE_LOWSPEED_BIT)) + // preceide every low-speed packet with a full-speed PRE PID + mode |= mask (MAX3421_MODE_HUBPRE_BIT); +#endif + // turn on SOFKAENAB bit: + mode |= mask (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 max3421_hcd *max3421_hcd; + struct usb_hcd *hcd = dev_id; + u32 chg, old_port_status; + struct spi_device *spi; + u8 hirq, ack; + + max3421_hcd = hcd_to_max3421 (hcd); + + spin_lock (&max3421_hcd->lock); + + spi = to_spi_device (hcd->self.controller); + + old_port_status = max3421_hcd->port_status; + + // read pending interrupts: + hirq = spi_rd8 (spi, MAX3421_REG_HIRQ, NULL); + // ack pending interrupts (but CPU must never clear SNDBAV directly): + ack = hirq & ~mask (MAX3421_HI_SNDBAV_BIT); + spi_wr8 (spi, MAX3421_REG_HIRQ, ack, NULL); + + if (hirq & mask (MAX3421_HI_BUSEVENT_BIT)) + printk ("%s: BUSEVENT\n", __FUNCTION__); + if (hirq & mask (MAX3421_HI_RWU_BIT)) + printk ("%s: RWU\n", __FUNCTION__); + if (hirq & mask (MAX3421_HI_RCVDAV_BIT)) + printk ("%s: RCVDAV\n", __FUNCTION__); + if (hirq & mask (MAX3421_HI_SUSDN_BIT)) + printk ("%s: SUSDN\n", __FUNCTION__); + if (hirq & mask (MAX3421_HI_CONDET_BIT)) + max3421_detect_conn (hcd); + if (hirq & mask (MAX3421_HI_FRAME_BIT)) { + // handle frame interrupt + 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; + } else if (hirq & mask (MAX3421_HI_SNDBAV_BIT)) + max3421_process_queue (hcd); // this releases/reacquires our lock! + } + if (hirq & mask (MAX3421_HI_HXFRDN_BIT)) + // handle frame interrupt + printk ("%s: HXFRDN\n", __FUNCTION__); + + spin_unlock (&max3421_hcd->lock); + + 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, mask (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, mask (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) & mask (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, + (mask (MAX3421_MODE_HOST_BIT) | + mask (MAX3421_MODE_SOFKAENAB_BIT) | + mask (MAX3421_MODE_DMPULLDN_BIT) | + mask (MAX3421_MODE_DPPULLDN_BIT)), NULL); + + // enable frame, connection-detected, and bus-event interrupts: + max3421_hcd->hien = (mask (MAX3421_HI_FRAME_BIT) | + mask (MAX3421_HI_CONDET_BIT) | + mask (MAX3421_HI_BUSEVENT_BIT)); + spi_wr8 (spi, MAX3421_REG_HIEN, max3421_hcd->hien, NULL); + + // clear pending host interrupts: + spi_wr8 (spi, MAX3421_REG_HIRQ, 0xff, NULL); + + // enable interrupts: + spi_wr8 (spi, MAX3421_REG_CPUCTL, mask (MAX3421_CPUCTL_IE_BIT), NULL); + + // sample the state of the D+ and D- lines + spi_wr8 (spi, MAX3421_REG_HCTL, mask (MAX3421_HCTL_SAMPLEBUS_BIT), NULL); + max3421_detect_conn (hcd); + 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->urbp_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 max3421_hcd *max3421_hcd = hcd_to_max3421 (hcd); + struct max3421_urbp *urbp; + int retval; + + if (0) { + static unsigned stats[4]; + static unsigned long last_time; + ++stats[usb_pipetype (urb->pipe)]; + if (jiffies - last_time > HZ) { + int i; + printk ("%s: stats", __FUNCTION__); + for (i = 0; i < 4; ++i) { + printk (" %u", stats[i]); + stats[i] = 0; + } + printk (" (nak %lu dsptch %lu qlen %lu qint %lu)\n", nakcnt, dsptchcnt, qlen, qint); + last_time = jiffies; + } + } + + + if (!urb->ep->hcpriv) { + // gets freed in max3421_endpoint_disable: + struct max3421_ep *max3421_ep = kzalloc (sizeof (struct max3421_ep), mem_flags); + if (!max3421_ep) + return -ENOMEM; + urb->ep->hcpriv = max3421_ep; + } + + urbp = kzalloc (sizeof (*urbp), mem_flags); + if (!urbp) + return -ENOMEM; + urbp->urb = urb; + + spin_lock (&max3421_hcd->lock); + + urb->hcpriv = urbp; + + if ((retval = usb_hcd_link_urb_to_ep (hcd, urb)) != 0) { + kfree (urbp); + goto done; + } + + list_add_tail (&urbp->urbp_list, &max3421_hcd->urbp_list); + +done: + spin_unlock (&max3421_hcd->lock); + return retval; +} + +static int +max3421_urb_dequeue (struct usb_hcd *hcd, struct urb *urb, int status) +{ + struct max3421_hcd *max3421_hcd; + int retval; + + printk ("%s(status=%d)\n", __FUNCTION__, status); + max3421_hcd = hcd_to_max3421 (hcd); + + spin_lock (&max3421_hcd->lock); + + /* + * This will set urb->unlinked which in turns causes the entry to be dropped + * in max3421_irq_thread: + */ + retval = usb_hcd_check_unlink_urb (hcd, urb, status); + + spin_unlock (&max3421_hcd->lock); + return retval; +} + +static void +max3421_endpoint_disable (struct usb_hcd *hcd, struct usb_host_endpoint *ep) +{ + if (ep->hcpriv) { + kfree (ep->hcpriv); + ep->hcpriv = NULL; + } +} + +static int +max3421_get_frame_number (struct usb_hcd *hcd) +{ + printk ("%s()\n", __FUNCTION__); + return -1; +} + +/* + * 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; + int retval = 0; + + max3421_hcd = hcd_to_max3421 (hcd); + + spin_lock (&max3421_hcd->lock); + 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 (&max3421_hcd->lock); + return retval; +} + +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); + int retval = 0; + + spin_lock (&max3421_hcd->lock); + + 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 (&max3421_hcd->lock); + + 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_USB2, + .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, + (mask (MAX3421_PINCTL_FDUPSPI_BIT) | // full-duplex + mask (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; + + 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 (&max3421_hcd->lock); + + *prev = max3421_hcd->next; + + spin_unlock (&max3421_hcd->lock); + + usb_remove_hcd (hcd); + usb_put_hcd (hcd); + 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");