All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] mmc: USB SDIO/SD/MMC Host Controller (VUB300) driver
       [not found]         ` <m3oc9n241c.fsf@pullcord.laptop.org>
@ 2010-11-22 15:05           ` Tony Olech
  2010-11-30  6:15             ` Chris Ball
                               ` (2 more replies)
  0 siblings, 3 replies; 41+ messages in thread
From: Tony Olech @ 2010-11-22 15:05 UTC (permalink / raw)
  To: Chris Ball; +Cc: linux-mmc, David Vrabel

[-- Attachment #1: Type: text/plain, Size: 1 bytes --]



[-- Attachment #2: vub300-driver-1.patch --]
[-- Type: text/plain, Size: 84758 bytes --]

Add a driver for Elan Digital System's VUB300 chip
which is a USB connected SDIO/SDmem/MMC host controller.
A VUB300 chip enables a USB 2.0 or USB 1.1 connected host
computer to use SDIO/SD/MMC cards without the need for
a directly connected, for example via PCI, SDIO host
controller.

Signed-off-by: Anthony F Olech <tony.olech@elandigitalsystems.com>
---
This is the first submission attempt.
There are 4 "do not initialise statics" errors reported by scripts/checkpatch.pl
This driver has been tested on
a) 32bit x86
b) 64bit x86
c) dual processor
d) PowerPC
---
--- linux-2.6.36-vanilla/MAINTAINERS	2010-10-20 21:30:22.000000000 +0100
+++ linux-2.6.36-vub300/MAINTAINERS	2010-11-22 09:13:04.000000000 +0000
@@ -6369,6 +6369,13 @@ L:	lm-sensors@lm-sensors.org
 S:	Maintained
 F:	drivers/hwmon/vt8231.c
 
+VUB300 USB to SDIO/SD/MMC bridge chip
+M:	Tony Olech <tony.olech@elandigitalsystems.com>
+L:	linux-mmc@vger.kernel.org
+L:	linux-usb@vger.kernel.org
+S:	Supported
+F:	drivers/mmc/host/vub300.c
+
 W1 DALLAS'S 1-WIRE BUS
 M:	Evgeniy Polyakov <johnpol@2ka.mipt.ru>
 S:	Maintained
--- linux-2.6.36-vanilla/drivers/mmc/host/Makefile	2010-10-20 21:30:22.000000000 +0100
+++ linux-2.6.36-vub300/drivers/mmc/host/Makefile	2010-11-17 12:00:53.000000000 +0000
@@ -36,6 +36,8 @@ obj-$(CONFIG_MMC_VIA_SDMMC)	+= via-sdmmc
 obj-$(CONFIG_SDH_BFIN)		+= bfin_sdh.o
 obj-$(CONFIG_MMC_SH_MMCIF)	+= sh_mmcif.o
 obj-$(CONFIG_MMC_JZ4740)	+= jz4740_mmc.o
+obj-$(CONFIG_MMC_VUB300)	+= vub300.o
+
 
 obj-$(CONFIG_MMC_SDHCI_PLTFM)			+= sdhci-platform.o
 sdhci-platform-y				:= sdhci-pltfm.o
--- linux-2.6.36-vanilla/drivers/mmc/host/Kconfig	2010-10-20 21:30:22.000000000 +0100
+++ linux-2.6.36-vub300/drivers/mmc/host/Kconfig	2010-11-19 15:35:36.000000000 +0000
@@ -451,3 +451,35 @@ config MMC_JZ4740
 	  SoCs.
 	  If you have a board based on such a SoC and with a SD/MMC slot,
 	  say Y or M here.
+
+config MMC_VUB300
+	tristate "VUB300 USB to SDIO/SD/MMC Host Controller support"
+	depends on USB
+	help
+	  This selects support for Elan Digital Systems' VUB300 chip.
+
+	  The VUB300 is a USB-SDIO Host Controller Interface chip
+	  that enables the host computer to use SDIO/SD/MMC cards
+	  via a USB 2.0 or USB 1.1 host.
+
+	  The VUB300 chip will be found in both physically separate
+	  USB to SDIO/SD/MMC adapters and embedded on some motherboards.
+
+	  The VUB300 chip supports SD and MMC memory cards in addition
+	  to single and multifunction SDIO cards.
+
+	  Some SDIO cards will need a firmware file to be loaded and
+	  sent to VUB300 chip in order to achieve better data throughput.
+	  Download these "Offload Pseudocode" from Elan Digital Systems'
+	  web-site http://www.elandigitalsystems.com/support/downloads.php
+	  and put them in /lib/firmware. Note that without these additional
+	  firmware files the VUB300 chip will still function, but not at
+	  the best obtainable data rate.
+
+	  To compile this mmc host controller driver as a module,
+	  choose M here: the module will be called vub300.
+
+	  If you have a computer with an embedded VUB300 chip
+	  or if you intend connecting a USB adapter based on a
+	  VUB300 chip say Y or M here.
+
--- /dev/null	2010-11-22 08:39:34.632000002 +0000
+++ linux-2.6.36-vub300/drivers/mmc/host/vub300.c	2010-11-22 13:59:00.000000000 +0000
@@ -0,0 +1,2606 @@
+/*
+ * Remote VUB300 SDIO/SDmem Host Controller Driver
+ *
+ * Copyright (C) 2010 Elan Digital Systems Limited
+ *
+ * based on USB Skeleton driver - 2.2
+ *
+ * Copyright (C) 2001-2004 Greg Kroah-Hartman (greg@kroah.com)
+ *
+ *	This program is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU General Public License as
+ *	published by the Free Software Foundation, version 2
+ *
+ * VUB300: is a USB 2.0 client device with a single SDIO/SDmem/MMC slot
+ *         Any SDIO/SDmem/MMC device plugged into the VUB300 will appear,
+ *         by virtue of this driver, to have been plugged into a local
+ *         SDIO host controller, similar to, say, a PCI Ricoh controller
+ *         This is because this kernel device driver is both a USB 2.0
+ *         client device driver AND an MMC host controller driver. Thus
+ *         if there is an existing driver for the inserted SDIO/SDmem/MMC
+ *         device then that driver will be used by the kernel to manage
+ *         the device in exactly the same fashion as if it had been
+ *         directly plugged into, say, a local pci bus Ricoh controller
+ *
+ * RANT: this driver was written using a display 128x48 - converting it
+ *       to a line width of 80 makes it very difficult to support. In
+ *       particular functions have been broken down into sub functions
+ *       and the original meaningful names have been shortened into
+ *       cryptic ones.
+ *       The problem is that executing a fragment of code subject to
+ *       two conditions means an indentation of 24, thus leaving only
+ *       56 characters for a C statement. And that is quite ridiculus!
+ *
+ * Data types: data passed to/from the VUB300 is fixed to a number of
+ *             bits and driver data fields reflect that limit by using
+ *             u8, u16, u32
+ */
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/kref.h>
+#include <linux/uaccess.h>
+#include <linux/usb.h>
+#include <linux/mutex.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/card.h>
+#include <linux/mmc/sdio_func.h>
+#include <linux/mmc/sdio_ids.h>
+#include <linux/workqueue.h>
+#include <linux/ctype.h>
+#include <linux/firmware.h>
+#include <linux/scatterlist.h>
+#pragma pack(1)
+struct HostController_Info {
+	u8 Size;
+	u16 FirmwareVer;
+	u8 NumberOfPorts;
+};
+struct SD_Command_Header {
+	u8 HeaderSize;
+	u8 HeaderType;
+	u8 PortNumber;
+	u8 Command_Type;	/* Bit7 - Rd/Wr */
+	u8 CommandIndex;
+	u8 TransferSize[4];	/* ReadSize + ReadSize */
+	u8 ResponseType;
+	u8 Arguments[4];
+	u8 BlockCount[2];
+	u8 BlockSize[2];
+	u8 BlockBoundary[2];
+	u8 Reserved[44];	/* to pad out to 64 bytes */
+};
+struct SD_IRQpoll_Header {
+	u8 HeaderSize;
+	u8 HeaderType;
+	u8 PortNumber;
+	u8 Command_Type;	/* Bit7 - Rd/Wr */
+	u8 Padding[16];		/* don't ask why !! */
+	u8 Poll_Timeout_MSB;
+	u8 Poll_Timeout_LSB;
+	u8 Reserved[42];	/* to pad out to 64 bytes */
+};
+struct SD_Common_Header {
+	u8 HeaderSize;
+	u8 HeaderType;
+	u8 PortNumber;
+};
+struct SD_Response_Header {
+	u8 HeaderSize;
+	u8 HeaderType;
+	u8 PortNumber;
+	u8 Command_Type;
+	u8 CommandIndex;
+	u8 CommandResponse[0];
+};
+struct SD_Status_Header {
+	u8 HeaderSize;
+	u8 HeaderType;
+	u8 PortNumber;
+	u16 PortFlags;
+	u32 SDIOclock;
+	u16 HostHeaderSize;
+	u16 FuncHeaderSize;
+	u16 CtrlHeaderSize;
+};
+struct SD_Error_Header {
+	u8 HeaderSize;
+	u8 HeaderType;
+	u8 PortNumber;
+	u8 ErrorCode;
+};
+struct SD_Interrupt_Header {
+	u8 HeaderSize;
+	u8 HeaderType;
+	u8 PortNumber;
+};
+struct Offload_Registers_Access {
+	u8 Command_Byte[4];
+	u8 Respond_Byte[4];
+};
+#define INTERRUPT_REGISTER_ACCESSES 15
+struct SD_Offloaded_Interrupt {
+	u8 HeaderSize;
+	u8 HeaderType;
+	u8 PortNumber;
+	struct Offload_Registers_Access reg[INTERRUPT_REGISTER_ACCESSES];
+};
+struct SD_Register_Header {
+	u8 HeaderSize;
+	u8 HeaderType;
+	u8 PortNumber;
+	u8 Command_Type;
+	u8 CommandIndex;
+	u8 CommandResponse[6];
+};
+#define PIGGYBACK_REGISTER_ACCESSES 14
+struct SD_Offloaded_Piggyback {
+	struct SD_Register_Header sdio;
+	struct Offload_Registers_Access reg[PIGGYBACK_REGISTER_ACCESSES];
+};
+union SD_Response {
+	struct SD_Common_Header common;
+	struct SD_Status_Header status;
+	struct SD_Error_Header error;
+	struct SD_Interrupt_Header interrupt;
+	struct SD_Response_Header response;
+	struct SD_Offloaded_Interrupt irq;
+	struct SD_Offloaded_Piggyback pig;
+};
+union SD_Command {
+	struct SD_Command_Header head;
+	struct SD_IRQpoll_Header poll;
+};
+enum SD_RESPONSE_TYPE {
+	SDRT_UNSPECIFIED = 0,
+	SDRT_NONE,
+	SDRT_1,
+	SDRT_1B,
+	SDRT_2,
+	SDRT_3,
+	SDRT_4,
+	SDRT_5,
+	SDRT_5B,
+	SDRT_6,
+	SDRT_7,
+};
+#pragma pack()
+#define RESPONSE_INTERRUPT 0x01
+#define RESPONSE_ERROR 0x02
+#define RESPONSE_STATUS 0x03
+#define RESPONSE_IRQ_DISABLED 0x05
+#define RESPONSE_IRQ_ENABLED 0x06
+#define RESPONSE_PIGGYBACKED 0x07
+#define RESPONSE_NO_INTERRUPT 0x08
+#define RESPONSE_PIG_DISABLED 0x09
+#define RESPONSE_PIG_ENABLED 0x0A
+#define SD_ERROR_1BIT_TIMEOUT   0x01
+#define SD_ERROR_4BIT_TIMEOUT   0x02
+#define SD_ERROR_1BIT_CRC_WRONG 0x03
+#define SD_ERROR_4BIT_CRC_WRONG 0x04
+#define SD_ERROR_1BIT_CRC_ERROR 0x05
+#define SD_ERROR_4BIT_CRC_ERROR 0x06
+#define SD_ERROR_NO_CMD_ENDBIT  0x07
+#define SD_ERROR_NO_1BIT_DATEND 0x08
+#define SD_ERROR_NO_4BIT_DATEND 0x09
+#define SD_ERROR_1BIT_UNEXPECTED_TIMEOUT    0x0A
+#define SD_ERROR_4BIT_UNEXPECTED_TIMEOUT    0x0B
+#define SD_ERROR_ILLEGAL_COMMAND    0x0C
+#define SD_ERROR_NO_DEVICE 0x0D
+#define SD_ERROR_TRANSFER_LENGTH    0x0E
+#define SD_ERROR_1BIT_DATA_TIMEOUT  0x0F
+#define SD_ERROR_4BIT_DATA_TIMEOUT  0x10
+#define SD_ERROR_ILLEGAL_STATE  0x11
+#define SD_ERROR_UNKNOWN_ERROR  0x12
+#define SD_ERROR_RESERVED_ERROR 0x13
+#define SD_ERROR_INVALID_FUNCTION   0x14
+#define SD_ERROR_OUT_OF_RANGE   0x15
+#define SD_ERROR_STAT_CMD 0x16
+#define SD_ERROR_STAT_DATA 0x17
+#define SD_ERROR_STAT_CMD_TIMEOUT 0x18
+#define SD_ERROR_SDCRDY_STUCK 0x19
+#define SD_ERROR_UNHANDLED 0x1A
+#define SD_ERROR_OVERRUN 0x1B
+#define SD_ERROR_PIO_TIMEOUT 0x1C
+MODULE_AUTHOR("Tony Olech <tony.olech@elandigitalsystems.com>");
+MODULE_DESCRIPTION("VUB300 USB to SD/MMC/SDIO adapter driver");
+MODULE_LICENSE("GPL");
+#define FUN(c) (0x000007 & (c->arg>>28))
+#define REG(c) (0x01FFFF & (c->arg>>9))
+static int limit_speed_to_24_MHz = 1;
+module_param(limit_speed_to_24_MHz, bool, 0644);
+MODULE_PARM_DESC(limit_speed_to_24_MHz, "Limit Max SDIO Clock Speed to 24 MHz");
+static int pad_input_to_usb_pkt = 0;
+module_param(pad_input_to_usb_pkt, bool, 0644);
+MODULE_PARM_DESC(pad_input_to_usb_pkt,
+		 "Pad USB data input transfers to whole USB Packet");
+static int disable_offload_processing = 0;
+module_param(disable_offload_processing, bool, 0644);
+MODULE_PARM_DESC(disable_offload_processing, "Disable Offload Processing");
+static int force_1_bit_data_xfers = 0;
+module_param(force_1_bit_data_xfers, bool, 0644);
+MODULE_PARM_DESC(force_1_bit_data_xfers,
+		 "Force SDIO Data Transfers to 1-bit Mode");
+static int force_polling_for_irqs = 0;
+module_param(force_polling_for_irqs, bool, 0644);
+MODULE_PARM_DESC(force_polling_for_irqs, "Force Polling for SDIO interrupts");
+static int firmware_irqpoll_timeout = 1024;
+module_param(firmware_irqpoll_timeout, int, 0644);
+MODULE_PARM_DESC(firmware_irqpoll_timeout, "VUB300 firmware irqpoll timeout");
+static int force_max_req_size = 128;
+module_param(force_max_req_size, int, 0644);
+MODULE_PARM_DESC(force_max_req_size, "set max request size in kBytes");
+#ifdef SMSC_DEVELOPMENT_BOARD
+static int firmware_rom_wait_states = 0x04;
+#else
+static int firmware_rom_wait_states = 0x1C;
+#endif
+module_param(firmware_rom_wait_states, bool, 0644);
+MODULE_PARM_DESC(firmware_rom_wait_states,
+		 "ROM wait states byte=RRRIIEEE (Reserved Internal External)");
+/* Define these values to match your devices */
+#define ELAN_VENDOR_ID			0x2201
+#define VUB300_VENDOR_ID		0x0424
+#define VUB300_PRODUCT_ID		0x012C
+#define FIRMWARE_BLOCK_BOUNDARY 1024
+/* table of devices that work with this driver */
+static struct usb_device_id vub300_table[] = {
+	{USB_DEVICE(ELAN_VENDOR_ID, VUB300_PRODUCT_ID)},
+	{USB_DEVICE(VUB300_VENDOR_ID, VUB300_PRODUCT_ID)},
+	{}			/* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE(usb, vub300_table);
+static struct workqueue_struct *cmndworkqueue;
+static struct workqueue_struct *pollworkqueue;
+static struct workqueue_struct *deadworkqueue;
+static inline int interface_to_InterfaceNumber(struct usb_interface *interface)
+{
+	if (!interface)
+		return -1;
+	if (!interface->cur_altsetting)
+		return -1;
+	return interface->cur_altsetting->desc.bInterfaceNumber;
+}
+
+struct sdio_register {
+	unsigned func_num:3;
+	unsigned sdio_reg:17;
+	unsigned activate:1;
+	unsigned prepared:1;
+	unsigned regvalue:8;
+	unsigned response:8;
+	unsigned sparebit:26;
+};
+struct vub300_mmc_host {
+	struct usb_device *udev;
+	struct usb_interface *interface;
+	struct kref kref;
+	struct mutex cmd_mutex;
+	struct mutex irq_mutex;
+	char vub_name[3 + (9 * 8) + 4 + 1];	/* max of 7 sdio fn's */
+	u8 cmnd_out_ep;		/* EndPoint for commands */
+	u8 cmnd_res_ep;		/* EndPoint for responses */
+	u8 data_out_ep;		/* EndPoint for out data */
+	u8 data_inp_ep;		/* EndPoint for inp data */
+	unsigned card_powered:1;
+	unsigned card_present:1;
+	unsigned read_only:1;
+	unsigned large_usb_packets:1;
+	unsigned app_spec:1;	/* ApplicationSpecific */
+	unsigned irq_enabled:1;	/* by the MMC CORE */
+	unsigned irq_disabled:1;	/* in the firmware */
+	unsigned bus_width:4;
+	u8 total_offload_count;
+	u8 dynamic_register_count;
+	u8 resp_len;
+	u32 datasize;
+	int errors;
+	int usb_transport_fail;
+	int usb_timed_out;
+	int irqs_queued;
+	struct sdio_register sdio_register[16];
+	struct offload_interrupt_function_register {
+#define MAXREGBITS 4
+#define MAXREGS (1<<MAXREGBITS)
+#define MAXREGMASK (MAXREGS-1)
+		u8 offload_count;
+		u32 offload_point;
+		struct Offload_Registers_Access reg[MAXREGS];
+	} fn[8];
+	u16 fbs[8];		/* Function Block Size */
+	struct mmc_command *cmd;
+	struct mmc_request *req;
+	struct mmc_data *data;
+	struct mmc_host *mmc;
+	struct urb *urb;
+	struct urb *command_out_urb;
+	struct urb *command_res_urb;
+	struct completion command_complete;
+	struct completion irqpoll_complete;
+	union SD_Command cmnd;
+	union SD_Response resp;
+	struct timer_list sg_transfer_timer;
+	struct usb_sg_request sg_request;
+	struct timer_list inactivity_timer;
+	struct work_struct deadwork;
+	struct work_struct cmndwork;
+	struct delayed_work pollwork;
+	struct HostController_Info hc_info;
+	struct SD_Status_Header system_port_status;
+	u8 padded_buffer[64];
+};
+#define kref_to_vub300_mmc_host(d) container_of(d, struct vub300_mmc_host, kref)
+#define SET_TRANSFER_PSEUDOCODE 21
+#define SET_INTERRUPT_PSEUDOCODE 20
+#define SET_FAILURE_MODE 18
+#define SET_ROM_WAIT_STATES 16
+#define SET_IRQ_ENABLE 13
+#define SET_CLOCK_SPEED 11
+#define SET_FUNCTION_BLOCK_SIZE 9
+#define SET_SD_DATA_MODE 6
+#define SET_SD_POWER 4
+#define ENTER_DFU_MODE 3
+#define GET_HC_INF0 1
+#define GET_SYSTEM_PORT_STATUS 0
+static void vub300_delete(struct kref *kref)
+{				/* kref callback - softirq */
+	struct vub300_mmc_host *vub300 = kref_to_vub300_mmc_host(kref);
+	struct mmc_host *mmc = vub300->mmc;
+	usb_free_urb(vub300->command_out_urb);
+	vub300->command_out_urb = NULL;
+	usb_free_urb(vub300->command_res_urb);
+	vub300->command_res_urb = NULL;
+	usb_put_dev(vub300->udev);
+	mmc_free_host(mmc);
+	/*
+	 * and hence also frees vub300
+	 * which is contained at the end of struct mmc
+	 */
+}
+
+static ssize_t __show_OperatingMode(struct vub300_mmc_host *vub300,
+				    struct mmc_host *mmc, char *buf)
+{
+	int usb_packet_size = vub300->large_usb_packets ? 512 : 64;
+	return sprintf(buf, "VUB %s %s %d MHz %s %d byte USB packets using %s",
+		       (mmc->caps & MMC_CAP_SDIO_IRQ) ? "IRQs" : "POLL",
+		       (mmc->caps & MMC_CAP_4_BIT_DATA) ? "4-bit" : "1-bit",
+		       mmc->f_max / 1000000,
+		       pad_input_to_usb_pkt ? "padding input data to" : "with",
+		       usb_packet_size, vub300->vub_name);
+}
+
+static ssize_t show_OperatingMode(struct device *dev,
+				  struct device_attribute *attr, char *buf)
+{
+	struct usb_interface *intf = to_usb_interface(dev);
+	struct vub300_mmc_host *vub300 = usb_get_intfdata(intf);
+	struct mmc_host *mmc = vub300->mmc;
+	if (mmc)
+		return __show_OperatingMode(vub300, mmc, buf);
+	else
+		return sprintf(buf, "VUB driver has no attached device");
+}
+
+static DEVICE_ATTR(OperatingMode, S_IRUGO, show_OperatingMode, NULL);
+static struct attribute *vub300_attrs[] = {
+	&dev_attr_OperatingMode.attr,
+	NULL,
+};
+
+static struct attribute_group vub300_attr_grp = {
+	.attrs = vub300_attrs,
+};
+
+static void vub300_queue_cmnd_work(struct vub300_mmc_host *vub300)
+{
+	kref_get(&vub300->kref);
+	if (queue_work(cmndworkqueue, &vub300->cmndwork)) {
+		/*
+		 * then the cmndworkqueue was not previously
+		 * running and the above get ref is obvious
+		 * required and will be put when the thread
+		 * terminates by a specific call
+		 */
+	} else {
+		/*
+		 * the cmndworkqueue was already running from
+		 * a previous invocation and thus to keep the
+		 * kref counts correct we must undo the get
+		 */
+		kref_put(&vub300->kref, vub300_delete);
+	}
+}
+
+static void vub300_queue_poll_work(struct vub300_mmc_host *vub300, int delay)
+{
+	kref_get(&vub300->kref);
+	if (queue_delayed_work(pollworkqueue, &vub300->pollwork, delay)) {
+		/*
+		 * then the pollworkqueue was not previously
+		 * running and the above get ref is obvious
+		 * required and will be put when the thread
+		 * terminates by a specific call
+		 */
+	} else {
+		/*
+		 * the pollworkqueue was already running from
+		 * a previous invocation and thus to keep the
+		 * kref counts correct we must undo the get
+		 */
+		kref_put(&vub300->kref, vub300_delete);
+	}
+}
+
+static void vub300_queue_dead_work(struct vub300_mmc_host *vub300)
+{
+	kref_get(&vub300->kref);
+	if (queue_work(deadworkqueue, &vub300->deadwork)) {
+		/*
+		 * then the deadworkqueue was not previously
+		 * running and the above get ref is obvious
+		 * required and will be put when the thread
+		 * terminates by a specific call
+		 */
+	} else {
+		/*
+		 * the deadworkqueue was already running from
+		 * a previous invocation and thus to keep the
+		 * kref counts correct we must undo the get
+		 */
+		kref_put(&vub300->kref, vub300_delete);
+	}
+}
+
+static void irqpoll_res_completed(struct urb *urb)
+{				/* urb completion handler - hardirq */
+	/*
+	 * cmd_mutex is held by vub300_pollwork_thread
+	 */
+	struct vub300_mmc_host *vub300 = (struct vub300_mmc_host *)urb->context;
+	if (urb->status)
+		vub300->usb_transport_fail = urb->status;
+	complete(&vub300->irqpoll_complete);
+}
+
+static void irqpoll_out_completed(struct urb *urb)
+{				/* urb completion handler - hardirq */
+	/*
+	 * cmd_mutex is held by vub300_pollwork_thread
+	 */
+	struct vub300_mmc_host *vub300 = (struct vub300_mmc_host *)urb->context;
+	if (urb->status) {
+		vub300->usb_transport_fail = urb->status;
+		complete(&vub300->irqpoll_complete);
+		return;
+	} else {
+		int ret;
+		unsigned int pipe =
+		    usb_rcvbulkpipe(vub300->udev, vub300->cmnd_res_ep);
+		usb_fill_bulk_urb(vub300->command_res_urb, vub300->udev, pipe,
+				  &vub300->resp, sizeof(vub300->resp),
+				  irqpoll_res_completed, vub300);
+		vub300->command_res_urb->actual_length = 0;
+		ret = usb_submit_urb(vub300->command_res_urb, GFP_NOIO);
+		if (ret) {
+			vub300->usb_transport_fail = ret;
+			complete(&vub300->irqpoll_complete);
+		}
+		return;
+	}
+}
+
+static void send_irqpoll(struct vub300_mmc_host *vub300)
+{
+	/*
+	 * cmd_mutex is held by vub300_pollwork_thread
+	 */
+	int retval;
+	int timeout = 0xFFFF & (0x0001FFFF - firmware_irqpoll_timeout);
+	vub300->cmnd.poll.HeaderSize = 22;
+	vub300->cmnd.poll.HeaderType = 1;
+	vub300->cmnd.poll.PortNumber = 0;
+	vub300->cmnd.poll.Command_Type = 2;
+	vub300->cmnd.poll.Poll_Timeout_LSB = 0xFF & (unsigned)timeout;
+	vub300->cmnd.poll.Poll_Timeout_MSB = 0xFF & (unsigned)(timeout >> 8);
+	usb_fill_bulk_urb(vub300->command_out_urb, vub300->udev,
+			  usb_sndbulkpipe(vub300->udev, vub300->cmnd_out_ep)
+			  , &vub300->cmnd, sizeof(vub300->cmnd)
+			  , irqpoll_out_completed, vub300);
+	retval = usb_submit_urb(vub300->command_out_urb, GFP_ATOMIC);
+	if (0 > retval) {
+		vub300->usb_transport_fail = retval;
+		vub300_queue_poll_work(vub300, 1);
+		complete(&vub300->irqpoll_complete);
+		return;
+	} else {
+		return;
+	}
+}
+
+static void new_system_port_status(struct vub300_mmc_host *vub300)
+{
+	int old_card_present = vub300->card_present;
+	int new_card_present =
+	    (0x0001 & vub300->system_port_status.PortFlags) ? 1 : 0;
+	vub300->read_only =
+	    (0x0010 & vub300->system_port_status.PortFlags) ? 1 : 0;
+	if (new_card_present && !old_card_present) {
+		dev_info(&vub300->udev->dev, "card just inserted");
+		vub300->card_present = 1;
+		vub300->bus_width = 0;
+		if (disable_offload_processing)
+			strncpy(vub300->vub_name, "EMPTY Processing Disabled",
+				sizeof(vub300->vub_name));
+		else
+			vub300->vub_name[0] = 0;
+		mmc_detect_change(vub300->mmc, 1);
+		return;
+	} else if (!new_card_present && old_card_present) {
+		dev_info(&vub300->udev->dev, "card just ejected");
+		vub300->card_present = 0;
+		mmc_detect_change(vub300->mmc, 0);
+		return;
+	} else {
+		return;
+	}
+}
+
+static void __add_offloaded_reg_to_fifo(struct vub300_mmc_host *vub300,
+					struct Offload_Registers_Access
+					*register_access, u8 Function)
+{
+	u8 r =
+	    vub300->fn[Function].offload_point +
+	    vub300->fn[Function].offload_count;
+	memcpy(&vub300->fn[Function].reg[MAXREGMASK & r]
+	       , register_access, sizeof(struct Offload_Registers_Access));
+	vub300->fn[Function].offload_count += 1;
+	vub300->total_offload_count += 1;
+}
+
+static void add_offloaded_reg(struct vub300_mmc_host *vub300,
+			      struct Offload_Registers_Access *register_access)
+{
+	u32 Register = ((0x03 & register_access->Command_Byte[0]) << 15)
+	    | ((0xFF & register_access->Command_Byte[1]) << 7)
+	    | ((0xFE & register_access->Command_Byte[2]) >> 1);
+	u8 Function = ((0x70 & register_access->Command_Byte[0]) >> 4);
+	u8 regs = vub300->dynamic_register_count;
+	u8 i = 0;
+	while (0 < regs-- && 1 == vub300->sdio_register[i].activate) {
+		if ((vub300->sdio_register[i].func_num == Function)
+		    && (vub300->sdio_register[i].sdio_reg == Register)
+		    ) {
+			if (0 == vub300->sdio_register[i].prepared)
+				vub300->sdio_register[i].prepared = 1;
+			vub300->sdio_register[i].response =
+			    register_access->Respond_Byte[2];
+			vub300->sdio_register[i].regvalue =
+			    register_access->Respond_Byte[3];
+			return;
+		} else {
+			i += 1;
+			continue;
+		}
+	};
+	__add_offloaded_reg_to_fifo(vub300, register_access, Function);
+}
+
+static void check_vub300_port_status(struct vub300_mmc_host *vub300)
+{
+	/*
+	 * cmd_mutex is held by vub300_pollwork_thread,
+	 * vub300_deadwork_thread or vub300_cmndwork_thread
+	 */
+	int retval;
+	retval =
+	    usb_control_msg(vub300->udev, usb_rcvctrlpipe(vub300->udev, 0),
+			    GET_SYSTEM_PORT_STATUS,
+			    USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			    0x0000, 0x0000, &vub300->system_port_status,
+			    sizeof(vub300->system_port_status), HZ);
+	if (sizeof(vub300->system_port_status) == retval)
+		new_system_port_status(vub300);
+}
+
+static void __vub300_irqpoll_response(struct vub300_mmc_host *vub300)
+{
+	/*
+	 * cmd_mutex is held by vub300_pollwork_thread
+	 */
+	if (0 == vub300->command_res_urb->actual_length) {
+		mutex_unlock(&vub300->cmd_mutex);
+		kref_put(&vub300->kref, vub300_delete);
+		return;
+	} else if (RESPONSE_INTERRUPT == vub300->resp.common.HeaderType) {
+		mutex_lock(&vub300->irq_mutex);
+		if (vub300->irq_enabled)
+			mmc_signal_sdio_irq(vub300->mmc);
+		else
+			vub300->irqs_queued += 1;
+		vub300->irq_disabled = 1;
+		mutex_unlock(&vub300->irq_mutex);
+		mutex_unlock(&vub300->cmd_mutex);
+		kref_put(&vub300->kref, vub300_delete);
+		return;
+	} else if (RESPONSE_ERROR == vub300->resp.common.HeaderType) {
+		if (SD_ERROR_NO_DEVICE == vub300->resp.error.ErrorCode)
+			check_vub300_port_status(vub300);
+		mutex_unlock(&vub300->cmd_mutex);
+		vub300_queue_poll_work(vub300, HZ);
+		kref_put(&vub300->kref, vub300_delete);
+		return;
+	} else if (RESPONSE_STATUS == vub300->resp.common.HeaderType) {
+		vub300->system_port_status = vub300->resp.status;
+		new_system_port_status(vub300);
+		mutex_unlock(&vub300->cmd_mutex);
+		if (!vub300->card_present)
+			vub300_queue_poll_work(vub300, HZ / 5);
+		kref_put(&vub300->kref, vub300_delete);
+		return;
+	} else if (RESPONSE_IRQ_DISABLED == vub300->resp.common.HeaderType) {
+		int offloaded_data_length = vub300->resp.common.HeaderSize - 3;
+		int register_count = offloaded_data_length >> 3;
+		int ri = 0;
+		while (register_count--) {
+			add_offloaded_reg(vub300, &vub300->resp.irq.reg[ri]);
+			ri += 1;
+		}
+		mutex_lock(&vub300->irq_mutex);
+		if (vub300->irq_enabled)
+			mmc_signal_sdio_irq(vub300->mmc);
+		else
+			vub300->irqs_queued += 1;
+		vub300->irq_disabled = 1;
+		mutex_unlock(&vub300->irq_mutex);
+		mutex_unlock(&vub300->cmd_mutex);
+		kref_put(&vub300->kref, vub300_delete);
+		return;
+	} else if (RESPONSE_IRQ_ENABLED == vub300->resp.common.HeaderType) {
+		int offloaded_data_length = vub300->resp.common.HeaderSize - 3;
+		int register_count = offloaded_data_length >> 3;
+		int ri = 0;
+		while (register_count--) {
+			add_offloaded_reg(vub300, &vub300->resp.irq.reg[ri]);
+			ri += 1;
+		}
+		mutex_lock(&vub300->irq_mutex);
+		if (vub300->irq_enabled)
+			mmc_signal_sdio_irq(vub300->mmc);
+		else if (vub300->irqs_queued)
+			vub300->irqs_queued += 1;
+		else
+			vub300->irqs_queued += 1;
+		vub300->irq_disabled = 0;
+		mutex_unlock(&vub300->irq_mutex);
+		mutex_unlock(&vub300->cmd_mutex);
+		kref_put(&vub300->kref, vub300_delete);
+		return;
+	} else if (RESPONSE_NO_INTERRUPT == vub300->resp.common.HeaderType) {
+		vub300_queue_poll_work(vub300, 1);
+		mutex_unlock(&vub300->cmd_mutex);
+		kref_put(&vub300->kref, vub300_delete);
+		return;
+	} else {
+		mutex_unlock(&vub300->cmd_mutex);
+		kref_put(&vub300->kref, vub300_delete);
+		return;
+	}
+}
+
+static void __do_poll(struct vub300_mmc_host *vub300)
+{
+	/*
+	 * cmd_mutex is held by vub300_pollwork_thread
+	 */
+	long commretval;
+	mod_timer(&vub300->inactivity_timer, jiffies + HZ);
+	init_completion(&vub300->irqpoll_complete);
+	send_irqpoll(vub300);
+	commretval =
+	    wait_for_completion_timeout(&vub300->irqpoll_complete,
+					msecs_to_jiffies(500));
+	if (vub300->usb_transport_fail) {
+		mutex_unlock(&vub300->cmd_mutex);
+		kref_put(&vub300->kref, vub300_delete);
+		return;
+	} else if (0 == commretval) {
+		vub300->usb_timed_out = 1;
+		usb_kill_urb(vub300->command_out_urb);
+		usb_kill_urb(vub300->command_res_urb);
+		mutex_unlock(&vub300->cmd_mutex);
+		kref_put(&vub300->kref, vub300_delete);
+		return;
+	} else if (0 > commretval) {
+		vub300_queue_poll_work(vub300, 1);
+		mutex_unlock(&vub300->cmd_mutex);
+		kref_put(&vub300->kref, vub300_delete);
+		return;
+	} else {		/*(0 < commretval) */
+
+		__vub300_irqpoll_response(vub300);
+		return;
+	}
+}
+
+/* this thread runs only when the driver
+ * is trying to poll the device for an IRQ
+ */
+static void vub300_pollwork_thread(struct work_struct *work)
+{				/* NOT irq */
+	struct vub300_mmc_host *vub300 =
+	    container_of(work, struct vub300_mmc_host, pollwork.work);
+	if (!vub300->interface) {
+		kref_put(&vub300->kref, vub300_delete);
+		return;
+	}
+	mutex_lock(&vub300->cmd_mutex);
+	if (vub300->cmd) {
+		mutex_unlock(&vub300->cmd_mutex);
+		vub300_queue_poll_work(vub300, 1);
+		kref_put(&vub300->kref, vub300_delete);
+	} else if (!vub300->card_present) {
+		mutex_unlock(&vub300->cmd_mutex);
+		kref_put(&vub300->kref, vub300_delete);
+	} else {		/* vub300->card_present */
+		mutex_lock(&vub300->irq_mutex);
+		if (!vub300->irq_enabled) {
+			mutex_unlock(&vub300->irq_mutex);
+			mutex_unlock(&vub300->cmd_mutex);
+			kref_put(&vub300->kref, vub300_delete);
+		} else if (vub300->irqs_queued) {
+			vub300->irqs_queued -= 1;
+			mmc_signal_sdio_irq(vub300->mmc);
+			mod_timer(&vub300->inactivity_timer, jiffies + HZ);
+			mutex_unlock(&vub300->irq_mutex);
+			mutex_unlock(&vub300->cmd_mutex);
+			kref_put(&vub300->kref, vub300_delete);
+		} else {	/* NOT vub300->irqs_queued */
+			mutex_unlock(&vub300->irq_mutex);
+			__do_poll(vub300);
+		}
+	}
+}
+
+static void vub300_deadwork_thread(struct work_struct *work)
+{				/* NOT irq */
+	struct vub300_mmc_host *vub300 =
+	    container_of(work, struct vub300_mmc_host, deadwork);
+	if (!vub300->interface) {
+		kref_put(&vub300->kref, vub300_delete);
+		return;
+	}
+	mutex_lock(&vub300->cmd_mutex);
+	if (vub300->cmd) {
+		/*
+		 * a command got in as the inactivity
+		 * timer expired - so we just let the
+		 * processing of the command show if
+		 * the device is dead
+		 */
+	} else if (vub300->card_present) {
+		check_vub300_port_status(vub300);
+	} else if (vub300->mmc && vub300->mmc->card
+		   && mmc_card_present(vub300->mmc->card)) {
+		/*
+		 * the MMC core must not have responded
+		 * to the previous indication - lets
+		 * hope that it eventually does so we
+		 * will just ignore this for now
+		 */
+	} else {
+		check_vub300_port_status(vub300);
+	}
+	mod_timer(&vub300->inactivity_timer, jiffies + HZ);
+	mutex_unlock(&vub300->cmd_mutex);
+	kref_put(&vub300->kref, vub300_delete);
+}
+
+static void vub300_inactivity_timer_expired(unsigned long data)
+{				/* softirq */
+	struct vub300_mmc_host *vub300 = (struct vub300_mmc_host *)data;
+	if (!vub300->interface) {
+		kref_put(&vub300->kref, vub300_delete);
+	} else if (vub300->cmd) {
+		mod_timer(&vub300->inactivity_timer, jiffies + HZ);
+	} else {
+		vub300_queue_dead_work(vub300);
+		mod_timer(&vub300->inactivity_timer, jiffies + HZ);
+	}
+}
+
+static int vub300_response_error(u8 ErrorCode)
+{
+	switch (ErrorCode) {
+	case SD_ERROR_PIO_TIMEOUT:
+	case SD_ERROR_1BIT_TIMEOUT:
+	case SD_ERROR_4BIT_TIMEOUT:
+		return -ETIMEDOUT;
+	case SD_ERROR_STAT_DATA:
+	case SD_ERROR_OVERRUN:
+	case SD_ERROR_STAT_CMD:
+	case SD_ERROR_STAT_CMD_TIMEOUT:
+	case SD_ERROR_SDCRDY_STUCK:
+	case SD_ERROR_UNHANDLED:
+	case SD_ERROR_1BIT_CRC_WRONG:
+	case SD_ERROR_4BIT_CRC_WRONG:
+	case SD_ERROR_1BIT_CRC_ERROR:
+	case SD_ERROR_4BIT_CRC_ERROR:
+	case SD_ERROR_NO_CMD_ENDBIT:
+	case SD_ERROR_NO_1BIT_DATEND:
+	case SD_ERROR_NO_4BIT_DATEND:
+	case SD_ERROR_1BIT_DATA_TIMEOUT:
+	case SD_ERROR_4BIT_DATA_TIMEOUT:
+	case SD_ERROR_1BIT_UNEXPECTED_TIMEOUT:
+	case SD_ERROR_4BIT_UNEXPECTED_TIMEOUT:
+		return -EILSEQ;
+	case 33:
+		return -EILSEQ;
+	case SD_ERROR_ILLEGAL_COMMAND:
+		return -EINVAL;
+	case SD_ERROR_NO_DEVICE:
+		return -ENOMEDIUM;
+	default:
+		return -ENODEV;
+	}
+}
+
+static void command_res_completed(struct urb *urb)
+{				/* urb completion handler - hardirq */
+	/*
+	 * cmd_mutex is held by vub300_cmndwork_thread
+	 */
+	struct vub300_mmc_host *vub300 = (struct vub300_mmc_host *)urb->context;
+	if (urb->status) {
+		/*
+		 * we have to let the initiator handle the error
+		 */
+	} else if (0 == vub300->command_res_urb->actual_length) {
+		/*
+		 * we have seen this happen once or twice and
+		 * we suspect a buggy USB host controller
+		 */
+	} else if (!vub300->data) {
+		/*
+		 * this means that the command (typically CMD52) suceeded
+		 */
+	} else if (0x02 != vub300->resp.common.HeaderType) {
+		/*
+		 * this is an error response from the VUB300 chip
+		 * and we let the initiator handle it
+		 */
+	} else if (vub300->urb) {
+		vub300->cmd->error =
+		    vub300_response_error(vub300->resp.error.ErrorCode);
+		usb_unlink_urb(vub300->urb);
+	} else {
+		vub300->cmd->error =
+		    vub300_response_error(vub300->resp.error.ErrorCode);
+		usb_sg_cancel(&vub300->sg_request);
+	}
+	complete(&vub300->command_complete);	/* got_response_in */
+}
+
+static void command_out_completed(struct urb *urb)
+{				/* urb completion handler - hardirq */
+	/*
+	 * cmd_mutex is held by vub300_cmndwork_thread
+	 */
+	struct vub300_mmc_host *vub300 = (struct vub300_mmc_host *)urb->context;
+	if (urb->status) {
+		complete(&vub300->command_complete);
+	} else {
+		int ret;
+		unsigned int pipe =
+		    usb_rcvbulkpipe(vub300->udev, vub300->cmnd_res_ep);
+		usb_fill_bulk_urb(vub300->command_res_urb, vub300->udev, pipe,
+				  &vub300->resp, sizeof(vub300->resp),
+				  command_res_completed, vub300);
+		vub300->command_res_urb->actual_length = 0;
+		ret = usb_submit_urb(vub300->command_res_urb, GFP_NOIO);
+		if (0 == ret) {
+			/*
+			 * the urb completion handler will call
+			 * our completion handler
+			 */
+		} else {
+			/*
+			 * and thus we only call it directly
+			 * when it will not be called
+			 */
+			complete(&vub300->command_complete);
+		}
+	}
+}
+
+/*
+ * the STUFF bits are masked out for the comparisons
+ */
+static void snoop_block_size_and_bus_width(struct vub300_mmc_host *vub300,
+					   u32 cmd_arg)
+{
+	if (0x80022200 == (0xFBFFFE00 & cmd_arg))
+		vub300->fbs[1] = (cmd_arg << 8) | (0x00FF & vub300->fbs[1]);
+	else if (0x80022000 == (0xFBFFFE00 & cmd_arg))
+		vub300->fbs[1] = (0xFF & cmd_arg) | (0xFF00 & vub300->fbs[1]);
+	else if (0x80042200 == (0xFBFFFE00 & cmd_arg))
+		vub300->fbs[2] = (cmd_arg << 8) | (0x00FF & vub300->fbs[2]);
+	else if (0x80042000 == (0xFBFFFE00 & cmd_arg))
+		vub300->fbs[2] = (0xFF & cmd_arg) | (0xFF00 & vub300->fbs[2]);
+	else if (0x80062200 == (0xFBFFFE00 & cmd_arg))
+		vub300->fbs[3] = (cmd_arg << 8) | (0x00FF & vub300->fbs[3]);
+	else if (0x80062000 == (0xFBFFFE00 & cmd_arg))
+		vub300->fbs[3] = (0xFF & cmd_arg) | (0xFF00 & vub300->fbs[3]);
+	else if (0x80082200 == (0xFBFFFE00 & cmd_arg))
+		vub300->fbs[4] = (cmd_arg << 8) | (0x00FF & vub300->fbs[4]);
+	else if (0x80082000 == (0xFBFFFE00 & cmd_arg))
+		vub300->fbs[4] = (0xFF & cmd_arg) | (0xFF00 & vub300->fbs[4]);
+	else if (0x800A2200 == (0xFBFFFE00 & cmd_arg))
+		vub300->fbs[5] = (cmd_arg << 8) | (0x00FF & vub300->fbs[5]);
+	else if (0x800A2000 == (0xFBFFFE00 & cmd_arg))
+		vub300->fbs[5] = (0xFF & cmd_arg) | (0xFF00 & vub300->fbs[5]);
+	else if (0x800C2200 == (0xFBFFFE00 & cmd_arg))
+		vub300->fbs[6] = (cmd_arg << 8) | (0x00FF & vub300->fbs[6]);
+	else if (0x800C2000 == (0xFBFFFE00 & cmd_arg))
+		vub300->fbs[6] = (0xFF & cmd_arg) | (0xFF00 & vub300->fbs[6]);
+	else if (0x800E2200 == (0xFBFFFE00 & cmd_arg))
+		vub300->fbs[7] = (cmd_arg << 8) | (0x00FF & vub300->fbs[7]);
+	else if (0x800E2000 == (0xFBFFFE00 & cmd_arg))
+		vub300->fbs[7] = (0xFF & cmd_arg) | (0xFF00 & vub300->fbs[7]);
+	else if (0x80000E00 == (0xFBFFFE03 & cmd_arg))
+		vub300->bus_width = 1;
+	else if (0x80000E02 == (0xFBFFFE03 & cmd_arg))
+		vub300->bus_width = 4;
+}
+
+static void send_command(struct vub300_mmc_host *vub300)
+{
+	/*
+	 * cmd_mutex is held by vub300_cmndwork_thread
+	 */
+	struct mmc_command *cmd = vub300->cmd;
+	struct mmc_data *data = vub300->data;
+	int retval;
+	u8 ResponseType;
+	if (vub300->app_spec) {
+		if (6 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+			if (0x00000000 == (0x00000003 & cmd->arg))
+				vub300->bus_width = 1;
+			else if (0x00000002 == (0x00000003 & cmd->arg))
+				vub300->bus_width = 4;
+			else
+				dev_err(&vub300->udev->dev,
+					"unexpected ACMD6 bus_width=%d",
+					0x00000003 & cmd->arg);
+		} else if (13 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (22 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (23 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (41 == cmd->opcode) {
+			ResponseType = SDRT_3;
+			vub300->resp_len = 6;
+		} else if (42 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (51 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (55 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else {
+			vub300->resp_len = 0;
+			cmd->error = -EINVAL;
+			complete(&vub300->command_complete);
+			return;
+		}
+		vub300->app_spec = 0;
+	} else {
+		if (0 == cmd->opcode) {
+			ResponseType = SDRT_NONE;
+			vub300->resp_len = 0;
+		} else if (1 == cmd->opcode) {
+			ResponseType = SDRT_3;
+			vub300->resp_len = 6;
+		} else if (2 == cmd->opcode) {
+			ResponseType = SDRT_2;
+			vub300->resp_len = 17;
+		} else if (3 == cmd->opcode) {
+			ResponseType = SDRT_6;
+			vub300->resp_len = 6;
+		} else if (4 == cmd->opcode) {
+			ResponseType = SDRT_NONE;
+			vub300->resp_len = 0;
+		} else if (5 == cmd->opcode) {
+			ResponseType = SDRT_4;
+			vub300->resp_len = 6;
+		} else if (6 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (7 == cmd->opcode) {
+			ResponseType = SDRT_1B;
+			vub300->resp_len = 6;
+		} else if (8 == cmd->opcode) {
+			ResponseType = SDRT_7;
+			vub300->resp_len = 6;
+		} else if (9 == cmd->opcode) {
+			ResponseType = SDRT_2;
+			vub300->resp_len = 17;
+		} else if (10 == cmd->opcode) {
+			ResponseType = SDRT_2;
+			vub300->resp_len = 17;
+		} else if (12 == cmd->opcode) {
+			ResponseType = SDRT_1B;
+			vub300->resp_len = 6;
+		} else if (13 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (15 == cmd->opcode) {
+			ResponseType = SDRT_NONE;
+			vub300->resp_len = 0;
+		} else if (16 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+			vub300->fbs[0] = 0xFFFF & cmd->arg;
+			vub300->fbs[1] = 0xFFFF & cmd->arg;
+			vub300->fbs[2] = 0xFFFF & cmd->arg;
+			vub300->fbs[3] = 0xFFFF & cmd->arg;
+			vub300->fbs[4] = 0xFFFF & cmd->arg;
+			vub300->fbs[5] = 0xFFFF & cmd->arg;
+			vub300->fbs[6] = 0xFFFF & cmd->arg;
+			vub300->fbs[7] = 0xFFFF & cmd->arg;
+		} else if (17 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (18 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (24 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (25 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (27 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (28 == cmd->opcode) {
+			ResponseType = SDRT_1B;
+			vub300->resp_len = 6;
+		} else if (29 == cmd->opcode) {
+			ResponseType = SDRT_1B;
+			vub300->resp_len = 6;
+		} else if (30 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (32 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (33 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (38 == cmd->opcode) {
+			ResponseType = SDRT_1B;
+			vub300->resp_len = 6;
+		} else if (42 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (52 == cmd->opcode) {
+			ResponseType = SDRT_5;
+			vub300->resp_len = 6;
+			snoop_block_size_and_bus_width(vub300, cmd->arg);
+		} else if (53 == cmd->opcode) {
+			ResponseType = SDRT_5;
+			vub300->resp_len = 6;
+		} else if (55 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+			vub300->app_spec = 1;
+		} else if (56 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else {
+			vub300->resp_len = 0;
+			cmd->error = -EINVAL;
+			complete(&vub300->command_complete);
+			return;
+		}
+	}
+	/*
+	 * it is a shame that we can not use "sizeof(struct SD_Command_Header)"
+	 * this is because the packet _must_ be padded to 64 bytes
+	 */
+	vub300->cmnd.head.HeaderSize = 20;
+	vub300->cmnd.head.HeaderType = 0x00;
+	vub300->cmnd.head.PortNumber = 0;	/* "0" means port 1 */
+	vub300->cmnd.head.Command_Type = 0x00;	/* standard read command */
+	vub300->cmnd.head.ResponseType = ResponseType;
+	vub300->cmnd.head.CommandIndex = cmd->opcode;
+	vub300->cmnd.head.Arguments[0] = cmd->arg >> 24;
+	vub300->cmnd.head.Arguments[1] = cmd->arg >> 16;
+	vub300->cmnd.head.Arguments[2] = cmd->arg >> 8;
+	vub300->cmnd.head.Arguments[3] = cmd->arg >> 0;
+	if (52 == cmd->opcode) {
+		int fn = 0x7 & (cmd->arg >> 28);
+		vub300->cmnd.head.BlockCount[0] = 0;
+		vub300->cmnd.head.BlockCount[1] = 0;
+		vub300->cmnd.head.BlockSize[0] = (vub300->fbs[fn] >> 8) & 0xFF;
+		vub300->cmnd.head.BlockSize[1] = (vub300->fbs[fn] >> 0) & 0xFF;
+		vub300->cmnd.head.Command_Type = 0x00;
+		vub300->cmnd.head.TransferSize[0] = 0;
+		vub300->cmnd.head.TransferSize[1] = 0;
+		vub300->cmnd.head.TransferSize[2] = 0;
+		vub300->cmnd.head.TransferSize[3] = 0;
+	} else if (!data) {
+		vub300->cmnd.head.BlockCount[0] = 0;
+		vub300->cmnd.head.BlockCount[1] = 0;
+		vub300->cmnd.head.BlockSize[0] = (vub300->fbs[0] >> 8) & 0xFF;
+		vub300->cmnd.head.BlockSize[1] = (vub300->fbs[0] >> 0) & 0xFF;
+		vub300->cmnd.head.Command_Type = 0x00;
+		vub300->cmnd.head.TransferSize[0] = 0;
+		vub300->cmnd.head.TransferSize[1] = 0;
+		vub300->cmnd.head.TransferSize[2] = 0;
+		vub300->cmnd.head.TransferSize[3] = 0;
+	} else if (53 == cmd->opcode) {
+		int fn = 0x7 & (cmd->arg >> 28);
+		if (0x08 & vub300->cmnd.head.Arguments[0]) {	/* BLOCK MODE */
+			vub300->cmnd.head.BlockCount[0] =
+			    (data->blocks >> 8) & 0xFF;
+			vub300->cmnd.head.BlockCount[1] =
+			    (data->blocks >> 0) & 0xFF;
+			vub300->cmnd.head.BlockSize[0] =
+			    (data->blksz >> 8) & 0xFF;
+			vub300->cmnd.head.BlockSize[1] =
+			    (data->blksz >> 0) & 0xFF;
+		} else {	/* BYTE MODE */
+			vub300->cmnd.head.BlockCount[0] = 0;
+			vub300->cmnd.head.BlockCount[1] = 0;
+			vub300->cmnd.head.BlockSize[0] =
+			    (vub300->datasize >> 8) & 0xFF;
+			vub300->cmnd.head.BlockSize[1] =
+			    (vub300->datasize >> 0) & 0xFF;
+		}
+		vub300->cmnd.head.Command_Type =
+		    (MMC_DATA_READ & data->flags) ? 0x00 : 0x80;
+		vub300->cmnd.head.TransferSize[0] =
+		    (vub300->datasize >> 24) & 0xFF;
+		vub300->cmnd.head.TransferSize[1] =
+		    (vub300->datasize >> 16) & 0xFF;
+		vub300->cmnd.head.TransferSize[2] =
+		    (vub300->datasize >> 8) & 0xFF;
+		vub300->cmnd.head.TransferSize[3] =
+		    (vub300->datasize >> 0) & 0xFF;
+		if (vub300->datasize < vub300->fbs[fn]) {
+			vub300->cmnd.head.BlockCount[0] = 0;
+			vub300->cmnd.head.BlockCount[1] = 0;
+		}
+	} else {
+		vub300->cmnd.head.BlockCount[0] = (data->blocks >> 8) & 0xFF;
+		vub300->cmnd.head.BlockCount[1] = (data->blocks >> 0) & 0xFF;
+		vub300->cmnd.head.BlockSize[0] = (data->blksz >> 8) & 0xFF;
+		vub300->cmnd.head.BlockSize[1] = (data->blksz >> 0) & 0xFF;
+		vub300->cmnd.head.Command_Type =
+		    (MMC_DATA_READ & data->flags) ? 0x00 : 0x80;
+		vub300->cmnd.head.TransferSize[0] =
+		    (vub300->datasize >> 24) & 0xFF;
+		vub300->cmnd.head.TransferSize[1] =
+		    (vub300->datasize >> 16) & 0xFF;
+		vub300->cmnd.head.TransferSize[2] =
+		    (vub300->datasize >> 8) & 0xFF;
+		vub300->cmnd.head.TransferSize[3] =
+		    (vub300->datasize >> 0) & 0xFF;
+		if (vub300->datasize < vub300->fbs[0]) {
+			vub300->cmnd.head.BlockCount[0] = 0;
+			vub300->cmnd.head.BlockCount[1] = 0;
+		}
+	}
+	if (vub300->cmnd.head.BlockSize[0] || vub300->cmnd.head.BlockSize[1]) {
+		u16 BlockSize = vub300->cmnd.head.BlockSize[1]
+		    | (vub300->cmnd.head.BlockSize[0] << 8);
+		u16 BlockBoundary =
+		    FIRMWARE_BLOCK_BOUNDARY -
+		    (FIRMWARE_BLOCK_BOUNDARY % BlockSize);
+		vub300->cmnd.head.BlockBoundary[0] =
+		    (BlockBoundary >> 8) & 0xFF;
+		vub300->cmnd.head.BlockBoundary[1] =
+		    (BlockBoundary >> 0) & 0xFF;
+	} else {
+		vub300->cmnd.head.BlockBoundary[0] = 0;
+		vub300->cmnd.head.BlockBoundary[1] = 0;
+	}
+	usb_fill_bulk_urb(vub300->command_out_urb, vub300->udev,
+			  usb_sndbulkpipe(vub300->udev, vub300->cmnd_out_ep),
+			  &vub300->cmnd, sizeof(vub300->cmnd),
+			  command_out_completed, vub300);
+	retval = usb_submit_urb(vub300->command_out_urb, GFP_ATOMIC);
+	if (0 > retval) {
+		cmd->error = retval;
+		complete(&vub300->command_complete);
+		return;
+	} else {
+		return;
+	}
+}
+
+/*
+ * timer callback runs in atomic mode
+ *       so it cannot call usb_kill_urb()
+ */
+static void vub300_sg_timed_out(unsigned long data)
+{
+	struct vub300_mmc_host *vub300 = (struct vub300_mmc_host *)data;
+	vub300->usb_timed_out = 1;
+	usb_sg_cancel(&vub300->sg_request);
+	usb_unlink_urb(vub300->command_out_urb);
+	usb_unlink_urb(vub300->command_res_urb);
+}
+
+static u16 roundup_to_multiple_of_64(u16 number)
+{
+	return 0xFFC0 & (0x3F + number);
+}
+
+/*
+ * this is a separate function to solve the 80 column width restriction
+ */
+static void __download_offload_pseudocode(struct vub300_mmc_host *vub300,
+					  const struct firmware *fw)
+{
+	u8 register_count = 0;
+	u16 transfer_size = 0;
+	u16 interrupt_size = 0;
+	const u8 *data = fw->data;
+	int size = fw->size;
+	u8 c;
+	dev_info(&vub300->udev->dev, "using %s for SDIO offload processing",
+		 vub300->vub_name);
+	do {
+		c = *data++;
+	} while (size-- && c);	/* skip comment */
+	dev_info(&vub300->udev->dev, "using offload firmware %s %s", fw->data,
+		 vub300->vub_name);
+	if (size < 4) {
+		dev_err(&vub300->udev->dev,
+			"corrupt offload pseudocode in firmware %s\n",
+			vub300->vub_name);
+		strncpy(vub300->vub_name, "corrupt offload pseudocode",
+			sizeof(vub300->vub_name));
+		return;
+	}
+	interrupt_size += *data++;
+	size -= 1;
+	interrupt_size <<= 8;
+	interrupt_size += *data++;
+	size -= 1;
+	if (interrupt_size < size) {
+		u16 xfer_length = roundup_to_multiple_of_64(interrupt_size);
+		u8 *xfer_buffer = kmalloc(xfer_length, GFP_KERNEL);
+		if (xfer_buffer) {
+			int retval;
+			memcpy(xfer_buffer, data, interrupt_size);
+			memset(xfer_buffer + interrupt_size, 0,
+			       xfer_length - interrupt_size);
+			size -= interrupt_size;
+			data += interrupt_size;
+			retval =
+			    usb_control_msg(vub300->udev,
+					    usb_sndctrlpipe(vub300->udev, 0),
+					    SET_INTERRUPT_PSEUDOCODE,
+					    USB_DIR_OUT | USB_TYPE_VENDOR |
+					    USB_RECIP_DEVICE, 0x0000, 0x0000,
+					    xfer_buffer, xfer_length, HZ);
+			kfree(xfer_buffer);
+			if (retval < 0) {
+				strncpy(vub300->vub_name,
+					"SDIO pseudocode download failed",
+					sizeof(vub300->vub_name));
+				return;
+			}
+		} else {
+			dev_err(&vub300->udev->dev,
+				"not enough memory for xfer buffer to send"
+				" INTERRUPT_PSEUDOCODE for %s %s\n", fw->data,
+				vub300->vub_name);
+			strncpy(vub300->vub_name,
+				"SDIO interrupt pseudocode download failed",
+				sizeof(vub300->vub_name));
+			return;
+		}
+	} else {
+		dev_err(&vub300->udev->dev,
+			"corrupt interrupt pseudocode in firmware %s %s\n",
+			fw->data, vub300->vub_name);
+		strncpy(vub300->vub_name, "corrupt interrupt pseudocode",
+			sizeof(vub300->vub_name));
+		return;
+	}
+	transfer_size += *data++;
+	size -= 1;
+	transfer_size <<= 8;
+	transfer_size += *data++;
+	size -= 1;
+	if (transfer_size < size) {
+		u16 xfer_length = roundup_to_multiple_of_64(transfer_size);
+		u8 *xfer_buffer = kmalloc(xfer_length, GFP_KERNEL);
+		if (xfer_buffer) {
+			int retval;
+			memcpy(xfer_buffer, data, transfer_size);
+			memset(xfer_buffer + transfer_size, 0,
+			       xfer_length - transfer_size);
+			size -= transfer_size;
+			data += transfer_size;
+			retval =
+			    usb_control_msg(vub300->udev,
+					    usb_sndctrlpipe(vub300->udev, 0),
+					    SET_TRANSFER_PSEUDOCODE,
+					    USB_DIR_OUT | USB_TYPE_VENDOR |
+					    USB_RECIP_DEVICE, 0x0000, 0x0000,
+					    xfer_buffer, xfer_length, HZ);
+			kfree(xfer_buffer);
+			if (retval < 0) {
+				strncpy(vub300->vub_name,
+					"SDIO pseudocode download failed",
+					sizeof(vub300->vub_name));
+				return;
+			}
+		} else {
+			dev_err(&vub300->udev->dev,
+				"not enough memory for xfer buffer to send"
+				" TRANSFER_PSEUDOCODE for %s %s\n", fw->data,
+				vub300->vub_name);
+			strncpy(vub300->vub_name,
+				"SDIO transfer pseudocode download failed",
+				sizeof(vub300->vub_name));
+			return;
+		}
+	} else {
+		dev_err(&vub300->udev->dev,
+			"corrupt transfer pseudocode in firmware %s %s\n",
+			fw->data, vub300->vub_name);
+		strncpy(vub300->vub_name, "corrupt transfer pseudocode",
+			sizeof(vub300->vub_name));
+		return;
+	}
+	register_count += *data++;
+	size -= 1;
+	if (register_count * 4 == size) {
+		int I = vub300->dynamic_register_count = register_count;
+		int i = 0;
+		while (I--) {
+			unsigned int func_num = 0;
+			vub300->sdio_register[i].func_num = *data++;
+			size -= 1;
+			func_num += *data++;
+			size -= 1;
+			func_num <<= 8;
+			func_num += *data++;
+			size -= 1;
+			func_num <<= 8;
+			func_num += *data++;
+			size -= 1;
+			vub300->sdio_register[i].sdio_reg = func_num;
+			vub300->sdio_register[i].activate = 1;
+			vub300->sdio_register[i].prepared = 0;
+			i += 1;
+		}
+		dev_info(&vub300->udev->dev,
+			 "initialized %d dynamic pseudocode registers",
+			 vub300->dynamic_register_count);
+		return;
+	} else {
+		dev_err(&vub300->udev->dev,
+			"corrupt dynamic registers in firmware %s\n",
+			vub300->vub_name);
+		strncpy(vub300->vub_name, "corrupt dynamic registers",
+			sizeof(vub300->vub_name));
+		return;
+	}
+}
+
+/*
+ * if the binary containing the EMPTY PseudoCode can not be found
+ * vub300->vub_name is set anyway in order to prevent an automatic retry
+ */
+static void download_offload_pseudocode(struct vub300_mmc_host *vub300)
+{
+	struct mmc_card *card = vub300->mmc->card;
+	int sdio_funcs = card->sdio_funcs;
+	const struct firmware *fw = NULL;
+	int l = snprintf(vub300->vub_name, sizeof(vub300->vub_name),
+			 "vub_%04X%04X", card->cis.vendor, card->cis.device);
+	int N = sdio_funcs;
+	int n = 0;
+	int retval;
+	while (N--) {
+		struct sdio_func *sf = card->sdio_func[n++];
+		l += snprintf(vub300->vub_name + l,
+			      sizeof(vub300->vub_name) - l, "_%04X%04X",
+			      sf->vendor, sf->device);
+	};
+	snprintf(vub300->vub_name + l, sizeof(vub300->vub_name) - l, ".bin");
+	dev_info(&vub300->udev->dev, "requesting offload firmware %s",
+		 vub300->vub_name);
+	retval = request_firmware(&fw, vub300->vub_name, &card->dev);
+	if (0 > retval) {
+		strncpy(vub300->vub_name, "vub_default.bin",
+			sizeof(vub300->vub_name));
+		retval = request_firmware(&fw, vub300->vub_name, &card->dev);
+		if (0 > retval) {
+			strncpy(vub300->vub_name,
+				"no SDIO offload firmware found",
+				sizeof(vub300->vub_name));
+		} else {
+			__download_offload_pseudocode(vub300, fw);
+			release_firmware(fw);
+		}
+	} else {
+		__download_offload_pseudocode(vub300, fw);
+		release_firmware(fw);
+	}
+}
+
+static void vub300_usb_bulk_msg_completion(struct urb *urb)
+{				/* urb completion handler - hardirq */
+	complete((struct completion *)urb->context);
+}
+
+static int vub300_usb_bulk_msg(struct vub300_mmc_host *vub300,
+			       unsigned int pipe, void *data, int len,
+			       int *actual_length, int timeout_msecs)
+{
+	/*
+	 * cmd_mutex is held by vub300_cmndwork_thread
+	 */
+	struct usb_device *usb_dev = vub300->udev;
+	struct completion done;
+	int retval;
+	vub300->urb = usb_alloc_urb(0, GFP_KERNEL);
+	if (!vub300->urb)
+		return -ENOMEM;
+	usb_fill_bulk_urb(vub300->urb, usb_dev, pipe, data, len,
+			  vub300_usb_bulk_msg_completion, NULL);
+	init_completion(&done);
+	vub300->urb->context = &done;
+	vub300->urb->actual_length = 0;
+	retval = usb_submit_urb(vub300->urb, GFP_NOIO);
+	if (unlikely(retval))
+		goto out;
+	if (!wait_for_completion_timeout
+	    (&done, msecs_to_jiffies(timeout_msecs))) {
+		retval = -ETIMEDOUT;
+		usb_kill_urb(vub300->urb);
+	} else {
+		retval = vub300->urb->status;
+	}
+out:
+	*actual_length = vub300->urb->actual_length;
+	usb_free_urb(vub300->urb);
+	vub300->urb = NULL;
+	return retval;
+}
+
+static int __command_read_data(struct vub300_mmc_host *vub300,
+			       struct mmc_command *cmd, struct mmc_data *data)
+{
+	/*
+	 * cmd_mutex is held by vub300_cmndwork_thread
+	 */
+	int linear_length = vub300->datasize;
+	int padded_length = vub300->large_usb_packets ?
+	    ((511 + linear_length) >> 9) << 9 :
+	    ((63 + linear_length) >> 6) << 6;
+	if ((padded_length == linear_length) || !pad_input_to_usb_pkt) {
+		int result;
+		unsigned pipe;
+		pipe = usb_rcvbulkpipe(vub300->udev, vub300->data_inp_ep);
+		result = usb_sg_init(&vub300->sg_request, vub300->udev,
+				     pipe, 0, data->sg,
+				     data->sg_len, 0, GFP_NOIO);
+		if (0 > result) {
+			usb_unlink_urb(vub300->command_out_urb);
+			usb_unlink_urb(vub300->command_res_urb);
+			cmd->error = result;
+			data->bytes_xfered = 0;
+			return 0;
+		} else {
+			vub300->sg_transfer_timer.expires =
+			    jiffies + msecs_to_jiffies(2000 +
+						       (linear_length / 16384));
+			add_timer(&vub300->sg_transfer_timer);
+			usb_sg_wait(&vub300->sg_request);
+			del_timer(&vub300->sg_transfer_timer);
+			if (0 > vub300->sg_request.status) {
+				cmd->error = vub300->sg_request.status;
+				data->bytes_xfered = 0;
+				return 0;
+			} else {
+				data->bytes_xfered = vub300->datasize;
+				return linear_length;
+			}
+		}
+	} else {
+		u8 *buf = kmalloc(padded_length, GFP_KERNEL);
+		if (buf) {
+			int result;
+			unsigned pipe =
+			    usb_rcvbulkpipe(vub300->udev, vub300->data_inp_ep);
+			int actual_length = 0;
+			result =
+			    vub300_usb_bulk_msg(vub300, pipe, buf,
+						padded_length, &actual_length,
+						2000 + (padded_length / 16384));
+			if (0 > result) {
+				cmd->error = result;
+				data->bytes_xfered = 0;
+				kfree(buf);
+				return 0;
+			} else if (actual_length < linear_length) {
+				cmd->error = -EREMOTEIO;
+				data->bytes_xfered = 0;
+				kfree(buf);
+				return 0;
+			} else {
+				sg_copy_from_buffer(data->sg, data->sg_len, buf,
+						    linear_length);
+				kfree(buf);
+				data->bytes_xfered = vub300->datasize;
+				return linear_length;
+			}
+		} else {
+			cmd->error = -ENOMEM;
+			data->bytes_xfered = 0;
+			return 0;
+		}
+	}
+}
+
+static int __command_write_data(struct vub300_mmc_host *vub300,
+				struct mmc_command *cmd, struct mmc_data *data)
+{
+	/*
+	 * cmd_mutex is held by vub300_cmndwork_thread
+	 */
+	unsigned pipe = usb_sndbulkpipe(vub300->udev, vub300->data_out_ep);
+	int linear_length = vub300->datasize;
+	int modulo_64_length = linear_length & 0x003F;
+	int modulo_512_length = linear_length & 0x01FF;
+	if (64 > linear_length) {
+		int result;
+		int actual_length;
+		sg_copy_to_buffer(data->sg, data->sg_len,
+				  vub300->padded_buffer,
+				  sizeof(vub300->padded_buffer));
+		memset(vub300->padded_buffer + linear_length, 0,
+		       sizeof(vub300->padded_buffer) - linear_length);
+		result =
+		    vub300_usb_bulk_msg(vub300, pipe, vub300->padded_buffer,
+					sizeof(vub300->padded_buffer),
+					&actual_length,
+					2000 +
+					(sizeof(vub300->padded_buffer) /
+					 16384));
+		if (0 > result) {
+			cmd->error = result;
+			data->bytes_xfered = 0;
+		} else {
+			data->bytes_xfered = vub300->datasize;
+		}
+	} else if ((!vub300->large_usb_packets && (0 < modulo_64_length))
+		   || (vub300->large_usb_packets && (64 > modulo_512_length))
+	    ) {			/* don't you just love these work-rounds */
+		int padded_length = ((63 + linear_length) >> 6) << 6;
+		u8 *buf = kmalloc(padded_length, GFP_KERNEL);
+		if (buf) {
+			int result;
+			int actual_length;
+			sg_copy_to_buffer(data->sg, data->sg_len, buf,
+					  padded_length);
+			memset(buf + linear_length, 0,
+			       padded_length - linear_length);
+			result =
+			    vub300_usb_bulk_msg(vub300, pipe, buf,
+						padded_length, &actual_length,
+						2000 + padded_length / 16384);
+			kfree(buf);
+			if (0 > result) {
+				cmd->error = result;
+				data->bytes_xfered = 0;
+			} else {
+				data->bytes_xfered = vub300->datasize;
+			}
+		} else {
+			cmd->error = -ENOMEM;
+			data->bytes_xfered = 0;
+		}
+	} else {		/* no data padding required */
+		int result;
+		unsigned char buf[64 * 4];
+		sg_copy_to_buffer(data->sg, data->sg_len, buf, sizeof(buf));
+		result = usb_sg_init(&vub300->sg_request, vub300->udev,
+				     pipe, 0, data->sg,
+				     data->sg_len, 0, GFP_NOIO);
+		if (0 > result) {
+			usb_unlink_urb(vub300->command_out_urb);
+			usb_unlink_urb(vub300->command_res_urb);
+			cmd->error = result;
+			data->bytes_xfered = 0;
+		} else {
+			vub300->sg_transfer_timer.expires =
+			    jiffies + msecs_to_jiffies(2000 +
+						       linear_length / 16384);
+			add_timer(&vub300->sg_transfer_timer);
+			usb_sg_wait(&vub300->sg_request);
+			if (cmd->error) {
+				data->bytes_xfered = 0;
+			} else {
+				del_timer(&vub300->sg_transfer_timer);
+				if (0 > vub300->sg_request.status) {
+					cmd->error = vub300->sg_request.status;
+					data->bytes_xfered = 0;
+				} else {
+					data->bytes_xfered = vub300->datasize;
+				}
+			}
+		}
+	}
+	return linear_length;
+}
+
+static void __vub300_command_response(struct vub300_mmc_host *vub300,
+				      struct mmc_command *cmd,
+				      struct mmc_data *data, int data_length)
+{
+	/*
+	 * cmd_mutex is held by vub300_cmndwork_thread
+	 */
+	long respretval;
+	int msec_timeout = 1000 + data_length / 4;
+	respretval =
+	    wait_for_completion_timeout(&vub300->command_complete,
+					msecs_to_jiffies(msec_timeout));
+	if (0 == respretval) {	/* TIMED OUT */
+		/*
+		 * we don't know which of "out" and "res" if any failed
+		 */
+		int result;
+		vub300->usb_timed_out = 1;
+		usb_kill_urb(vub300->command_out_urb);
+		usb_kill_urb(vub300->command_res_urb);
+		cmd->error = -ETIMEDOUT;
+		result =
+		    usb_lock_device_for_reset(vub300->udev, vub300->interface);
+		if (result == 0) {
+			result = usb_reset_device(vub300->udev);
+			usb_unlock_device(vub300->udev);
+		}
+	} else if (0 > respretval) {
+		/*
+		 * we don't know which of "out" and "res" if any failed
+		 */
+		usb_kill_urb(vub300->command_out_urb);
+		usb_kill_urb(vub300->command_res_urb);
+		cmd->error = respretval;
+	} else if (cmd->error) {
+		/*
+		 * the error occured sending the command
+		 * or recieving the response
+		 */
+	} else if (vub300->command_out_urb->status) {
+		vub300->usb_transport_fail = vub300->command_out_urb->status;
+		cmd->error = -EPROTO == vub300->command_out_urb->status ?
+		    -ESHUTDOWN : vub300->command_out_urb->status;
+	} else if (vub300->command_res_urb->status) {
+		vub300->usb_transport_fail = vub300->command_res_urb->status;
+		cmd->error = -EPROTO == vub300->command_res_urb->status ?
+		    -ESHUTDOWN : vub300->command_res_urb->status;
+	} else if (0x00 == vub300->resp.common.HeaderType) {
+		/*
+		 * the command completed successfully
+		 * and there was no piggybacked data
+		 */
+	} else if (RESPONSE_ERROR == vub300->resp.common.HeaderType) {
+		cmd->error =
+		    vub300_response_error(vub300->resp.error.ErrorCode);
+		if (vub300->data)
+			usb_sg_cancel(&vub300->sg_request);
+	} else if (RESPONSE_PIGGYBACKED == vub300->resp.common.HeaderType) {
+		int offloaded_data_length =
+		    vub300->resp.common.HeaderSize -
+		    sizeof(struct SD_Register_Header);
+		int register_count = offloaded_data_length >> 3;
+		int ri = 0;
+		while (register_count--) {
+			add_offloaded_reg(vub300, &vub300->resp.pig.reg[ri]);
+			ri += 1;
+		}
+		vub300->resp.common.HeaderSize =
+		    sizeof(struct SD_Register_Header);
+		vub300->resp.common.HeaderType = 0x00;
+		cmd->error = 0;
+	} else if (RESPONSE_PIG_DISABLED == vub300->resp.common.HeaderType) {
+		int offloaded_data_length =
+		    vub300->resp.common.HeaderSize -
+		    sizeof(struct SD_Register_Header);
+		int register_count = offloaded_data_length >> 3;
+		int ri = 0;
+		while (register_count--) {
+			add_offloaded_reg(vub300, &vub300->resp.pig.reg[ri]);
+			ri += 1;
+		}
+		mutex_lock(&vub300->irq_mutex);
+		if (vub300->irqs_queued) {
+			vub300->irqs_queued += 1;
+		} else if (vub300->irq_enabled) {
+			vub300->irqs_queued += 1;
+			vub300_queue_poll_work(vub300, 0);
+		} else {
+			vub300->irqs_queued += 1;
+		}
+		vub300->irq_disabled = 1;
+		mutex_unlock(&vub300->irq_mutex);
+		vub300->resp.common.HeaderSize =
+		    sizeof(struct SD_Register_Header);
+		vub300->resp.common.HeaderType = 0x00;
+		cmd->error = 0;
+	} else if (RESPONSE_PIG_ENABLED == vub300->resp.common.HeaderType) {
+		int offloaded_data_length =
+		    vub300->resp.common.HeaderSize -
+		    sizeof(struct SD_Register_Header);
+		int register_count = offloaded_data_length >> 3;
+		int ri = 0;
+		while (register_count--) {
+			add_offloaded_reg(vub300, &vub300->resp.pig.reg[ri]);
+			ri += 1;
+		}
+		mutex_lock(&vub300->irq_mutex);
+		if (vub300->irqs_queued) {
+			vub300->irqs_queued += 1;
+		} else if (vub300->irq_enabled) {
+			vub300->irqs_queued += 1;
+			vub300_queue_poll_work(vub300, 0);
+		} else {
+			vub300->irqs_queued += 1;
+		}
+		vub300->irq_disabled = 0;
+		mutex_unlock(&vub300->irq_mutex);
+		vub300->resp.common.HeaderSize =
+		    sizeof(struct SD_Register_Header);
+		vub300->resp.common.HeaderType = 0x00;
+		cmd->error = 0;
+	} else {
+		cmd->error = -EINVAL;
+	}
+}
+
+static void construct_request_response(struct vub300_mmc_host *vub300,
+				       struct mmc_command *cmd)
+{
+	int resp_len = vub300->resp_len;
+	int less_cmd = (17 == resp_len) ? resp_len : resp_len - 1;
+	int bytes = 3 & less_cmd;
+	int words = less_cmd >> 2;
+	u8 *r = vub300->resp.response.CommandResponse;
+	if (3 == bytes) {
+		cmd->resp[words] = (r[1 + (words << 2)] << 24)
+		    | (r[2 + (words << 2)] << 16)
+		    | (r[3 + (words << 2)] << 8);
+	} else if (2 == bytes) {
+		cmd->resp[words] = (r[1 + (words << 2)] << 24)
+		    | (r[2 + (words << 2)] << 16);
+	} else if (1 == bytes) {
+		cmd->resp[words] = (r[1 + (words << 2)] << 24);
+	}
+	while (words-- > 0) {
+		cmd->resp[words] = (r[1 + (words << 2)] << 24)
+		    | (r[2 + (words << 2)] << 16)
+		    | (r[3 + (words << 2)] << 8)
+		    | (r[4 + (words << 2)] << 0);
+	}
+	if ((53 == cmd->opcode) && (0x000000FF & cmd->resp[0]))
+		cmd->resp[0] &= 0xFFFFFF00;
+}
+
+/*
+ * this thread runs only when there
+ * is an upper level command req outstanding
+ */
+static void vub300_cmndwork_thread(struct work_struct *work)
+{
+	struct vub300_mmc_host *vub300 =
+	    container_of(work, struct vub300_mmc_host, cmndwork);
+	if (!vub300->interface) {
+		kref_put(&vub300->kref, vub300_delete);
+		return;
+	} else {
+		struct mmc_request *req = vub300->req;
+		struct mmc_command *cmd = vub300->cmd;
+		struct mmc_data *data = vub300->data;
+		int data_length;
+		mutex_lock(&vub300->cmd_mutex);
+		init_completion(&vub300->command_complete);
+		if (likely(vub300->vub_name[0])
+		    || !vub300->mmc->card
+		    || !mmc_card_present(vub300->mmc->card)) {
+			/*
+			 * the name of the EMPTY Pseudo firmware file
+			 * is used as a flag to indicate that the file
+			 * has been already downloaded to the VUB300 chip
+			 */
+		} else if (0 == vub300->mmc->card->sdio_funcs) {
+			strncpy(vub300->vub_name, "SD memory device",
+				sizeof(vub300->vub_name));
+		} else {
+			download_offload_pseudocode(vub300);
+		}
+		send_command(vub300);
+		if (!data)
+			data_length = 0;
+		else if (MMC_DATA_READ & data->flags)
+			data_length = __command_read_data(vub300, cmd, data);
+		else
+			data_length = __command_write_data(vub300, cmd, data);
+		__vub300_command_response(vub300, cmd, data, data_length);
+		vub300->req = NULL;
+		vub300->cmd = NULL;
+		vub300->data = NULL;
+		if (cmd->error) {
+			if (-ENOMEDIUM == cmd->error)
+				check_vub300_port_status(vub300);
+			mutex_unlock(&vub300->cmd_mutex);
+			mmc_request_done(vub300->mmc, req);
+			kref_put(&vub300->kref, vub300_delete);
+			return;
+		} else {
+			construct_request_response(vub300, cmd);
+			vub300->resp_len = 0;
+			mutex_unlock(&vub300->cmd_mutex);
+			kref_put(&vub300->kref, vub300_delete);
+			mmc_request_done(vub300->mmc, req);
+			return;
+		}
+	}
+}
+
+static int examine_cyclic_buffer(struct vub300_mmc_host *vub300,
+				 struct mmc_command *cmd, u8 Function)
+{
+	/*
+	 * cmd_mutex is held by vub300_mmc_request
+	 */
+	u8 cmd0 = 0xFF & (cmd->arg >> 24);
+	u8 cmd1 = 0xFF & (cmd->arg >> 16);
+	u8 cmd2 = 0xFF & (cmd->arg >> 8);
+	u8 cmd3 = 0xFF & (cmd->arg >> 0);
+	int first = MAXREGMASK & vub300->fn[Function].offload_point;
+	struct Offload_Registers_Access *rf = &vub300->fn[Function].reg[first];
+	if (cmd0 == rf->Command_Byte[0]
+	    && cmd1 == rf->Command_Byte[1]
+	    && cmd2 == rf->Command_Byte[2]
+	    && cmd3 == rf->Command_Byte[3]
+	    ) {
+		u8 checksum = 0x00;
+		cmd->resp[1] = checksum << 24;
+		cmd->resp[0] = (rf->Respond_Byte[0] << 24)
+		    | (rf->Respond_Byte[1] << 16)
+		    | (rf->Respond_Byte[2] << 8)
+		    | (rf->Respond_Byte[3] << 0);
+		vub300->fn[Function].offload_point += 1;
+		vub300->fn[Function].offload_count -= 1;
+		vub300->total_offload_count -= 1;
+		return 1;
+	} else {
+		int delta = 1;	/* because it does not match the first one */
+		u8 register_count = vub300->fn[Function].offload_count - 1;
+		u32 register_point = vub300->fn[Function].offload_point + 1;
+		while (0 < register_count) {
+			int point = MAXREGMASK & register_point;
+			struct Offload_Registers_Access *r =
+			    &vub300->fn[Function].reg[point];
+			if (cmd0 == r->Command_Byte[0]
+			    && cmd1 == r->Command_Byte[1]
+			    && cmd2 == r->Command_Byte[2]
+			    && cmd3 == r->Command_Byte[3]
+			    ) {
+				u8 checksum = 0x00;
+				cmd->resp[1] = checksum << 24;
+				cmd->resp[0] = (r->Respond_Byte[0] << 24)
+				    | (r->Respond_Byte[1] << 16)
+				    | (r->Respond_Byte[2] << 8)
+				    | (r->Respond_Byte[3] << 0);
+				vub300->fn[Function].offload_point += delta;
+				vub300->fn[Function].offload_count -= delta;
+				vub300->total_offload_count -= delta;
+				return 1;
+			} else {
+				register_point += 1;
+				register_count -= 1;
+				delta += 1;
+				continue;
+			}
+		}
+		return 0;
+	}
+}
+
+static int satisfy_request_from_offloaded_data(struct vub300_mmc_host *vub300,
+					       struct mmc_command *cmd)
+{
+	/*
+	 * cmd_mutex is held by vub300_mmc_request
+	 */
+	u8 regs = vub300->dynamic_register_count;
+	u8 i = 0;
+	u8 Function = FUN(cmd);
+	u32 Register = REG(cmd);
+	while (0 < regs--) {
+		if ((vub300->sdio_register[i].func_num == Function)
+		    && (vub300->sdio_register[i].sdio_reg == Register)
+		    ) {
+			if (!vub300->sdio_register[i].prepared) {
+				return 0;
+			} else if (0x80000000 == (0x80000000 & cmd->arg)) {
+				/*
+				 * a write to a dynamic register
+				 * nullifies our offloaded value
+				 */
+				vub300->sdio_register[i].prepared = 0;
+				return 0;
+			} else {
+				u8 checksum = 0x00;
+				u8 rsp0 = 0x00;
+				u8 rsp1 = 0x00;
+				u8 rsp2 = vub300->sdio_register[i].response;
+				u8 rsp3 = vub300->sdio_register[i].regvalue;
+				vub300->sdio_register[i].prepared = 0;
+				cmd->resp[1] = checksum << 24;
+				cmd->resp[0] = (rsp0 << 24)
+				    | (rsp1 << 16)
+				    | (rsp2 << 8)
+				    | (rsp3 << 0);
+				return 1;
+			}
+		} else {
+			i += 1;
+			continue;
+		}
+	};
+	if (0 == vub300->total_offload_count)
+		return 0;
+	else if (0 == vub300->fn[Function].offload_count)
+		return 0;
+	else
+		return examine_cyclic_buffer(vub300, cmd, Function);
+}
+
+static void vub300_mmc_request(struct mmc_host *mmc, struct mmc_request *req)
+{				/* NOT irq */
+	struct mmc_command *cmd = req->cmd;
+	struct vub300_mmc_host *vub300 = mmc_priv(mmc);
+	if (!vub300->interface) {
+		cmd->error = -ESHUTDOWN;
+		mmc_request_done(mmc, req);
+		return;
+	} else {
+		struct mmc_data *data = req->data;
+		if (!vub300->card_powered) {
+			cmd->error = -ENOMEDIUM;
+			mmc_request_done(mmc, req);
+			return;
+		}
+		if (!vub300->card_present) {
+			cmd->error = -ENOMEDIUM;
+			mmc_request_done(mmc, req);
+			return;
+		}
+		if (vub300->usb_transport_fail) {
+			cmd->error = vub300->usb_transport_fail;
+			mmc_request_done(mmc, req);
+			return;
+		}
+		if (!vub300->interface) {
+			cmd->error = -ENODEV;
+			mmc_request_done(mmc, req);
+			return;
+		}
+		kref_get(&vub300->kref);
+		mutex_lock(&vub300->cmd_mutex);
+		mod_timer(&vub300->inactivity_timer, jiffies + HZ);
+		/*
+		 * for performance we have to return immediately
+		 * if the requested data has been offloaded
+		 */
+		if ((52 == cmd->opcode)
+		    && satisfy_request_from_offloaded_data(vub300, cmd)) {
+			cmd->error = 0;
+			mutex_unlock(&vub300->cmd_mutex);
+			kref_put(&vub300->kref, vub300_delete);
+			mmc_request_done(mmc, req);
+			return;
+		} else {
+			vub300->cmd = cmd;
+			vub300->req = req;
+			vub300->data = data;
+			if (data)
+				vub300->datasize = data->blksz * data->blocks;
+			else
+				vub300->datasize = 0;
+			vub300_queue_cmnd_work(vub300);
+			mutex_unlock(&vub300->cmd_mutex);
+			kref_put(&vub300->kref, vub300_delete);
+			/*
+			 * the kernel lock diagnostics complain
+			 * if the cmd_mutex * is "passed on"
+			 * to the cmndwork thread,
+			 * so we must release it now
+			 * and re-acquire it in the cmndwork thread
+			 */
+		}
+	}
+}
+
+static void __set_clock_speed(struct vub300_mmc_host *vub300, u8 * buf,
+			      struct mmc_ios *ios)
+{
+	int retval;
+	u32 kHzClock;
+	if (ios->clock >= 48000000)
+		kHzClock = 48000;
+	else if (ios->clock >= 24000000)
+		kHzClock = 24000;
+	else if (ios->clock >= 20000000)
+		kHzClock = 20000;
+	else if (ios->clock >= 15000000)
+		kHzClock = 15000;
+	else if (ios->clock >= 200000)
+		kHzClock = 200;
+	else
+		kHzClock = 0;
+	buf[0] = 0xFF & (kHzClock >> 0);
+	buf[1] = 0xFF & (kHzClock >> 8);
+	buf[2] = 0xFF & (kHzClock >> 16);
+	buf[3] = 0xFF & (kHzClock >> 24);
+	buf[4] = 0;
+	buf[5] = 0;
+	buf[6] = 0;
+	buf[7] = 0;
+	retval =
+	    usb_control_msg(vub300->udev, usb_sndctrlpipe(vub300->udev, 0),
+			    SET_CLOCK_SPEED,
+			    USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			    0x00, 0x00, buf, 8, HZ);
+	if (8 != retval) {
+		dev_err(&vub300->udev->dev, "SET_CLOCK_SPEED"
+			" %dkHz failed with retval=%d", kHzClock, retval);
+	}
+}
+
+static void vub300_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{				/* NOT irq */
+	struct vub300_mmc_host *vub300 = mmc_priv(mmc);
+	if (!vub300->interface)
+		return;
+	kref_get(&vub300->kref);
+	mutex_lock(&vub300->cmd_mutex);
+	if ((MMC_POWER_OFF == ios->power_mode) && vub300->card_powered) {
+		int retval;
+		vub300->card_powered = 0;
+		retval =
+		    usb_control_msg(vub300->udev,
+				    usb_sndctrlpipe(vub300->udev, 0),
+				    SET_SD_POWER,
+				    USB_DIR_OUT | USB_TYPE_VENDOR |
+				    USB_RECIP_DEVICE, 0x0000, 0x0000, NULL, 0,
+				    HZ);
+		/*
+		 * must wait for the VUB300 u-proc to boot up
+		 */
+		msleep(600);
+	} else if ((MMC_POWER_UP == ios->power_mode) && !vub300->card_powered) {
+		int retval;
+		retval =
+		    usb_control_msg(vub300->udev,
+				    usb_sndctrlpipe(vub300->udev, 0),
+				    SET_SD_POWER,
+				    USB_DIR_OUT | USB_TYPE_VENDOR |
+				    USB_RECIP_DEVICE, 0x0001, 0x0000, NULL, 0,
+				    HZ);
+		msleep(600);
+		vub300->card_powered = 1;
+	} else if (MMC_POWER_ON == ios->power_mode) {
+		u8 *buf = kmalloc(8, GFP_KERNEL);
+		if (buf) {
+			__set_clock_speed(vub300, buf, ios);
+			kfree(buf);
+		}
+	} else {
+		/*
+		 * this should mean no change of state
+		 */
+	}
+	mutex_unlock(&vub300->cmd_mutex);
+	kref_put(&vub300->kref, vub300_delete);
+}
+
+static int vub300_mmc_get_ro(struct mmc_host *mmc)
+{
+	struct vub300_mmc_host *vub300 = mmc_priv(mmc);
+	return vub300->read_only;
+}
+
+static void vub300_enable_sdio_irq(struct mmc_host *mmc, int enable)
+{				/* NOT irq */
+	struct vub300_mmc_host *vub300 = mmc_priv(mmc);
+	if (!vub300->interface)
+		return;
+	kref_get(&vub300->kref);
+	if (enable) {
+		mutex_lock(&vub300->irq_mutex);
+		if (vub300->irqs_queued) {
+			vub300->irqs_queued -= 1;
+			mmc_signal_sdio_irq(vub300->mmc);
+		} else if (vub300->irq_disabled) {
+			vub300->irq_disabled = 0;
+			vub300->irq_enabled = 1;
+			vub300_queue_poll_work(vub300, 0);
+		} else if (vub300->irq_enabled) {
+			/*
+			 * this should not happen
+			 * so we will just ignore it
+			 */
+		} else {
+			vub300->irq_enabled = 1;
+			vub300_queue_poll_work(vub300, 0);
+		}
+		mutex_unlock(&vub300->irq_mutex);
+	} else {
+		vub300->irq_enabled = 0;
+	}
+	kref_put(&vub300->kref, vub300_delete);
+}
+
+void vub300_init_card(struct mmc_host *mmc, struct mmc_card *card)
+{				/* NOT irq */
+	struct vub300_mmc_host *vub300 = mmc_priv(mmc);
+	dev_info(&vub300->udev->dev, "NO host QUIRKS for this card");
+}
+
+static struct mmc_host_ops vub300_mmc_ops = {
+	.request = vub300_mmc_request,
+	.set_ios = vub300_mmc_set_ios,
+	.get_ro = vub300_mmc_get_ro,
+	.enable_sdio_irq = vub300_enable_sdio_irq,
+	.init_card = vub300_init_card,
+};
+
+static int vub300_probe(struct usb_interface *interface,
+			const struct usb_device_id *id)
+{				/* NOT irq */
+	struct vub300_mmc_host *vub300 = NULL;
+	struct usb_host_interface *iface_desc;
+	struct usb_device *udev = usb_get_dev(interface_to_usbdev(interface));
+	int i;
+	int retval = -ENOMEM;
+	struct urb *command_out_urb;
+	struct urb *command_res_urb;
+	struct mmc_host *mmc;
+	char Manufacturer[48];
+	char Product[32];
+	char SerialNumber[32];
+	usb_string(udev, udev->descriptor.iManufacturer, Manufacturer,
+		   sizeof(Manufacturer));
+	usb_string(udev, udev->descriptor.iProduct, Product, sizeof(Product));
+	usb_string(udev, udev->descriptor.iSerialNumber, SerialNumber,
+		   sizeof(SerialNumber));
+	dev_info(&udev->dev, "probing VID:PID(%04X:%04X) %s %s %s",
+		 udev->descriptor.idVendor, udev->descriptor.idProduct,
+		 Manufacturer, Product, SerialNumber);
+	command_out_urb = usb_alloc_urb(0, GFP_KERNEL);
+	if (!command_out_urb) {
+		retval = -ENOMEM;
+		dev_err(&vub300->udev->dev,
+			"not enough memory for the command_out_urb");
+		goto error0;
+	}
+	command_res_urb = usb_alloc_urb(0, GFP_KERNEL);
+	if (!command_res_urb) {
+		retval = -ENOMEM;
+		dev_err(&vub300->udev->dev,
+			"not enough memory for the command_res_urb");
+		goto error1;
+	}
+	/* this also allocates memory for our VUB300 mmc host device */
+	mmc = mmc_alloc_host(sizeof(struct vub300_mmc_host), &udev->dev);
+	if (!mmc) {
+		retval = -ENOMEM;
+		dev_err(&vub300->udev->dev,
+			"not enough memory for the mmc_host");
+		goto error4;
+	}
+	/* MMC core transfer sizes tunable parameters */
+	mmc->caps = 0;
+	if (!force_1_bit_data_xfers)
+		mmc->caps |= MMC_CAP_4_BIT_DATA;
+	if (!force_polling_for_irqs)
+		mmc->caps |= MMC_CAP_SDIO_IRQ;
+	mmc->caps &= ~MMC_CAP_NEEDS_POLL;
+	/*
+	 * MMC_CAP_NEEDS_POLL causes core.c:mmc_rescan() to poll
+	 * for devices which results in spurious CMD7's being
+	 * issued which stops some SDIO cards from working
+	 */
+	if (limit_speed_to_24_MHz) {
+		mmc->caps |= MMC_CAP_MMC_HIGHSPEED;
+		mmc->caps |= MMC_CAP_SD_HIGHSPEED;
+		mmc->f_max = 24000000;
+		dev_info(&udev->dev, "limiting SDIO speed to 24_MHz");
+	} else {
+		mmc->caps |= MMC_CAP_MMC_HIGHSPEED;
+		mmc->caps |= MMC_CAP_SD_HIGHSPEED;
+		mmc->f_max = 48000000;
+	}
+	mmc->f_min = 200000;
+	mmc->max_blk_count = 511;
+	mmc->max_blk_size = 512;
+	mmc->max_hw_segs = 128;
+	mmc->max_phys_segs = 128;
+	if (force_max_req_size)
+		mmc->max_req_size = force_max_req_size * 1024;
+	else
+		mmc->max_req_size = 64 * 1024;
+	mmc->max_seg_size = mmc->max_req_size;
+	mmc->ocr_avail = 0;
+	mmc->ocr_avail |= MMC_VDD_165_195;
+	mmc->ocr_avail |= MMC_VDD_20_21;
+	mmc->ocr_avail |= MMC_VDD_21_22;
+	mmc->ocr_avail |= MMC_VDD_22_23;
+	mmc->ocr_avail |= MMC_VDD_23_24;
+	mmc->ocr_avail |= MMC_VDD_24_25;
+	mmc->ocr_avail |= MMC_VDD_25_26;
+	mmc->ocr_avail |= MMC_VDD_26_27;
+	mmc->ocr_avail |= MMC_VDD_27_28;
+	mmc->ocr_avail |= MMC_VDD_28_29;
+	mmc->ocr_avail |= MMC_VDD_29_30;
+	mmc->ocr_avail |= MMC_VDD_30_31;
+	mmc->ocr_avail |= MMC_VDD_31_32;
+	mmc->ocr_avail |= MMC_VDD_32_33;
+	mmc->ocr_avail |= MMC_VDD_33_34;
+	mmc->ocr_avail |= MMC_VDD_34_35;
+	mmc->ocr_avail |= MMC_VDD_35_36;
+	mmc->ops = &vub300_mmc_ops;
+	vub300 = mmc_priv(mmc);
+	vub300->mmc = mmc;
+	vub300->card_powered = 0;
+	vub300->bus_width = 0;
+	vub300->cmnd.head.BlockSize[0] = 0x00;
+	vub300->cmnd.head.BlockSize[1] = 0x00;
+	vub300->app_spec = 0;
+	mutex_init(&vub300->cmd_mutex);
+	mutex_init(&vub300->irq_mutex);
+	vub300->command_out_urb = command_out_urb;
+	vub300->command_res_urb = command_res_urb;
+	vub300->usb_timed_out = 0;
+	vub300->dynamic_register_count = 0;
+	{
+		int i = 0;
+		do {
+			vub300->fn[i].offload_point = 0;
+			vub300->fn[i].offload_count = 0;
+		} while (++i < 8);
+	}
+	vub300->total_offload_count = 0;
+	vub300->irq_enabled = 0;
+	vub300->irq_disabled = 0;
+	vub300->irqs_queued = 0;
+	{
+		int i = 0;
+		int I = ARRAY_SIZE(vub300->sdio_register);
+		while (I--)
+			vub300->sdio_register[i++].activate = 0;
+	}
+	vub300->udev = udev;
+	vub300->interface = interface;
+	vub300->cmnd_res_ep = 0;
+	vub300->cmnd_out_ep = 0;
+	vub300->data_inp_ep = 0;
+	vub300->data_out_ep = 0;
+	vub300->fbs[0] = 512;
+	vub300->fbs[1] = 512;
+	vub300->fbs[2] = 512;
+	vub300->fbs[3] = 512;
+	vub300->fbs[4] = 512;
+	vub300->fbs[5] = 512;
+	vub300->fbs[6] = 512;
+	vub300->fbs[7] = 512;
+	/*
+	 *      set up the endpoint information
+	 *
+	 * use the first pair of bulk-in and bulk-out
+	 *     endpoints for Command/Response+Interrupt
+	 *
+	 * use the second pair of bulk-in and bulk-out
+	 *     endpoints for Data In/Out
+	 */
+	vub300->large_usb_packets = 0;
+	iface_desc = interface->cur_altsetting;
+	for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {
+		struct usb_endpoint_descriptor *endpoint =
+		    &iface_desc->endpoint[i].desc;
+		dev_info(&vub300->udev->dev,
+			 "vub300 testing %s EndPoint(%d) %02X",
+			 usb_endpoint_is_bulk_in(endpoint) ? "BULK IN" :
+			 usb_endpoint_is_bulk_out(endpoint) ? "BULK OUT" :
+			 "UNKNOWN", i, endpoint->bEndpointAddress);
+		if (64 < endpoint->wMaxPacketSize)
+			vub300->large_usb_packets = 1;
+		if (usb_endpoint_is_bulk_in(endpoint)) {
+			if (!vub300->cmnd_res_ep) {
+				vub300->cmnd_res_ep =
+				    endpoint->bEndpointAddress;
+			} else if (!vub300->data_inp_ep) {
+				vub300->data_inp_ep =
+				    endpoint->bEndpointAddress;
+			} else {
+				dev_warn(&vub300->udev->dev,
+					 "ignoring"
+					 " unexpected bulk_in endpoint");
+			}
+		} else if (usb_endpoint_is_bulk_out(endpoint)) {
+			if (!vub300->cmnd_out_ep) {
+				vub300->cmnd_out_ep =
+				    endpoint->bEndpointAddress;
+			} else if (!vub300->data_out_ep) {
+				vub300->data_out_ep =
+				    endpoint->bEndpointAddress;
+			} else {
+				dev_warn(&vub300->udev->dev,
+					 "ignoring"
+					 " unexpected bulk_out endpoint");
+			}
+		} else {
+			dev_warn(&vub300->udev->dev,
+				 "vub300 ignoring EndPoint(%d) %02X", i,
+				 endpoint->bEndpointAddress);
+		}
+	}
+	if (vub300->cmnd_res_ep
+	    && vub300->cmnd_out_ep
+	    && vub300->data_inp_ep && vub300->data_out_ep) {
+		dev_info(&vub300->udev->dev,
+			 "vub300 %s packets"
+			 " using EndPoints %02X %02X %02X %02X",
+			 vub300->large_usb_packets ? "LARGE" : "SMALL",
+			 vub300->cmnd_out_ep, vub300->cmnd_res_ep,
+			 vub300->data_out_ep, vub300->data_inp_ep);
+		/*
+		 * we have the expected EndPoints
+		 */
+	} else {
+		dev_err(&vub300->udev->dev,
+			"Could not find two sets of bulk-in/out endpoint pairs");
+		retval = -EINVAL;
+		goto error5;
+	}
+	retval =
+	    usb_control_msg(vub300->udev, usb_rcvctrlpipe(vub300->udev, 0),
+			    GET_HC_INF0,
+			    USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			    0x0000, 0x0000, &vub300->hc_info,
+			    sizeof(vub300->hc_info), HZ);
+	if (retval < 0)
+		goto error5;
+	retval =
+	    usb_control_msg(vub300->udev, usb_rcvctrlpipe(vub300->udev, 0),
+			    SET_ROM_WAIT_STATES,
+			    USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			    firmware_rom_wait_states, 0x0000, NULL, 0, HZ);
+	if (retval < 0)
+		goto error5;
+	dev_info(&vub300->udev->dev,
+		 "OperatingMode = %s %s %d MHz %s %d byte USB packets",
+		 (mmc->caps & MMC_CAP_SDIO_IRQ) ? "IRQs" : "POLL",
+		 (mmc->caps & MMC_CAP_4_BIT_DATA) ? "4-bit" : "1-bit",
+		 mmc->f_max / 1000000,
+		 pad_input_to_usb_pkt ? "padding input data to" : "with",
+		 vub300->large_usb_packets ? 512 : 64);
+	retval =
+	    usb_control_msg(vub300->udev, usb_rcvctrlpipe(vub300->udev, 0),
+			    GET_SYSTEM_PORT_STATUS,
+			    USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			    0x0000, 0x0000, &vub300->system_port_status,
+			    sizeof(vub300->system_port_status), HZ);
+	if (retval < 0) {
+		goto error4;
+	} else if (sizeof(vub300->system_port_status) == retval) {
+		vub300->card_present =
+		    (0x0001 & vub300->system_port_status.PortFlags) ? 1 : 0;
+		vub300->read_only =
+		    (0x0010 & vub300->system_port_status.PortFlags) ? 1 : 0;
+	} else {
+		goto error4;
+	}
+	usb_set_intfdata(interface, vub300);
+	INIT_DELAYED_WORK(&vub300->pollwork, vub300_pollwork_thread);
+	INIT_WORK(&vub300->cmndwork, vub300_cmndwork_thread);
+	INIT_WORK(&vub300->deadwork, vub300_deadwork_thread);
+	kref_init(&vub300->kref);
+	init_timer(&vub300->sg_transfer_timer);
+	vub300->sg_transfer_timer.data = (unsigned long)vub300;
+	vub300->sg_transfer_timer.function = vub300_sg_timed_out;
+	kref_get(&vub300->kref);
+	init_timer(&vub300->inactivity_timer);
+	vub300->inactivity_timer.data = (unsigned long)vub300;
+	vub300->inactivity_timer.function = vub300_inactivity_timer_expired;
+	vub300->inactivity_timer.expires = jiffies + HZ;
+	add_timer(&vub300->inactivity_timer);
+	if (vub300->card_present)
+		dev_info(&vub300->udev->dev,
+			 "USB vub300 remote SDIO host controller[%d]"
+			 "connected with SD/SDIO card inserted",
+			 interface_to_InterfaceNumber(interface));
+	else
+		dev_info(&vub300->udev->dev,
+			 "USB vub300 remote SDIO host controller[%d]"
+			 "connected with no SD/SDIO card inserted",
+			 interface_to_InterfaceNumber(interface));
+	retval = sysfs_create_group(&interface->dev.kobj, &vub300_attr_grp);
+	mmc_add_host(mmc);
+	return 0;
+error5:
+	mmc_free_host(mmc);
+	/*
+	 * and hence also frees vub300
+	 * which is contained at the end of struct mmc
+	 */
+error4:
+	usb_free_urb(command_out_urb);
+error1:
+	usb_free_urb(command_res_urb);
+error0:
+	return retval;
+}
+
+static void vub300_disconnect(struct usb_interface *interface)
+{				/* NOT irq */
+	struct vub300_mmc_host *vub300 = usb_get_intfdata(interface);
+	if (!vub300 || !vub300->mmc) {
+		return;
+	} else {
+		struct mmc_host *mmc = vub300->mmc;
+		if (!vub300->mmc) {
+			return;
+		} else {
+			int ifnum = interface_to_InterfaceNumber(interface);
+			sysfs_remove_group(&interface->dev.kobj,
+					   &vub300_attr_grp);
+			usb_set_intfdata(interface, NULL);
+			/* prevent more I/O from starting */
+			vub300->interface = NULL;
+			kref_put(&vub300->kref, vub300_delete);
+			mmc_remove_host(mmc);
+			pr_info("USB vub300 remote SDIO host controller[%d]"
+				" now disconnected", ifnum);
+			return;
+		}
+	}
+}
+
+#ifdef CONFIG_PM
+static int vub300_suspend(struct usb_interface *intf, pm_message_t message)
+{
+	struct vub300_mmc_host *vub300 = usb_get_intfdata(intf);
+	if (!vub300 || !vub300->mmc) {
+		return 0;
+	} else {
+		struct mmc_host *mmc = vub300->mmc;
+		mmc_suspend_host(mmc);
+		return 0;
+	}
+}
+
+static int vub300_resume(struct usb_interface *intf)
+{
+	struct vub300_mmc_host *vub300 = usb_get_intfdata(intf);
+	if (!vub300 || !vub300->mmc) {
+		return 0;
+	} else {
+		struct mmc_host *mmc = vub300->mmc;
+		mmc_resume_host(mmc);
+		return 0;
+	}
+}
+#else
+#define vub300_suspend NULL
+#define vub300_resume NULL
+#endif
+static int vub300_pre_reset(struct usb_interface *intf)
+{				/* NOT irq */
+	struct vub300_mmc_host *vub300 = usb_get_intfdata(intf);
+	mutex_lock(&vub300->cmd_mutex);
+	return 0;
+}
+
+static int vub300_post_reset(struct usb_interface *intf)
+{				/* NOT irq */
+	struct vub300_mmc_host *vub300 = usb_get_intfdata(intf);
+	/* we are sure no URBs are active - no locking needed */
+	vub300->errors = -EPIPE;
+	mutex_unlock(&vub300->cmd_mutex);
+	return 0;
+}
+
+static struct usb_driver vub300_driver = {
+	.name = "vub300",
+	.probe = vub300_probe,
+	.disconnect = vub300_disconnect,
+	.suspend = vub300_suspend,
+	.resume = vub300_resume,
+	.pre_reset = vub300_pre_reset,
+	.post_reset = vub300_post_reset,
+	.id_table = vub300_table,
+	.supports_autosuspend = 1,
+};
+
+static int __init vub300_init(void)
+{				/* NOT irq */
+	int result;
+	pr_info("VUB300 Driver rom wait states = %02X irqpoll timeout = %04X",
+		firmware_rom_wait_states, 0x0FFFF & firmware_irqpoll_timeout);
+	cmndworkqueue = create_singlethread_workqueue("kvub300c");
+	if (!cmndworkqueue) {
+		pr_err("not enough memory for the REQUEST workqueue");
+		result = -ENOMEM;
+		goto out1;
+	}
+	pollworkqueue = create_singlethread_workqueue("kvub300p");
+	if (!pollworkqueue) {
+		pr_err("not enough memory for the IRQPOLL workqueue");
+		result = -ENOMEM;
+		goto out2;
+	}
+	deadworkqueue = create_singlethread_workqueue("kvub300d");
+	if (!deadworkqueue) {
+		pr_err("not enough memory for the EXPIRED workqueue");
+		result = -ENOMEM;
+		goto out3;
+	}
+	result = usb_register(&vub300_driver);
+	if (result) {
+		pr_err("usb_register failed. Error number %d", result);
+		goto out4;
+	}
+	return 0;
+out4:
+	destroy_workqueue(deadworkqueue);
+out3:
+	destroy_workqueue(pollworkqueue);
+out2:
+	destroy_workqueue(cmndworkqueue);
+out1:
+	return result;
+}
+
+static void __exit vub300_exit(void)
+{
+	usb_deregister(&vub300_driver);
+	flush_workqueue(cmndworkqueue);
+	flush_workqueue(pollworkqueue);
+	flush_workqueue(deadworkqueue);
+	destroy_workqueue(cmndworkqueue);
+	destroy_workqueue(pollworkqueue);
+	destroy_workqueue(deadworkqueue);
+}
+
+module_init(vub300_init);
+module_exit(vub300_exit);
+MODULE_LICENSE("GPL");

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH] mmc: USB SDIO/SD/MMC Host Controller (VUB300) driver
  2010-11-22 15:05           ` [PATCH] mmc: USB SDIO/SD/MMC Host Controller (VUB300) driver Tony Olech
@ 2010-11-30  6:15             ` Chris Ball
  2010-11-30 12:23               ` David Vrabel
  2011-01-06 13:17             ` David Vrabel
  2011-01-21 10:50             ` [PATCH] mmc: USB SDIO/SD/MMC Host Controller (VUB300) driver Resubmission Tony Olech
  2 siblings, 1 reply; 41+ messages in thread
From: Chris Ball @ 2010-11-30  6:15 UTC (permalink / raw)
  To: David Vrabel; +Cc: Tony Olech, linux-mmc

Hi David,

Any interest in reviewing this USB-SD driver?  I'm especially curious
about whether you think there are worthwhile possibilities to share 
code with USHC.

Thanks!

- Chris.


From: Tony Olech <tony.olech@elandigitalsystems.com>
Subject: mmc: USB SDIO/SD/MMC Host Controller (VUB300) driver

Add a driver for Elan Digital System's VUB300 chip
which is a USB connected SDIO/SDmem/MMC host controller.
A VUB300 chip enables a USB 2.0 or USB 1.1 connected host
computer to use SDIO/SD/MMC cards without the need for
a directly connected, for example via PCI, SDIO host
controller.

Signed-off-by: Anthony F Olech <tony.olech@elandigitalsystems.com>
---
This is the first submission attempt.
There are 4 "do not initialise statics" errors reported by scripts/checkpatch.pl
This driver has been tested on
a) 32bit x86
b) 64bit x86
c) dual processor
d) PowerPC
---
--- linux-2.6.36-vanilla/MAINTAINERS	2010-10-20 21:30:22.000000000 +0100
+++ linux-2.6.36-vub300/MAINTAINERS	2010-11-22 09:13:04.000000000 +0000
@@ -6369,6 +6369,13 @@ L:	lm-sensors@lm-sensors.org
 S:	Maintained
 F:	drivers/hwmon/vt8231.c
 
+VUB300 USB to SDIO/SD/MMC bridge chip
+M:	Tony Olech <tony.olech@elandigitalsystems.com>
+L:	linux-mmc@vger.kernel.org
+L:	linux-usb@vger.kernel.org
+S:	Supported
+F:	drivers/mmc/host/vub300.c
+
 W1 DALLAS'S 1-WIRE BUS
 M:	Evgeniy Polyakov <johnpol@2ka.mipt.ru>
 S:	Maintained
--- linux-2.6.36-vanilla/drivers/mmc/host/Makefile	2010-10-20 21:30:22.000000000 +0100
+++ linux-2.6.36-vub300/drivers/mmc/host/Makefile	2010-11-17 12:00:53.000000000 +0000
@@ -36,6 +36,8 @@ obj-$(CONFIG_MMC_VIA_SDMMC)	+= via-sdmmc
 obj-$(CONFIG_SDH_BFIN)		+= bfin_sdh.o
 obj-$(CONFIG_MMC_SH_MMCIF)	+= sh_mmcif.o
 obj-$(CONFIG_MMC_JZ4740)	+= jz4740_mmc.o
+obj-$(CONFIG_MMC_VUB300)	+= vub300.o
+
 
 obj-$(CONFIG_MMC_SDHCI_PLTFM)			+= sdhci-platform.o
 sdhci-platform-y				:= sdhci-pltfm.o
--- linux-2.6.36-vanilla/drivers/mmc/host/Kconfig	2010-10-20 21:30:22.000000000 +0100
+++ linux-2.6.36-vub300/drivers/mmc/host/Kconfig	2010-11-19 15:35:36.000000000 +0000
@@ -451,3 +451,35 @@ config MMC_JZ4740
 	  SoCs.
 	  If you have a board based on such a SoC and with a SD/MMC slot,
 	  say Y or M here.
+
+config MMC_VUB300
+	tristate "VUB300 USB to SDIO/SD/MMC Host Controller support"
+	depends on USB
+	help
+	  This selects support for Elan Digital Systems' VUB300 chip.
+
+	  The VUB300 is a USB-SDIO Host Controller Interface chip
+	  that enables the host computer to use SDIO/SD/MMC cards
+	  via a USB 2.0 or USB 1.1 host.
+
+	  The VUB300 chip will be found in both physically separate
+	  USB to SDIO/SD/MMC adapters and embedded on some motherboards.
+
+	  The VUB300 chip supports SD and MMC memory cards in addition
+	  to single and multifunction SDIO cards.
+
+	  Some SDIO cards will need a firmware file to be loaded and
+	  sent to VUB300 chip in order to achieve better data throughput.
+	  Download these "Offload Pseudocode" from Elan Digital Systems'
+	  web-site http://www.elandigitalsystems.com/support/downloads.php
+	  and put them in /lib/firmware. Note that without these additional
+	  firmware files the VUB300 chip will still function, but not at
+	  the best obtainable data rate.
+
+	  To compile this mmc host controller driver as a module,
+	  choose M here: the module will be called vub300.
+
+	  If you have a computer with an embedded VUB300 chip
+	  or if you intend connecting a USB adapter based on a
+	  VUB300 chip say Y or M here.
+
--- /dev/null	2010-11-22 08:39:34.632000002 +0000
+++ linux-2.6.36-vub300/drivers/mmc/host/vub300.c	2010-11-22 13:59:00.000000000 +0000
@@ -0,0 +1,2606 @@
+/*
+ * Remote VUB300 SDIO/SDmem Host Controller Driver
+ *
+ * Copyright (C) 2010 Elan Digital Systems Limited
+ *
+ * based on USB Skeleton driver - 2.2
+ *
+ * Copyright (C) 2001-2004 Greg Kroah-Hartman (greg@kroah.com)
+ *
+ *	This program is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU General Public License as
+ *	published by the Free Software Foundation, version 2
+ *
+ * VUB300: is a USB 2.0 client device with a single SDIO/SDmem/MMC slot
+ *         Any SDIO/SDmem/MMC device plugged into the VUB300 will appear,
+ *         by virtue of this driver, to have been plugged into a local
+ *         SDIO host controller, similar to, say, a PCI Ricoh controller
+ *         This is because this kernel device driver is both a USB 2.0
+ *         client device driver AND an MMC host controller driver. Thus
+ *         if there is an existing driver for the inserted SDIO/SDmem/MMC
+ *         device then that driver will be used by the kernel to manage
+ *         the device in exactly the same fashion as if it had been
+ *         directly plugged into, say, a local pci bus Ricoh controller
+ *
+ * RANT: this driver was written using a display 128x48 - converting it
+ *       to a line width of 80 makes it very difficult to support. In
+ *       particular functions have been broken down into sub functions
+ *       and the original meaningful names have been shortened into
+ *       cryptic ones.
+ *       The problem is that executing a fragment of code subject to
+ *       two conditions means an indentation of 24, thus leaving only
+ *       56 characters for a C statement. And that is quite ridiculus!
+ *
+ * Data types: data passed to/from the VUB300 is fixed to a number of
+ *             bits and driver data fields reflect that limit by using
+ *             u8, u16, u32
+ */
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/kref.h>
+#include <linux/uaccess.h>
+#include <linux/usb.h>
+#include <linux/mutex.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/card.h>
+#include <linux/mmc/sdio_func.h>
+#include <linux/mmc/sdio_ids.h>
+#include <linux/workqueue.h>
+#include <linux/ctype.h>
+#include <linux/firmware.h>
+#include <linux/scatterlist.h>
+#pragma pack(1)
+struct HostController_Info {
+	u8 Size;
+	u16 FirmwareVer;
+	u8 NumberOfPorts;
+};
+struct SD_Command_Header {
+	u8 HeaderSize;
+	u8 HeaderType;
+	u8 PortNumber;
+	u8 Command_Type;	/* Bit7 - Rd/Wr */
+	u8 CommandIndex;
+	u8 TransferSize[4];	/* ReadSize + ReadSize */
+	u8 ResponseType;
+	u8 Arguments[4];
+	u8 BlockCount[2];
+	u8 BlockSize[2];
+	u8 BlockBoundary[2];
+	u8 Reserved[44];	/* to pad out to 64 bytes */
+};
+struct SD_IRQpoll_Header {
+	u8 HeaderSize;
+	u8 HeaderType;
+	u8 PortNumber;
+	u8 Command_Type;	/* Bit7 - Rd/Wr */
+	u8 Padding[16];		/* don't ask why !! */
+	u8 Poll_Timeout_MSB;
+	u8 Poll_Timeout_LSB;
+	u8 Reserved[42];	/* to pad out to 64 bytes */
+};
+struct SD_Common_Header {
+	u8 HeaderSize;
+	u8 HeaderType;
+	u8 PortNumber;
+};
+struct SD_Response_Header {
+	u8 HeaderSize;
+	u8 HeaderType;
+	u8 PortNumber;
+	u8 Command_Type;
+	u8 CommandIndex;
+	u8 CommandResponse[0];
+};
+struct SD_Status_Header {
+	u8 HeaderSize;
+	u8 HeaderType;
+	u8 PortNumber;
+	u16 PortFlags;
+	u32 SDIOclock;
+	u16 HostHeaderSize;
+	u16 FuncHeaderSize;
+	u16 CtrlHeaderSize;
+};
+struct SD_Error_Header {
+	u8 HeaderSize;
+	u8 HeaderType;
+	u8 PortNumber;
+	u8 ErrorCode;
+};
+struct SD_Interrupt_Header {
+	u8 HeaderSize;
+	u8 HeaderType;
+	u8 PortNumber;
+};
+struct Offload_Registers_Access {
+	u8 Command_Byte[4];
+	u8 Respond_Byte[4];
+};
+#define INTERRUPT_REGISTER_ACCESSES 15
+struct SD_Offloaded_Interrupt {
+	u8 HeaderSize;
+	u8 HeaderType;
+	u8 PortNumber;
+	struct Offload_Registers_Access reg[INTERRUPT_REGISTER_ACCESSES];
+};
+struct SD_Register_Header {
+	u8 HeaderSize;
+	u8 HeaderType;
+	u8 PortNumber;
+	u8 Command_Type;
+	u8 CommandIndex;
+	u8 CommandResponse[6];
+};
+#define PIGGYBACK_REGISTER_ACCESSES 14
+struct SD_Offloaded_Piggyback {
+	struct SD_Register_Header sdio;
+	struct Offload_Registers_Access reg[PIGGYBACK_REGISTER_ACCESSES];
+};
+union SD_Response {
+	struct SD_Common_Header common;
+	struct SD_Status_Header status;
+	struct SD_Error_Header error;
+	struct SD_Interrupt_Header interrupt;
+	struct SD_Response_Header response;
+	struct SD_Offloaded_Interrupt irq;
+	struct SD_Offloaded_Piggyback pig;
+};
+union SD_Command {
+	struct SD_Command_Header head;
+	struct SD_IRQpoll_Header poll;
+};
+enum SD_RESPONSE_TYPE {
+	SDRT_UNSPECIFIED = 0,
+	SDRT_NONE,
+	SDRT_1,
+	SDRT_1B,
+	SDRT_2,
+	SDRT_3,
+	SDRT_4,
+	SDRT_5,
+	SDRT_5B,
+	SDRT_6,
+	SDRT_7,
+};
+#pragma pack()
+#define RESPONSE_INTERRUPT 0x01
+#define RESPONSE_ERROR 0x02
+#define RESPONSE_STATUS 0x03
+#define RESPONSE_IRQ_DISABLED 0x05
+#define RESPONSE_IRQ_ENABLED 0x06
+#define RESPONSE_PIGGYBACKED 0x07
+#define RESPONSE_NO_INTERRUPT 0x08
+#define RESPONSE_PIG_DISABLED 0x09
+#define RESPONSE_PIG_ENABLED 0x0A
+#define SD_ERROR_1BIT_TIMEOUT   0x01
+#define SD_ERROR_4BIT_TIMEOUT   0x02
+#define SD_ERROR_1BIT_CRC_WRONG 0x03
+#define SD_ERROR_4BIT_CRC_WRONG 0x04
+#define SD_ERROR_1BIT_CRC_ERROR 0x05
+#define SD_ERROR_4BIT_CRC_ERROR 0x06
+#define SD_ERROR_NO_CMD_ENDBIT  0x07
+#define SD_ERROR_NO_1BIT_DATEND 0x08
+#define SD_ERROR_NO_4BIT_DATEND 0x09
+#define SD_ERROR_1BIT_UNEXPECTED_TIMEOUT    0x0A
+#define SD_ERROR_4BIT_UNEXPECTED_TIMEOUT    0x0B
+#define SD_ERROR_ILLEGAL_COMMAND    0x0C
+#define SD_ERROR_NO_DEVICE 0x0D
+#define SD_ERROR_TRANSFER_LENGTH    0x0E
+#define SD_ERROR_1BIT_DATA_TIMEOUT  0x0F
+#define SD_ERROR_4BIT_DATA_TIMEOUT  0x10
+#define SD_ERROR_ILLEGAL_STATE  0x11
+#define SD_ERROR_UNKNOWN_ERROR  0x12
+#define SD_ERROR_RESERVED_ERROR 0x13
+#define SD_ERROR_INVALID_FUNCTION   0x14
+#define SD_ERROR_OUT_OF_RANGE   0x15
+#define SD_ERROR_STAT_CMD 0x16
+#define SD_ERROR_STAT_DATA 0x17
+#define SD_ERROR_STAT_CMD_TIMEOUT 0x18
+#define SD_ERROR_SDCRDY_STUCK 0x19
+#define SD_ERROR_UNHANDLED 0x1A
+#define SD_ERROR_OVERRUN 0x1B
+#define SD_ERROR_PIO_TIMEOUT 0x1C
+MODULE_AUTHOR("Tony Olech <tony.olech@elandigitalsystems.com>");
+MODULE_DESCRIPTION("VUB300 USB to SD/MMC/SDIO adapter driver");
+MODULE_LICENSE("GPL");
+#define FUN(c) (0x000007 & (c->arg>>28))
+#define REG(c) (0x01FFFF & (c->arg>>9))
+static int limit_speed_to_24_MHz = 1;
+module_param(limit_speed_to_24_MHz, bool, 0644);
+MODULE_PARM_DESC(limit_speed_to_24_MHz, "Limit Max SDIO Clock Speed to 24 MHz");
+static int pad_input_to_usb_pkt = 0;
+module_param(pad_input_to_usb_pkt, bool, 0644);
+MODULE_PARM_DESC(pad_input_to_usb_pkt,
+		 "Pad USB data input transfers to whole USB Packet");
+static int disable_offload_processing = 0;
+module_param(disable_offload_processing, bool, 0644);
+MODULE_PARM_DESC(disable_offload_processing, "Disable Offload Processing");
+static int force_1_bit_data_xfers = 0;
+module_param(force_1_bit_data_xfers, bool, 0644);
+MODULE_PARM_DESC(force_1_bit_data_xfers,
+		 "Force SDIO Data Transfers to 1-bit Mode");
+static int force_polling_for_irqs = 0;
+module_param(force_polling_for_irqs, bool, 0644);
+MODULE_PARM_DESC(force_polling_for_irqs, "Force Polling for SDIO interrupts");
+static int firmware_irqpoll_timeout = 1024;
+module_param(firmware_irqpoll_timeout, int, 0644);
+MODULE_PARM_DESC(firmware_irqpoll_timeout, "VUB300 firmware irqpoll timeout");
+static int force_max_req_size = 128;
+module_param(force_max_req_size, int, 0644);
+MODULE_PARM_DESC(force_max_req_size, "set max request size in kBytes");
+#ifdef SMSC_DEVELOPMENT_BOARD
+static int firmware_rom_wait_states = 0x04;
+#else
+static int firmware_rom_wait_states = 0x1C;
+#endif
+module_param(firmware_rom_wait_states, bool, 0644);
+MODULE_PARM_DESC(firmware_rom_wait_states,
+		 "ROM wait states byte=RRRIIEEE (Reserved Internal External)");
+/* Define these values to match your devices */
+#define ELAN_VENDOR_ID			0x2201
+#define VUB300_VENDOR_ID		0x0424
+#define VUB300_PRODUCT_ID		0x012C
+#define FIRMWARE_BLOCK_BOUNDARY 1024
+/* table of devices that work with this driver */
+static struct usb_device_id vub300_table[] = {
+	{USB_DEVICE(ELAN_VENDOR_ID, VUB300_PRODUCT_ID)},
+	{USB_DEVICE(VUB300_VENDOR_ID, VUB300_PRODUCT_ID)},
+	{}			/* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE(usb, vub300_table);
+static struct workqueue_struct *cmndworkqueue;
+static struct workqueue_struct *pollworkqueue;
+static struct workqueue_struct *deadworkqueue;
+static inline int interface_to_InterfaceNumber(struct usb_interface *interface)
+{
+	if (!interface)
+		return -1;
+	if (!interface->cur_altsetting)
+		return -1;
+	return interface->cur_altsetting->desc.bInterfaceNumber;
+}
+
+struct sdio_register {
+	unsigned func_num:3;
+	unsigned sdio_reg:17;
+	unsigned activate:1;
+	unsigned prepared:1;
+	unsigned regvalue:8;
+	unsigned response:8;
+	unsigned sparebit:26;
+};
+struct vub300_mmc_host {
+	struct usb_device *udev;
+	struct usb_interface *interface;
+	struct kref kref;
+	struct mutex cmd_mutex;
+	struct mutex irq_mutex;
+	char vub_name[3 + (9 * 8) + 4 + 1];	/* max of 7 sdio fn's */
+	u8 cmnd_out_ep;		/* EndPoint for commands */
+	u8 cmnd_res_ep;		/* EndPoint for responses */
+	u8 data_out_ep;		/* EndPoint for out data */
+	u8 data_inp_ep;		/* EndPoint for inp data */
+	unsigned card_powered:1;
+	unsigned card_present:1;
+	unsigned read_only:1;
+	unsigned large_usb_packets:1;
+	unsigned app_spec:1;	/* ApplicationSpecific */
+	unsigned irq_enabled:1;	/* by the MMC CORE */
+	unsigned irq_disabled:1;	/* in the firmware */
+	unsigned bus_width:4;
+	u8 total_offload_count;
+	u8 dynamic_register_count;
+	u8 resp_len;
+	u32 datasize;
+	int errors;
+	int usb_transport_fail;
+	int usb_timed_out;
+	int irqs_queued;
+	struct sdio_register sdio_register[16];
+	struct offload_interrupt_function_register {
+#define MAXREGBITS 4
+#define MAXREGS (1<<MAXREGBITS)
+#define MAXREGMASK (MAXREGS-1)
+		u8 offload_count;
+		u32 offload_point;
+		struct Offload_Registers_Access reg[MAXREGS];
+	} fn[8];
+	u16 fbs[8];		/* Function Block Size */
+	struct mmc_command *cmd;
+	struct mmc_request *req;
+	struct mmc_data *data;
+	struct mmc_host *mmc;
+	struct urb *urb;
+	struct urb *command_out_urb;
+	struct urb *command_res_urb;
+	struct completion command_complete;
+	struct completion irqpoll_complete;
+	union SD_Command cmnd;
+	union SD_Response resp;
+	struct timer_list sg_transfer_timer;
+	struct usb_sg_request sg_request;
+	struct timer_list inactivity_timer;
+	struct work_struct deadwork;
+	struct work_struct cmndwork;
+	struct delayed_work pollwork;
+	struct HostController_Info hc_info;
+	struct SD_Status_Header system_port_status;
+	u8 padded_buffer[64];
+};
+#define kref_to_vub300_mmc_host(d) container_of(d, struct vub300_mmc_host, kref)
+#define SET_TRANSFER_PSEUDOCODE 21
+#define SET_INTERRUPT_PSEUDOCODE 20
+#define SET_FAILURE_MODE 18
+#define SET_ROM_WAIT_STATES 16
+#define SET_IRQ_ENABLE 13
+#define SET_CLOCK_SPEED 11
+#define SET_FUNCTION_BLOCK_SIZE 9
+#define SET_SD_DATA_MODE 6
+#define SET_SD_POWER 4
+#define ENTER_DFU_MODE 3
+#define GET_HC_INF0 1
+#define GET_SYSTEM_PORT_STATUS 0
+static void vub300_delete(struct kref *kref)
+{				/* kref callback - softirq */
+	struct vub300_mmc_host *vub300 = kref_to_vub300_mmc_host(kref);
+	struct mmc_host *mmc = vub300->mmc;
+	usb_free_urb(vub300->command_out_urb);
+	vub300->command_out_urb = NULL;
+	usb_free_urb(vub300->command_res_urb);
+	vub300->command_res_urb = NULL;
+	usb_put_dev(vub300->udev);
+	mmc_free_host(mmc);
+	/*
+	 * and hence also frees vub300
+	 * which is contained at the end of struct mmc
+	 */
+}
+
+static ssize_t __show_OperatingMode(struct vub300_mmc_host *vub300,
+				    struct mmc_host *mmc, char *buf)
+{
+	int usb_packet_size = vub300->large_usb_packets ? 512 : 64;
+	return sprintf(buf, "VUB %s %s %d MHz %s %d byte USB packets using %s",
+		       (mmc->caps & MMC_CAP_SDIO_IRQ) ? "IRQs" : "POLL",
+		       (mmc->caps & MMC_CAP_4_BIT_DATA) ? "4-bit" : "1-bit",
+		       mmc->f_max / 1000000,
+		       pad_input_to_usb_pkt ? "padding input data to" : "with",
+		       usb_packet_size, vub300->vub_name);
+}
+
+static ssize_t show_OperatingMode(struct device *dev,
+				  struct device_attribute *attr, char *buf)
+{
+	struct usb_interface *intf = to_usb_interface(dev);
+	struct vub300_mmc_host *vub300 = usb_get_intfdata(intf);
+	struct mmc_host *mmc = vub300->mmc;
+	if (mmc)
+		return __show_OperatingMode(vub300, mmc, buf);
+	else
+		return sprintf(buf, "VUB driver has no attached device");
+}
+
+static DEVICE_ATTR(OperatingMode, S_IRUGO, show_OperatingMode, NULL);
+static struct attribute *vub300_attrs[] = {
+	&dev_attr_OperatingMode.attr,
+	NULL,
+};
+
+static struct attribute_group vub300_attr_grp = {
+	.attrs = vub300_attrs,
+};
+
+static void vub300_queue_cmnd_work(struct vub300_mmc_host *vub300)
+{
+	kref_get(&vub300->kref);
+	if (queue_work(cmndworkqueue, &vub300->cmndwork)) {
+		/*
+		 * then the cmndworkqueue was not previously
+		 * running and the above get ref is obvious
+		 * required and will be put when the thread
+		 * terminates by a specific call
+		 */
+	} else {
+		/*
+		 * the cmndworkqueue was already running from
+		 * a previous invocation and thus to keep the
+		 * kref counts correct we must undo the get
+		 */
+		kref_put(&vub300->kref, vub300_delete);
+	}
+}
+
+static void vub300_queue_poll_work(struct vub300_mmc_host *vub300, int delay)
+{
+	kref_get(&vub300->kref);
+	if (queue_delayed_work(pollworkqueue, &vub300->pollwork, delay)) {
+		/*
+		 * then the pollworkqueue was not previously
+		 * running and the above get ref is obvious
+		 * required and will be put when the thread
+		 * terminates by a specific call
+		 */
+	} else {
+		/*
+		 * the pollworkqueue was already running from
+		 * a previous invocation and thus to keep the
+		 * kref counts correct we must undo the get
+		 */
+		kref_put(&vub300->kref, vub300_delete);
+	}
+}
+
+static void vub300_queue_dead_work(struct vub300_mmc_host *vub300)
+{
+	kref_get(&vub300->kref);
+	if (queue_work(deadworkqueue, &vub300->deadwork)) {
+		/*
+		 * then the deadworkqueue was not previously
+		 * running and the above get ref is obvious
+		 * required and will be put when the thread
+		 * terminates by a specific call
+		 */
+	} else {
+		/*
+		 * the deadworkqueue was already running from
+		 * a previous invocation and thus to keep the
+		 * kref counts correct we must undo the get
+		 */
+		kref_put(&vub300->kref, vub300_delete);
+	}
+}
+
+static void irqpoll_res_completed(struct urb *urb)
+{				/* urb completion handler - hardirq */
+	/*
+	 * cmd_mutex is held by vub300_pollwork_thread
+	 */
+	struct vub300_mmc_host *vub300 = (struct vub300_mmc_host *)urb->context;
+	if (urb->status)
+		vub300->usb_transport_fail = urb->status;
+	complete(&vub300->irqpoll_complete);
+}
+
+static void irqpoll_out_completed(struct urb *urb)
+{				/* urb completion handler - hardirq */
+	/*
+	 * cmd_mutex is held by vub300_pollwork_thread
+	 */
+	struct vub300_mmc_host *vub300 = (struct vub300_mmc_host *)urb->context;
+	if (urb->status) {
+		vub300->usb_transport_fail = urb->status;
+		complete(&vub300->irqpoll_complete);
+		return;
+	} else {
+		int ret;
+		unsigned int pipe =
+		    usb_rcvbulkpipe(vub300->udev, vub300->cmnd_res_ep);
+		usb_fill_bulk_urb(vub300->command_res_urb, vub300->udev, pipe,
+				  &vub300->resp, sizeof(vub300->resp),
+				  irqpoll_res_completed, vub300);
+		vub300->command_res_urb->actual_length = 0;
+		ret = usb_submit_urb(vub300->command_res_urb, GFP_NOIO);
+		if (ret) {
+			vub300->usb_transport_fail = ret;
+			complete(&vub300->irqpoll_complete);
+		}
+		return;
+	}
+}
+
+static void send_irqpoll(struct vub300_mmc_host *vub300)
+{
+	/*
+	 * cmd_mutex is held by vub300_pollwork_thread
+	 */
+	int retval;
+	int timeout = 0xFFFF & (0x0001FFFF - firmware_irqpoll_timeout);
+	vub300->cmnd.poll.HeaderSize = 22;
+	vub300->cmnd.poll.HeaderType = 1;
+	vub300->cmnd.poll.PortNumber = 0;
+	vub300->cmnd.poll.Command_Type = 2;
+	vub300->cmnd.poll.Poll_Timeout_LSB = 0xFF & (unsigned)timeout;
+	vub300->cmnd.poll.Poll_Timeout_MSB = 0xFF & (unsigned)(timeout >> 8);
+	usb_fill_bulk_urb(vub300->command_out_urb, vub300->udev,
+			  usb_sndbulkpipe(vub300->udev, vub300->cmnd_out_ep)
+			  , &vub300->cmnd, sizeof(vub300->cmnd)
+			  , irqpoll_out_completed, vub300);
+	retval = usb_submit_urb(vub300->command_out_urb, GFP_ATOMIC);
+	if (0 > retval) {
+		vub300->usb_transport_fail = retval;
+		vub300_queue_poll_work(vub300, 1);
+		complete(&vub300->irqpoll_complete);
+		return;
+	} else {
+		return;
+	}
+}
+
+static void new_system_port_status(struct vub300_mmc_host *vub300)
+{
+	int old_card_present = vub300->card_present;
+	int new_card_present =
+	    (0x0001 & vub300->system_port_status.PortFlags) ? 1 : 0;
+	vub300->read_only =
+	    (0x0010 & vub300->system_port_status.PortFlags) ? 1 : 0;
+	if (new_card_present && !old_card_present) {
+		dev_info(&vub300->udev->dev, "card just inserted");
+		vub300->card_present = 1;
+		vub300->bus_width = 0;
+		if (disable_offload_processing)
+			strncpy(vub300->vub_name, "EMPTY Processing Disabled",
+				sizeof(vub300->vub_name));
+		else
+			vub300->vub_name[0] = 0;
+		mmc_detect_change(vub300->mmc, 1);
+		return;
+	} else if (!new_card_present && old_card_present) {
+		dev_info(&vub300->udev->dev, "card just ejected");
+		vub300->card_present = 0;
+		mmc_detect_change(vub300->mmc, 0);
+		return;
+	} else {
+		return;
+	}
+}
+
+static void __add_offloaded_reg_to_fifo(struct vub300_mmc_host *vub300,
+					struct Offload_Registers_Access
+					*register_access, u8 Function)
+{
+	u8 r =
+	    vub300->fn[Function].offload_point +
+	    vub300->fn[Function].offload_count;
+	memcpy(&vub300->fn[Function].reg[MAXREGMASK & r]
+	       , register_access, sizeof(struct Offload_Registers_Access));
+	vub300->fn[Function].offload_count += 1;
+	vub300->total_offload_count += 1;
+}
+
+static void add_offloaded_reg(struct vub300_mmc_host *vub300,
+			      struct Offload_Registers_Access *register_access)
+{
+	u32 Register = ((0x03 & register_access->Command_Byte[0]) << 15)
+	    | ((0xFF & register_access->Command_Byte[1]) << 7)
+	    | ((0xFE & register_access->Command_Byte[2]) >> 1);
+	u8 Function = ((0x70 & register_access->Command_Byte[0]) >> 4);
+	u8 regs = vub300->dynamic_register_count;
+	u8 i = 0;
+	while (0 < regs-- && 1 == vub300->sdio_register[i].activate) {
+		if ((vub300->sdio_register[i].func_num == Function)
+		    && (vub300->sdio_register[i].sdio_reg == Register)
+		    ) {
+			if (0 == vub300->sdio_register[i].prepared)
+				vub300->sdio_register[i].prepared = 1;
+			vub300->sdio_register[i].response =
+			    register_access->Respond_Byte[2];
+			vub300->sdio_register[i].regvalue =
+			    register_access->Respond_Byte[3];
+			return;
+		} else {
+			i += 1;
+			continue;
+		}
+	};
+	__add_offloaded_reg_to_fifo(vub300, register_access, Function);
+}
+
+static void check_vub300_port_status(struct vub300_mmc_host *vub300)
+{
+	/*
+	 * cmd_mutex is held by vub300_pollwork_thread,
+	 * vub300_deadwork_thread or vub300_cmndwork_thread
+	 */
+	int retval;
+	retval =
+	    usb_control_msg(vub300->udev, usb_rcvctrlpipe(vub300->udev, 0),
+			    GET_SYSTEM_PORT_STATUS,
+			    USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			    0x0000, 0x0000, &vub300->system_port_status,
+			    sizeof(vub300->system_port_status), HZ);
+	if (sizeof(vub300->system_port_status) == retval)
+		new_system_port_status(vub300);
+}
+
+static void __vub300_irqpoll_response(struct vub300_mmc_host *vub300)
+{
+	/*
+	 * cmd_mutex is held by vub300_pollwork_thread
+	 */
+	if (0 == vub300->command_res_urb->actual_length) {
+		mutex_unlock(&vub300->cmd_mutex);
+		kref_put(&vub300->kref, vub300_delete);
+		return;
+	} else if (RESPONSE_INTERRUPT == vub300->resp.common.HeaderType) {
+		mutex_lock(&vub300->irq_mutex);
+		if (vub300->irq_enabled)
+			mmc_signal_sdio_irq(vub300->mmc);
+		else
+			vub300->irqs_queued += 1;
+		vub300->irq_disabled = 1;
+		mutex_unlock(&vub300->irq_mutex);
+		mutex_unlock(&vub300->cmd_mutex);
+		kref_put(&vub300->kref, vub300_delete);
+		return;
+	} else if (RESPONSE_ERROR == vub300->resp.common.HeaderType) {
+		if (SD_ERROR_NO_DEVICE == vub300->resp.error.ErrorCode)
+			check_vub300_port_status(vub300);
+		mutex_unlock(&vub300->cmd_mutex);
+		vub300_queue_poll_work(vub300, HZ);
+		kref_put(&vub300->kref, vub300_delete);
+		return;
+	} else if (RESPONSE_STATUS == vub300->resp.common.HeaderType) {
+		vub300->system_port_status = vub300->resp.status;
+		new_system_port_status(vub300);
+		mutex_unlock(&vub300->cmd_mutex);
+		if (!vub300->card_present)
+			vub300_queue_poll_work(vub300, HZ / 5);
+		kref_put(&vub300->kref, vub300_delete);
+		return;
+	} else if (RESPONSE_IRQ_DISABLED == vub300->resp.common.HeaderType) {
+		int offloaded_data_length = vub300->resp.common.HeaderSize - 3;
+		int register_count = offloaded_data_length >> 3;
+		int ri = 0;
+		while (register_count--) {
+			add_offloaded_reg(vub300, &vub300->resp.irq.reg[ri]);
+			ri += 1;
+		}
+		mutex_lock(&vub300->irq_mutex);
+		if (vub300->irq_enabled)
+			mmc_signal_sdio_irq(vub300->mmc);
+		else
+			vub300->irqs_queued += 1;
+		vub300->irq_disabled = 1;
+		mutex_unlock(&vub300->irq_mutex);
+		mutex_unlock(&vub300->cmd_mutex);
+		kref_put(&vub300->kref, vub300_delete);
+		return;
+	} else if (RESPONSE_IRQ_ENABLED == vub300->resp.common.HeaderType) {
+		int offloaded_data_length = vub300->resp.common.HeaderSize - 3;
+		int register_count = offloaded_data_length >> 3;
+		int ri = 0;
+		while (register_count--) {
+			add_offloaded_reg(vub300, &vub300->resp.irq.reg[ri]);
+			ri += 1;
+		}
+		mutex_lock(&vub300->irq_mutex);
+		if (vub300->irq_enabled)
+			mmc_signal_sdio_irq(vub300->mmc);
+		else if (vub300->irqs_queued)
+			vub300->irqs_queued += 1;
+		else
+			vub300->irqs_queued += 1;
+		vub300->irq_disabled = 0;
+		mutex_unlock(&vub300->irq_mutex);
+		mutex_unlock(&vub300->cmd_mutex);
+		kref_put(&vub300->kref, vub300_delete);
+		return;
+	} else if (RESPONSE_NO_INTERRUPT == vub300->resp.common.HeaderType) {
+		vub300_queue_poll_work(vub300, 1);
+		mutex_unlock(&vub300->cmd_mutex);
+		kref_put(&vub300->kref, vub300_delete);
+		return;
+	} else {
+		mutex_unlock(&vub300->cmd_mutex);
+		kref_put(&vub300->kref, vub300_delete);
+		return;
+	}
+}
+
+static void __do_poll(struct vub300_mmc_host *vub300)
+{
+	/*
+	 * cmd_mutex is held by vub300_pollwork_thread
+	 */
+	long commretval;
+	mod_timer(&vub300->inactivity_timer, jiffies + HZ);
+	init_completion(&vub300->irqpoll_complete);
+	send_irqpoll(vub300);
+	commretval =
+	    wait_for_completion_timeout(&vub300->irqpoll_complete,
+					msecs_to_jiffies(500));
+	if (vub300->usb_transport_fail) {
+		mutex_unlock(&vub300->cmd_mutex);
+		kref_put(&vub300->kref, vub300_delete);
+		return;
+	} else if (0 == commretval) {
+		vub300->usb_timed_out = 1;
+		usb_kill_urb(vub300->command_out_urb);
+		usb_kill_urb(vub300->command_res_urb);
+		mutex_unlock(&vub300->cmd_mutex);
+		kref_put(&vub300->kref, vub300_delete);
+		return;
+	} else if (0 > commretval) {
+		vub300_queue_poll_work(vub300, 1);
+		mutex_unlock(&vub300->cmd_mutex);
+		kref_put(&vub300->kref, vub300_delete);
+		return;
+	} else {		/*(0 < commretval) */
+
+		__vub300_irqpoll_response(vub300);
+		return;
+	}
+}
+
+/* this thread runs only when the driver
+ * is trying to poll the device for an IRQ
+ */
+static void vub300_pollwork_thread(struct work_struct *work)
+{				/* NOT irq */
+	struct vub300_mmc_host *vub300 =
+	    container_of(work, struct vub300_mmc_host, pollwork.work);
+	if (!vub300->interface) {
+		kref_put(&vub300->kref, vub300_delete);
+		return;
+	}
+	mutex_lock(&vub300->cmd_mutex);
+	if (vub300->cmd) {
+		mutex_unlock(&vub300->cmd_mutex);
+		vub300_queue_poll_work(vub300, 1);
+		kref_put(&vub300->kref, vub300_delete);
+	} else if (!vub300->card_present) {
+		mutex_unlock(&vub300->cmd_mutex);
+		kref_put(&vub300->kref, vub300_delete);
+	} else {		/* vub300->card_present */
+		mutex_lock(&vub300->irq_mutex);
+		if (!vub300->irq_enabled) {
+			mutex_unlock(&vub300->irq_mutex);
+			mutex_unlock(&vub300->cmd_mutex);
+			kref_put(&vub300->kref, vub300_delete);
+		} else if (vub300->irqs_queued) {
+			vub300->irqs_queued -= 1;
+			mmc_signal_sdio_irq(vub300->mmc);
+			mod_timer(&vub300->inactivity_timer, jiffies + HZ);
+			mutex_unlock(&vub300->irq_mutex);
+			mutex_unlock(&vub300->cmd_mutex);
+			kref_put(&vub300->kref, vub300_delete);
+		} else {	/* NOT vub300->irqs_queued */
+			mutex_unlock(&vub300->irq_mutex);
+			__do_poll(vub300);
+		}
+	}
+}
+
+static void vub300_deadwork_thread(struct work_struct *work)
+{				/* NOT irq */
+	struct vub300_mmc_host *vub300 =
+	    container_of(work, struct vub300_mmc_host, deadwork);
+	if (!vub300->interface) {
+		kref_put(&vub300->kref, vub300_delete);
+		return;
+	}
+	mutex_lock(&vub300->cmd_mutex);
+	if (vub300->cmd) {
+		/*
+		 * a command got in as the inactivity
+		 * timer expired - so we just let the
+		 * processing of the command show if
+		 * the device is dead
+		 */
+	} else if (vub300->card_present) {
+		check_vub300_port_status(vub300);
+	} else if (vub300->mmc && vub300->mmc->card
+		   && mmc_card_present(vub300->mmc->card)) {
+		/*
+		 * the MMC core must not have responded
+		 * to the previous indication - lets
+		 * hope that it eventually does so we
+		 * will just ignore this for now
+		 */
+	} else {
+		check_vub300_port_status(vub300);
+	}
+	mod_timer(&vub300->inactivity_timer, jiffies + HZ);
+	mutex_unlock(&vub300->cmd_mutex);
+	kref_put(&vub300->kref, vub300_delete);
+}
+
+static void vub300_inactivity_timer_expired(unsigned long data)
+{				/* softirq */
+	struct vub300_mmc_host *vub300 = (struct vub300_mmc_host *)data;
+	if (!vub300->interface) {
+		kref_put(&vub300->kref, vub300_delete);
+	} else if (vub300->cmd) {
+		mod_timer(&vub300->inactivity_timer, jiffies + HZ);
+	} else {
+		vub300_queue_dead_work(vub300);
+		mod_timer(&vub300->inactivity_timer, jiffies + HZ);
+	}
+}
+
+static int vub300_response_error(u8 ErrorCode)
+{
+	switch (ErrorCode) {
+	case SD_ERROR_PIO_TIMEOUT:
+	case SD_ERROR_1BIT_TIMEOUT:
+	case SD_ERROR_4BIT_TIMEOUT:
+		return -ETIMEDOUT;
+	case SD_ERROR_STAT_DATA:
+	case SD_ERROR_OVERRUN:
+	case SD_ERROR_STAT_CMD:
+	case SD_ERROR_STAT_CMD_TIMEOUT:
+	case SD_ERROR_SDCRDY_STUCK:
+	case SD_ERROR_UNHANDLED:
+	case SD_ERROR_1BIT_CRC_WRONG:
+	case SD_ERROR_4BIT_CRC_WRONG:
+	case SD_ERROR_1BIT_CRC_ERROR:
+	case SD_ERROR_4BIT_CRC_ERROR:
+	case SD_ERROR_NO_CMD_ENDBIT:
+	case SD_ERROR_NO_1BIT_DATEND:
+	case SD_ERROR_NO_4BIT_DATEND:
+	case SD_ERROR_1BIT_DATA_TIMEOUT:
+	case SD_ERROR_4BIT_DATA_TIMEOUT:
+	case SD_ERROR_1BIT_UNEXPECTED_TIMEOUT:
+	case SD_ERROR_4BIT_UNEXPECTED_TIMEOUT:
+		return -EILSEQ;
+	case 33:
+		return -EILSEQ;
+	case SD_ERROR_ILLEGAL_COMMAND:
+		return -EINVAL;
+	case SD_ERROR_NO_DEVICE:
+		return -ENOMEDIUM;
+	default:
+		return -ENODEV;
+	}
+}
+
+static void command_res_completed(struct urb *urb)
+{				/* urb completion handler - hardirq */
+	/*
+	 * cmd_mutex is held by vub300_cmndwork_thread
+	 */
+	struct vub300_mmc_host *vub300 = (struct vub300_mmc_host *)urb->context;
+	if (urb->status) {
+		/*
+		 * we have to let the initiator handle the error
+		 */
+	} else if (0 == vub300->command_res_urb->actual_length) {
+		/*
+		 * we have seen this happen once or twice and
+		 * we suspect a buggy USB host controller
+		 */
+	} else if (!vub300->data) {
+		/*
+		 * this means that the command (typically CMD52) suceeded
+		 */
+	} else if (0x02 != vub300->resp.common.HeaderType) {
+		/*
+		 * this is an error response from the VUB300 chip
+		 * and we let the initiator handle it
+		 */
+	} else if (vub300->urb) {
+		vub300->cmd->error =
+		    vub300_response_error(vub300->resp.error.ErrorCode);
+		usb_unlink_urb(vub300->urb);
+	} else {
+		vub300->cmd->error =
+		    vub300_response_error(vub300->resp.error.ErrorCode);
+		usb_sg_cancel(&vub300->sg_request);
+	}
+	complete(&vub300->command_complete);	/* got_response_in */
+}
+
+static void command_out_completed(struct urb *urb)
+{				/* urb completion handler - hardirq */
+	/*
+	 * cmd_mutex is held by vub300_cmndwork_thread
+	 */
+	struct vub300_mmc_host *vub300 = (struct vub300_mmc_host *)urb->context;
+	if (urb->status) {
+		complete(&vub300->command_complete);
+	} else {
+		int ret;
+		unsigned int pipe =
+		    usb_rcvbulkpipe(vub300->udev, vub300->cmnd_res_ep);
+		usb_fill_bulk_urb(vub300->command_res_urb, vub300->udev, pipe,
+				  &vub300->resp, sizeof(vub300->resp),
+				  command_res_completed, vub300);
+		vub300->command_res_urb->actual_length = 0;
+		ret = usb_submit_urb(vub300->command_res_urb, GFP_NOIO);
+		if (0 == ret) {
+			/*
+			 * the urb completion handler will call
+			 * our completion handler
+			 */
+		} else {
+			/*
+			 * and thus we only call it directly
+			 * when it will not be called
+			 */
+			complete(&vub300->command_complete);
+		}
+	}
+}
+
+/*
+ * the STUFF bits are masked out for the comparisons
+ */
+static void snoop_block_size_and_bus_width(struct vub300_mmc_host *vub300,
+					   u32 cmd_arg)
+{
+	if (0x80022200 == (0xFBFFFE00 & cmd_arg))
+		vub300->fbs[1] = (cmd_arg << 8) | (0x00FF & vub300->fbs[1]);
+	else if (0x80022000 == (0xFBFFFE00 & cmd_arg))
+		vub300->fbs[1] = (0xFF & cmd_arg) | (0xFF00 & vub300->fbs[1]);
+	else if (0x80042200 == (0xFBFFFE00 & cmd_arg))
+		vub300->fbs[2] = (cmd_arg << 8) | (0x00FF & vub300->fbs[2]);
+	else if (0x80042000 == (0xFBFFFE00 & cmd_arg))
+		vub300->fbs[2] = (0xFF & cmd_arg) | (0xFF00 & vub300->fbs[2]);
+	else if (0x80062200 == (0xFBFFFE00 & cmd_arg))
+		vub300->fbs[3] = (cmd_arg << 8) | (0x00FF & vub300->fbs[3]);
+	else if (0x80062000 == (0xFBFFFE00 & cmd_arg))
+		vub300->fbs[3] = (0xFF & cmd_arg) | (0xFF00 & vub300->fbs[3]);
+	else if (0x80082200 == (0xFBFFFE00 & cmd_arg))
+		vub300->fbs[4] = (cmd_arg << 8) | (0x00FF & vub300->fbs[4]);
+	else if (0x80082000 == (0xFBFFFE00 & cmd_arg))
+		vub300->fbs[4] = (0xFF & cmd_arg) | (0xFF00 & vub300->fbs[4]);
+	else if (0x800A2200 == (0xFBFFFE00 & cmd_arg))
+		vub300->fbs[5] = (cmd_arg << 8) | (0x00FF & vub300->fbs[5]);
+	else if (0x800A2000 == (0xFBFFFE00 & cmd_arg))
+		vub300->fbs[5] = (0xFF & cmd_arg) | (0xFF00 & vub300->fbs[5]);
+	else if (0x800C2200 == (0xFBFFFE00 & cmd_arg))
+		vub300->fbs[6] = (cmd_arg << 8) | (0x00FF & vub300->fbs[6]);
+	else if (0x800C2000 == (0xFBFFFE00 & cmd_arg))
+		vub300->fbs[6] = (0xFF & cmd_arg) | (0xFF00 & vub300->fbs[6]);
+	else if (0x800E2200 == (0xFBFFFE00 & cmd_arg))
+		vub300->fbs[7] = (cmd_arg << 8) | (0x00FF & vub300->fbs[7]);
+	else if (0x800E2000 == (0xFBFFFE00 & cmd_arg))
+		vub300->fbs[7] = (0xFF & cmd_arg) | (0xFF00 & vub300->fbs[7]);
+	else if (0x80000E00 == (0xFBFFFE03 & cmd_arg))
+		vub300->bus_width = 1;
+	else if (0x80000E02 == (0xFBFFFE03 & cmd_arg))
+		vub300->bus_width = 4;
+}
+
+static void send_command(struct vub300_mmc_host *vub300)
+{
+	/*
+	 * cmd_mutex is held by vub300_cmndwork_thread
+	 */
+	struct mmc_command *cmd = vub300->cmd;
+	struct mmc_data *data = vub300->data;
+	int retval;
+	u8 ResponseType;
+	if (vub300->app_spec) {
+		if (6 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+			if (0x00000000 == (0x00000003 & cmd->arg))
+				vub300->bus_width = 1;
+			else if (0x00000002 == (0x00000003 & cmd->arg))
+				vub300->bus_width = 4;
+			else
+				dev_err(&vub300->udev->dev,
+					"unexpected ACMD6 bus_width=%d",
+					0x00000003 & cmd->arg);
+		} else if (13 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (22 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (23 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (41 == cmd->opcode) {
+			ResponseType = SDRT_3;
+			vub300->resp_len = 6;
+		} else if (42 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (51 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (55 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else {
+			vub300->resp_len = 0;
+			cmd->error = -EINVAL;
+			complete(&vub300->command_complete);
+			return;
+		}
+		vub300->app_spec = 0;
+	} else {
+		if (0 == cmd->opcode) {
+			ResponseType = SDRT_NONE;
+			vub300->resp_len = 0;
+		} else if (1 == cmd->opcode) {
+			ResponseType = SDRT_3;
+			vub300->resp_len = 6;
+		} else if (2 == cmd->opcode) {
+			ResponseType = SDRT_2;
+			vub300->resp_len = 17;
+		} else if (3 == cmd->opcode) {
+			ResponseType = SDRT_6;
+			vub300->resp_len = 6;
+		} else if (4 == cmd->opcode) {
+			ResponseType = SDRT_NONE;
+			vub300->resp_len = 0;
+		} else if (5 == cmd->opcode) {
+			ResponseType = SDRT_4;
+			vub300->resp_len = 6;
+		} else if (6 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (7 == cmd->opcode) {
+			ResponseType = SDRT_1B;
+			vub300->resp_len = 6;
+		} else if (8 == cmd->opcode) {
+			ResponseType = SDRT_7;
+			vub300->resp_len = 6;
+		} else if (9 == cmd->opcode) {
+			ResponseType = SDRT_2;
+			vub300->resp_len = 17;
+		} else if (10 == cmd->opcode) {
+			ResponseType = SDRT_2;
+			vub300->resp_len = 17;
+		} else if (12 == cmd->opcode) {
+			ResponseType = SDRT_1B;
+			vub300->resp_len = 6;
+		} else if (13 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (15 == cmd->opcode) {
+			ResponseType = SDRT_NONE;
+			vub300->resp_len = 0;
+		} else if (16 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+			vub300->fbs[0] = 0xFFFF & cmd->arg;
+			vub300->fbs[1] = 0xFFFF & cmd->arg;
+			vub300->fbs[2] = 0xFFFF & cmd->arg;
+			vub300->fbs[3] = 0xFFFF & cmd->arg;
+			vub300->fbs[4] = 0xFFFF & cmd->arg;
+			vub300->fbs[5] = 0xFFFF & cmd->arg;
+			vub300->fbs[6] = 0xFFFF & cmd->arg;
+			vub300->fbs[7] = 0xFFFF & cmd->arg;
+		} else if (17 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (18 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (24 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (25 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (27 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (28 == cmd->opcode) {
+			ResponseType = SDRT_1B;
+			vub300->resp_len = 6;
+		} else if (29 == cmd->opcode) {
+			ResponseType = SDRT_1B;
+			vub300->resp_len = 6;
+		} else if (30 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (32 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (33 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (38 == cmd->opcode) {
+			ResponseType = SDRT_1B;
+			vub300->resp_len = 6;
+		} else if (42 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (52 == cmd->opcode) {
+			ResponseType = SDRT_5;
+			vub300->resp_len = 6;
+			snoop_block_size_and_bus_width(vub300, cmd->arg);
+		} else if (53 == cmd->opcode) {
+			ResponseType = SDRT_5;
+			vub300->resp_len = 6;
+		} else if (55 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+			vub300->app_spec = 1;
+		} else if (56 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else {
+			vub300->resp_len = 0;
+			cmd->error = -EINVAL;
+			complete(&vub300->command_complete);
+			return;
+		}
+	}
+	/*
+	 * it is a shame that we can not use "sizeof(struct SD_Command_Header)"
+	 * this is because the packet _must_ be padded to 64 bytes
+	 */
+	vub300->cmnd.head.HeaderSize = 20;
+	vub300->cmnd.head.HeaderType = 0x00;
+	vub300->cmnd.head.PortNumber = 0;	/* "0" means port 1 */
+	vub300->cmnd.head.Command_Type = 0x00;	/* standard read command */
+	vub300->cmnd.head.ResponseType = ResponseType;
+	vub300->cmnd.head.CommandIndex = cmd->opcode;
+	vub300->cmnd.head.Arguments[0] = cmd->arg >> 24;
+	vub300->cmnd.head.Arguments[1] = cmd->arg >> 16;
+	vub300->cmnd.head.Arguments[2] = cmd->arg >> 8;
+	vub300->cmnd.head.Arguments[3] = cmd->arg >> 0;
+	if (52 == cmd->opcode) {
+		int fn = 0x7 & (cmd->arg >> 28);
+		vub300->cmnd.head.BlockCount[0] = 0;
+		vub300->cmnd.head.BlockCount[1] = 0;
+		vub300->cmnd.head.BlockSize[0] = (vub300->fbs[fn] >> 8) & 0xFF;
+		vub300->cmnd.head.BlockSize[1] = (vub300->fbs[fn] >> 0) & 0xFF;
+		vub300->cmnd.head.Command_Type = 0x00;
+		vub300->cmnd.head.TransferSize[0] = 0;
+		vub300->cmnd.head.TransferSize[1] = 0;
+		vub300->cmnd.head.TransferSize[2] = 0;
+		vub300->cmnd.head.TransferSize[3] = 0;
+	} else if (!data) {
+		vub300->cmnd.head.BlockCount[0] = 0;
+		vub300->cmnd.head.BlockCount[1] = 0;
+		vub300->cmnd.head.BlockSize[0] = (vub300->fbs[0] >> 8) & 0xFF;
+		vub300->cmnd.head.BlockSize[1] = (vub300->fbs[0] >> 0) & 0xFF;
+		vub300->cmnd.head.Command_Type = 0x00;
+		vub300->cmnd.head.TransferSize[0] = 0;
+		vub300->cmnd.head.TransferSize[1] = 0;
+		vub300->cmnd.head.TransferSize[2] = 0;
+		vub300->cmnd.head.TransferSize[3] = 0;
+	} else if (53 == cmd->opcode) {
+		int fn = 0x7 & (cmd->arg >> 28);
+		if (0x08 & vub300->cmnd.head.Arguments[0]) {	/* BLOCK MODE */
+			vub300->cmnd.head.BlockCount[0] =
+			    (data->blocks >> 8) & 0xFF;
+			vub300->cmnd.head.BlockCount[1] =
+			    (data->blocks >> 0) & 0xFF;
+			vub300->cmnd.head.BlockSize[0] =
+			    (data->blksz >> 8) & 0xFF;
+			vub300->cmnd.head.BlockSize[1] =
+			    (data->blksz >> 0) & 0xFF;
+		} else {	/* BYTE MODE */
+			vub300->cmnd.head.BlockCount[0] = 0;
+			vub300->cmnd.head.BlockCount[1] = 0;
+			vub300->cmnd.head.BlockSize[0] =
+			    (vub300->datasize >> 8) & 0xFF;
+			vub300->cmnd.head.BlockSize[1] =
+			    (vub300->datasize >> 0) & 0xFF;
+		}
+		vub300->cmnd.head.Command_Type =
+		    (MMC_DATA_READ & data->flags) ? 0x00 : 0x80;
+		vub300->cmnd.head.TransferSize[0] =
+		    (vub300->datasize >> 24) & 0xFF;
+		vub300->cmnd.head.TransferSize[1] =
+		    (vub300->datasize >> 16) & 0xFF;
+		vub300->cmnd.head.TransferSize[2] =
+		    (vub300->datasize >> 8) & 0xFF;
+		vub300->cmnd.head.TransferSize[3] =
+		    (vub300->datasize >> 0) & 0xFF;
+		if (vub300->datasize < vub300->fbs[fn]) {
+			vub300->cmnd.head.BlockCount[0] = 0;
+			vub300->cmnd.head.BlockCount[1] = 0;
+		}
+	} else {
+		vub300->cmnd.head.BlockCount[0] = (data->blocks >> 8) & 0xFF;
+		vub300->cmnd.head.BlockCount[1] = (data->blocks >> 0) & 0xFF;
+		vub300->cmnd.head.BlockSize[0] = (data->blksz >> 8) & 0xFF;
+		vub300->cmnd.head.BlockSize[1] = (data->blksz >> 0) & 0xFF;
+		vub300->cmnd.head.Command_Type =
+		    (MMC_DATA_READ & data->flags) ? 0x00 : 0x80;
+		vub300->cmnd.head.TransferSize[0] =
+		    (vub300->datasize >> 24) & 0xFF;
+		vub300->cmnd.head.TransferSize[1] =
+		    (vub300->datasize >> 16) & 0xFF;
+		vub300->cmnd.head.TransferSize[2] =
+		    (vub300->datasize >> 8) & 0xFF;
+		vub300->cmnd.head.TransferSize[3] =
+		    (vub300->datasize >> 0) & 0xFF;
+		if (vub300->datasize < vub300->fbs[0]) {
+			vub300->cmnd.head.BlockCount[0] = 0;
+			vub300->cmnd.head.BlockCount[1] = 0;
+		}
+	}
+	if (vub300->cmnd.head.BlockSize[0] || vub300->cmnd.head.BlockSize[1]) {
+		u16 BlockSize = vub300->cmnd.head.BlockSize[1]
+		    | (vub300->cmnd.head.BlockSize[0] << 8);
+		u16 BlockBoundary =
+		    FIRMWARE_BLOCK_BOUNDARY -
+		    (FIRMWARE_BLOCK_BOUNDARY % BlockSize);
+		vub300->cmnd.head.BlockBoundary[0] =
+		    (BlockBoundary >> 8) & 0xFF;
+		vub300->cmnd.head.BlockBoundary[1] =
+		    (BlockBoundary >> 0) & 0xFF;
+	} else {
+		vub300->cmnd.head.BlockBoundary[0] = 0;
+		vub300->cmnd.head.BlockBoundary[1] = 0;
+	}
+	usb_fill_bulk_urb(vub300->command_out_urb, vub300->udev,
+			  usb_sndbulkpipe(vub300->udev, vub300->cmnd_out_ep),
+			  &vub300->cmnd, sizeof(vub300->cmnd),
+			  command_out_completed, vub300);
+	retval = usb_submit_urb(vub300->command_out_urb, GFP_ATOMIC);
+	if (0 > retval) {
+		cmd->error = retval;
+		complete(&vub300->command_complete);
+		return;
+	} else {
+		return;
+	}
+}
+
+/*
+ * timer callback runs in atomic mode
+ *       so it cannot call usb_kill_urb()
+ */
+static void vub300_sg_timed_out(unsigned long data)
+{
+	struct vub300_mmc_host *vub300 = (struct vub300_mmc_host *)data;
+	vub300->usb_timed_out = 1;
+	usb_sg_cancel(&vub300->sg_request);
+	usb_unlink_urb(vub300->command_out_urb);
+	usb_unlink_urb(vub300->command_res_urb);
+}
+
+static u16 roundup_to_multiple_of_64(u16 number)
+{
+	return 0xFFC0 & (0x3F + number);
+}
+
+/*
+ * this is a separate function to solve the 80 column width restriction
+ */
+static void __download_offload_pseudocode(struct vub300_mmc_host *vub300,
+					  const struct firmware *fw)
+{
+	u8 register_count = 0;
+	u16 transfer_size = 0;
+	u16 interrupt_size = 0;
+	const u8 *data = fw->data;
+	int size = fw->size;
+	u8 c;
+	dev_info(&vub300->udev->dev, "using %s for SDIO offload processing",
+		 vub300->vub_name);
+	do {
+		c = *data++;
+	} while (size-- && c);	/* skip comment */
+	dev_info(&vub300->udev->dev, "using offload firmware %s %s", fw->data,
+		 vub300->vub_name);
+	if (size < 4) {
+		dev_err(&vub300->udev->dev,
+			"corrupt offload pseudocode in firmware %s\n",
+			vub300->vub_name);
+		strncpy(vub300->vub_name, "corrupt offload pseudocode",
+			sizeof(vub300->vub_name));
+		return;
+	}
+	interrupt_size += *data++;
+	size -= 1;
+	interrupt_size <<= 8;
+	interrupt_size += *data++;
+	size -= 1;
+	if (interrupt_size < size) {
+		u16 xfer_length = roundup_to_multiple_of_64(interrupt_size);
+		u8 *xfer_buffer = kmalloc(xfer_length, GFP_KERNEL);
+		if (xfer_buffer) {
+			int retval;
+			memcpy(xfer_buffer, data, interrupt_size);
+			memset(xfer_buffer + interrupt_size, 0,
+			       xfer_length - interrupt_size);
+			size -= interrupt_size;
+			data += interrupt_size;
+			retval =
+			    usb_control_msg(vub300->udev,
+					    usb_sndctrlpipe(vub300->udev, 0),
+					    SET_INTERRUPT_PSEUDOCODE,
+					    USB_DIR_OUT | USB_TYPE_VENDOR |
+					    USB_RECIP_DEVICE, 0x0000, 0x0000,
+					    xfer_buffer, xfer_length, HZ);
+			kfree(xfer_buffer);
+			if (retval < 0) {
+				strncpy(vub300->vub_name,
+					"SDIO pseudocode download failed",
+					sizeof(vub300->vub_name));
+				return;
+			}
+		} else {
+			dev_err(&vub300->udev->dev,
+				"not enough memory for xfer buffer to send"
+				" INTERRUPT_PSEUDOCODE for %s %s\n", fw->data,
+				vub300->vub_name);
+			strncpy(vub300->vub_name,
+				"SDIO interrupt pseudocode download failed",
+				sizeof(vub300->vub_name));
+			return;
+		}
+	} else {
+		dev_err(&vub300->udev->dev,
+			"corrupt interrupt pseudocode in firmware %s %s\n",
+			fw->data, vub300->vub_name);
+		strncpy(vub300->vub_name, "corrupt interrupt pseudocode",
+			sizeof(vub300->vub_name));
+		return;
+	}
+	transfer_size += *data++;
+	size -= 1;
+	transfer_size <<= 8;
+	transfer_size += *data++;
+	size -= 1;
+	if (transfer_size < size) {
+		u16 xfer_length = roundup_to_multiple_of_64(transfer_size);
+		u8 *xfer_buffer = kmalloc(xfer_length, GFP_KERNEL);
+		if (xfer_buffer) {
+			int retval;
+			memcpy(xfer_buffer, data, transfer_size);
+			memset(xfer_buffer + transfer_size, 0,
+			       xfer_length - transfer_size);
+			size -= transfer_size;
+			data += transfer_size;
+			retval =
+			    usb_control_msg(vub300->udev,
+					    usb_sndctrlpipe(vub300->udev, 0),
+					    SET_TRANSFER_PSEUDOCODE,
+					    USB_DIR_OUT | USB_TYPE_VENDOR |
+					    USB_RECIP_DEVICE, 0x0000, 0x0000,
+					    xfer_buffer, xfer_length, HZ);
+			kfree(xfer_buffer);
+			if (retval < 0) {
+				strncpy(vub300->vub_name,
+					"SDIO pseudocode download failed",
+					sizeof(vub300->vub_name));
+				return;
+			}
+		} else {
+			dev_err(&vub300->udev->dev,
+				"not enough memory for xfer buffer to send"
+				" TRANSFER_PSEUDOCODE for %s %s\n", fw->data,
+				vub300->vub_name);
+			strncpy(vub300->vub_name,
+				"SDIO transfer pseudocode download failed",
+				sizeof(vub300->vub_name));
+			return;
+		}
+	} else {
+		dev_err(&vub300->udev->dev,
+			"corrupt transfer pseudocode in firmware %s %s\n",
+			fw->data, vub300->vub_name);
+		strncpy(vub300->vub_name, "corrupt transfer pseudocode",
+			sizeof(vub300->vub_name));
+		return;
+	}
+	register_count += *data++;
+	size -= 1;
+	if (register_count * 4 == size) {
+		int I = vub300->dynamic_register_count = register_count;
+		int i = 0;
+		while (I--) {
+			unsigned int func_num = 0;
+			vub300->sdio_register[i].func_num = *data++;
+			size -= 1;
+			func_num += *data++;
+			size -= 1;
+			func_num <<= 8;
+			func_num += *data++;
+			size -= 1;
+			func_num <<= 8;
+			func_num += *data++;
+			size -= 1;
+			vub300->sdio_register[i].sdio_reg = func_num;
+			vub300->sdio_register[i].activate = 1;
+			vub300->sdio_register[i].prepared = 0;
+			i += 1;
+		}
+		dev_info(&vub300->udev->dev,
+			 "initialized %d dynamic pseudocode registers",
+			 vub300->dynamic_register_count);
+		return;
+	} else {
+		dev_err(&vub300->udev->dev,
+			"corrupt dynamic registers in firmware %s\n",
+			vub300->vub_name);
+		strncpy(vub300->vub_name, "corrupt dynamic registers",
+			sizeof(vub300->vub_name));
+		return;
+	}
+}
+
+/*
+ * if the binary containing the EMPTY PseudoCode can not be found
+ * vub300->vub_name is set anyway in order to prevent an automatic retry
+ */
+static void download_offload_pseudocode(struct vub300_mmc_host *vub300)
+{
+	struct mmc_card *card = vub300->mmc->card;
+	int sdio_funcs = card->sdio_funcs;
+	const struct firmware *fw = NULL;
+	int l = snprintf(vub300->vub_name, sizeof(vub300->vub_name),
+			 "vub_%04X%04X", card->cis.vendor, card->cis.device);
+	int N = sdio_funcs;
+	int n = 0;
+	int retval;
+	while (N--) {
+		struct sdio_func *sf = card->sdio_func[n++];
+		l += snprintf(vub300->vub_name + l,
+			      sizeof(vub300->vub_name) - l, "_%04X%04X",
+			      sf->vendor, sf->device);
+	};
+	snprintf(vub300->vub_name + l, sizeof(vub300->vub_name) - l, ".bin");
+	dev_info(&vub300->udev->dev, "requesting offload firmware %s",
+		 vub300->vub_name);
+	retval = request_firmware(&fw, vub300->vub_name, &card->dev);
+	if (0 > retval) {
+		strncpy(vub300->vub_name, "vub_default.bin",
+			sizeof(vub300->vub_name));
+		retval = request_firmware(&fw, vub300->vub_name, &card->dev);
+		if (0 > retval) {
+			strncpy(vub300->vub_name,
+				"no SDIO offload firmware found",
+				sizeof(vub300->vub_name));
+		} else {
+			__download_offload_pseudocode(vub300, fw);
+			release_firmware(fw);
+		}
+	} else {
+		__download_offload_pseudocode(vub300, fw);
+		release_firmware(fw);
+	}
+}
+
+static void vub300_usb_bulk_msg_completion(struct urb *urb)
+{				/* urb completion handler - hardirq */
+	complete((struct completion *)urb->context);
+}
+
+static int vub300_usb_bulk_msg(struct vub300_mmc_host *vub300,
+			       unsigned int pipe, void *data, int len,
+			       int *actual_length, int timeout_msecs)
+{
+	/*
+	 * cmd_mutex is held by vub300_cmndwork_thread
+	 */
+	struct usb_device *usb_dev = vub300->udev;
+	struct completion done;
+	int retval;
+	vub300->urb = usb_alloc_urb(0, GFP_KERNEL);
+	if (!vub300->urb)
+		return -ENOMEM;
+	usb_fill_bulk_urb(vub300->urb, usb_dev, pipe, data, len,
+			  vub300_usb_bulk_msg_completion, NULL);
+	init_completion(&done);
+	vub300->urb->context = &done;
+	vub300->urb->actual_length = 0;
+	retval = usb_submit_urb(vub300->urb, GFP_NOIO);
+	if (unlikely(retval))
+		goto out;
+	if (!wait_for_completion_timeout
+	    (&done, msecs_to_jiffies(timeout_msecs))) {
+		retval = -ETIMEDOUT;
+		usb_kill_urb(vub300->urb);
+	} else {
+		retval = vub300->urb->status;
+	}
+out:
+	*actual_length = vub300->urb->actual_length;
+	usb_free_urb(vub300->urb);
+	vub300->urb = NULL;
+	return retval;
+}
+
+static int __command_read_data(struct vub300_mmc_host *vub300,
+			       struct mmc_command *cmd, struct mmc_data *data)
+{
+	/*
+	 * cmd_mutex is held by vub300_cmndwork_thread
+	 */
+	int linear_length = vub300->datasize;
+	int padded_length = vub300->large_usb_packets ?
+	    ((511 + linear_length) >> 9) << 9 :
+	    ((63 + linear_length) >> 6) << 6;
+	if ((padded_length == linear_length) || !pad_input_to_usb_pkt) {
+		int result;
+		unsigned pipe;
+		pipe = usb_rcvbulkpipe(vub300->udev, vub300->data_inp_ep);
+		result = usb_sg_init(&vub300->sg_request, vub300->udev,
+				     pipe, 0, data->sg,
+				     data->sg_len, 0, GFP_NOIO);
+		if (0 > result) {
+			usb_unlink_urb(vub300->command_out_urb);
+			usb_unlink_urb(vub300->command_res_urb);
+			cmd->error = result;
+			data->bytes_xfered = 0;
+			return 0;
+		} else {
+			vub300->sg_transfer_timer.expires =
+			    jiffies + msecs_to_jiffies(2000 +
+						       (linear_length / 16384));
+			add_timer(&vub300->sg_transfer_timer);
+			usb_sg_wait(&vub300->sg_request);
+			del_timer(&vub300->sg_transfer_timer);
+			if (0 > vub300->sg_request.status) {
+				cmd->error = vub300->sg_request.status;
+				data->bytes_xfered = 0;
+				return 0;
+			} else {
+				data->bytes_xfered = vub300->datasize;
+				return linear_length;
+			}
+		}
+	} else {
+		u8 *buf = kmalloc(padded_length, GFP_KERNEL);
+		if (buf) {
+			int result;
+			unsigned pipe =
+			    usb_rcvbulkpipe(vub300->udev, vub300->data_inp_ep);
+			int actual_length = 0;
+			result =
+			    vub300_usb_bulk_msg(vub300, pipe, buf,
+						padded_length, &actual_length,
+						2000 + (padded_length / 16384));
+			if (0 > result) {
+				cmd->error = result;
+				data->bytes_xfered = 0;
+				kfree(buf);
+				return 0;
+			} else if (actual_length < linear_length) {
+				cmd->error = -EREMOTEIO;
+				data->bytes_xfered = 0;
+				kfree(buf);
+				return 0;
+			} else {
+				sg_copy_from_buffer(data->sg, data->sg_len, buf,
+						    linear_length);
+				kfree(buf);
+				data->bytes_xfered = vub300->datasize;
+				return linear_length;
+			}
+		} else {
+			cmd->error = -ENOMEM;
+			data->bytes_xfered = 0;
+			return 0;
+		}
+	}
+}
+
+static int __command_write_data(struct vub300_mmc_host *vub300,
+				struct mmc_command *cmd, struct mmc_data *data)
+{
+	/*
+	 * cmd_mutex is held by vub300_cmndwork_thread
+	 */
+	unsigned pipe = usb_sndbulkpipe(vub300->udev, vub300->data_out_ep);
+	int linear_length = vub300->datasize;
+	int modulo_64_length = linear_length & 0x003F;
+	int modulo_512_length = linear_length & 0x01FF;
+	if (64 > linear_length) {
+		int result;
+		int actual_length;
+		sg_copy_to_buffer(data->sg, data->sg_len,
+				  vub300->padded_buffer,
+				  sizeof(vub300->padded_buffer));
+		memset(vub300->padded_buffer + linear_length, 0,
+		       sizeof(vub300->padded_buffer) - linear_length);
+		result =
+		    vub300_usb_bulk_msg(vub300, pipe, vub300->padded_buffer,
+					sizeof(vub300->padded_buffer),
+					&actual_length,
+					2000 +
+					(sizeof(vub300->padded_buffer) /
+					 16384));
+		if (0 > result) {
+			cmd->error = result;
+			data->bytes_xfered = 0;
+		} else {
+			data->bytes_xfered = vub300->datasize;
+		}
+	} else if ((!vub300->large_usb_packets && (0 < modulo_64_length))
+		   || (vub300->large_usb_packets && (64 > modulo_512_length))
+	    ) {			/* don't you just love these work-rounds */
+		int padded_length = ((63 + linear_length) >> 6) << 6;
+		u8 *buf = kmalloc(padded_length, GFP_KERNEL);
+		if (buf) {
+			int result;
+			int actual_length;
+			sg_copy_to_buffer(data->sg, data->sg_len, buf,
+					  padded_length);
+			memset(buf + linear_length, 0,
+			       padded_length - linear_length);
+			result =
+			    vub300_usb_bulk_msg(vub300, pipe, buf,
+						padded_length, &actual_length,
+						2000 + padded_length / 16384);
+			kfree(buf);
+			if (0 > result) {
+				cmd->error = result;
+				data->bytes_xfered = 0;
+			} else {
+				data->bytes_xfered = vub300->datasize;
+			}
+		} else {
+			cmd->error = -ENOMEM;
+			data->bytes_xfered = 0;
+		}
+	} else {		/* no data padding required */
+		int result;
+		unsigned char buf[64 * 4];
+		sg_copy_to_buffer(data->sg, data->sg_len, buf, sizeof(buf));
+		result = usb_sg_init(&vub300->sg_request, vub300->udev,
+				     pipe, 0, data->sg,
+				     data->sg_len, 0, GFP_NOIO);
+		if (0 > result) {
+			usb_unlink_urb(vub300->command_out_urb);
+			usb_unlink_urb(vub300->command_res_urb);
+			cmd->error = result;
+			data->bytes_xfered = 0;
+		} else {
+			vub300->sg_transfer_timer.expires =
+			    jiffies + msecs_to_jiffies(2000 +
+						       linear_length / 16384);
+			add_timer(&vub300->sg_transfer_timer);
+			usb_sg_wait(&vub300->sg_request);
+			if (cmd->error) {
+				data->bytes_xfered = 0;
+			} else {
+				del_timer(&vub300->sg_transfer_timer);
+				if (0 > vub300->sg_request.status) {
+					cmd->error = vub300->sg_request.status;
+					data->bytes_xfered = 0;
+				} else {
+					data->bytes_xfered = vub300->datasize;
+				}
+			}
+		}
+	}
+	return linear_length;
+}
+
+static void __vub300_command_response(struct vub300_mmc_host *vub300,
+				      struct mmc_command *cmd,
+				      struct mmc_data *data, int data_length)
+{
+	/*
+	 * cmd_mutex is held by vub300_cmndwork_thread
+	 */
+	long respretval;
+	int msec_timeout = 1000 + data_length / 4;
+	respretval =
+	    wait_for_completion_timeout(&vub300->command_complete,
+					msecs_to_jiffies(msec_timeout));
+	if (0 == respretval) {	/* TIMED OUT */
+		/*
+		 * we don't know which of "out" and "res" if any failed
+		 */
+		int result;
+		vub300->usb_timed_out = 1;
+		usb_kill_urb(vub300->command_out_urb);
+		usb_kill_urb(vub300->command_res_urb);
+		cmd->error = -ETIMEDOUT;
+		result =
+		    usb_lock_device_for_reset(vub300->udev, vub300->interface);
+		if (result == 0) {
+			result = usb_reset_device(vub300->udev);
+			usb_unlock_device(vub300->udev);
+		}
+	} else if (0 > respretval) {
+		/*
+		 * we don't know which of "out" and "res" if any failed
+		 */
+		usb_kill_urb(vub300->command_out_urb);
+		usb_kill_urb(vub300->command_res_urb);
+		cmd->error = respretval;
+	} else if (cmd->error) {
+		/*
+		 * the error occured sending the command
+		 * or recieving the response
+		 */
+	} else if (vub300->command_out_urb->status) {
+		vub300->usb_transport_fail = vub300->command_out_urb->status;
+		cmd->error = -EPROTO == vub300->command_out_urb->status ?
+		    -ESHUTDOWN : vub300->command_out_urb->status;
+	} else if (vub300->command_res_urb->status) {
+		vub300->usb_transport_fail = vub300->command_res_urb->status;
+		cmd->error = -EPROTO == vub300->command_res_urb->status ?
+		    -ESHUTDOWN : vub300->command_res_urb->status;
+	} else if (0x00 == vub300->resp.common.HeaderType) {
+		/*
+		 * the command completed successfully
+		 * and there was no piggybacked data
+		 */
+	} else if (RESPONSE_ERROR == vub300->resp.common.HeaderType) {
+		cmd->error =
+		    vub300_response_error(vub300->resp.error.ErrorCode);
+		if (vub300->data)
+			usb_sg_cancel(&vub300->sg_request);
+	} else if (RESPONSE_PIGGYBACKED == vub300->resp.common.HeaderType) {
+		int offloaded_data_length =
+		    vub300->resp.common.HeaderSize -
+		    sizeof(struct SD_Register_Header);
+		int register_count = offloaded_data_length >> 3;
+		int ri = 0;
+		while (register_count--) {
+			add_offloaded_reg(vub300, &vub300->resp.pig.reg[ri]);
+			ri += 1;
+		}
+		vub300->resp.common.HeaderSize =
+		    sizeof(struct SD_Register_Header);
+		vub300->resp.common.HeaderType = 0x00;
+		cmd->error = 0;
+	} else if (RESPONSE_PIG_DISABLED == vub300->resp.common.HeaderType) {
+		int offloaded_data_length =
+		    vub300->resp.common.HeaderSize -
+		    sizeof(struct SD_Register_Header);
+		int register_count = offloaded_data_length >> 3;
+		int ri = 0;
+		while (register_count--) {
+			add_offloaded_reg(vub300, &vub300->resp.pig.reg[ri]);
+			ri += 1;
+		}
+		mutex_lock(&vub300->irq_mutex);
+		if (vub300->irqs_queued) {
+			vub300->irqs_queued += 1;
+		} else if (vub300->irq_enabled) {
+			vub300->irqs_queued += 1;
+			vub300_queue_poll_work(vub300, 0);
+		} else {
+			vub300->irqs_queued += 1;
+		}
+		vub300->irq_disabled = 1;
+		mutex_unlock(&vub300->irq_mutex);
+		vub300->resp.common.HeaderSize =
+		    sizeof(struct SD_Register_Header);
+		vub300->resp.common.HeaderType = 0x00;
+		cmd->error = 0;
+	} else if (RESPONSE_PIG_ENABLED == vub300->resp.common.HeaderType) {
+		int offloaded_data_length =
+		    vub300->resp.common.HeaderSize -
+		    sizeof(struct SD_Register_Header);
+		int register_count = offloaded_data_length >> 3;
+		int ri = 0;
+		while (register_count--) {
+			add_offloaded_reg(vub300, &vub300->resp.pig.reg[ri]);
+			ri += 1;
+		}
+		mutex_lock(&vub300->irq_mutex);
+		if (vub300->irqs_queued) {
+			vub300->irqs_queued += 1;
+		} else if (vub300->irq_enabled) {
+			vub300->irqs_queued += 1;
+			vub300_queue_poll_work(vub300, 0);
+		} else {
+			vub300->irqs_queued += 1;
+		}
+		vub300->irq_disabled = 0;
+		mutex_unlock(&vub300->irq_mutex);
+		vub300->resp.common.HeaderSize =
+		    sizeof(struct SD_Register_Header);
+		vub300->resp.common.HeaderType = 0x00;
+		cmd->error = 0;
+	} else {
+		cmd->error = -EINVAL;
+	}
+}
+
+static void construct_request_response(struct vub300_mmc_host *vub300,
+				       struct mmc_command *cmd)
+{
+	int resp_len = vub300->resp_len;
+	int less_cmd = (17 == resp_len) ? resp_len : resp_len - 1;
+	int bytes = 3 & less_cmd;
+	int words = less_cmd >> 2;
+	u8 *r = vub300->resp.response.CommandResponse;
+	if (3 == bytes) {
+		cmd->resp[words] = (r[1 + (words << 2)] << 24)
+		    | (r[2 + (words << 2)] << 16)
+		    | (r[3 + (words << 2)] << 8);
+	} else if (2 == bytes) {
+		cmd->resp[words] = (r[1 + (words << 2)] << 24)
+		    | (r[2 + (words << 2)] << 16);
+	} else if (1 == bytes) {
+		cmd->resp[words] = (r[1 + (words << 2)] << 24);
+	}
+	while (words-- > 0) {
+		cmd->resp[words] = (r[1 + (words << 2)] << 24)
+		    | (r[2 + (words << 2)] << 16)
+		    | (r[3 + (words << 2)] << 8)
+		    | (r[4 + (words << 2)] << 0);
+	}
+	if ((53 == cmd->opcode) && (0x000000FF & cmd->resp[0]))
+		cmd->resp[0] &= 0xFFFFFF00;
+}
+
+/*
+ * this thread runs only when there
+ * is an upper level command req outstanding
+ */
+static void vub300_cmndwork_thread(struct work_struct *work)
+{
+	struct vub300_mmc_host *vub300 =
+	    container_of(work, struct vub300_mmc_host, cmndwork);
+	if (!vub300->interface) {
+		kref_put(&vub300->kref, vub300_delete);
+		return;
+	} else {
+		struct mmc_request *req = vub300->req;
+		struct mmc_command *cmd = vub300->cmd;
+		struct mmc_data *data = vub300->data;
+		int data_length;
+		mutex_lock(&vub300->cmd_mutex);
+		init_completion(&vub300->command_complete);
+		if (likely(vub300->vub_name[0])
+		    || !vub300->mmc->card
+		    || !mmc_card_present(vub300->mmc->card)) {
+			/*
+			 * the name of the EMPTY Pseudo firmware file
+			 * is used as a flag to indicate that the file
+			 * has been already downloaded to the VUB300 chip
+			 */
+		} else if (0 == vub300->mmc->card->sdio_funcs) {
+			strncpy(vub300->vub_name, "SD memory device",
+				sizeof(vub300->vub_name));
+		} else {
+			download_offload_pseudocode(vub300);
+		}
+		send_command(vub300);
+		if (!data)
+			data_length = 0;
+		else if (MMC_DATA_READ & data->flags)
+			data_length = __command_read_data(vub300, cmd, data);
+		else
+			data_length = __command_write_data(vub300, cmd, data);
+		__vub300_command_response(vub300, cmd, data, data_length);
+		vub300->req = NULL;
+		vub300->cmd = NULL;
+		vub300->data = NULL;
+		if (cmd->error) {
+			if (-ENOMEDIUM == cmd->error)
+				check_vub300_port_status(vub300);
+			mutex_unlock(&vub300->cmd_mutex);
+			mmc_request_done(vub300->mmc, req);
+			kref_put(&vub300->kref, vub300_delete);
+			return;
+		} else {
+			construct_request_response(vub300, cmd);
+			vub300->resp_len = 0;
+			mutex_unlock(&vub300->cmd_mutex);
+			kref_put(&vub300->kref, vub300_delete);
+			mmc_request_done(vub300->mmc, req);
+			return;
+		}
+	}
+}
+
+static int examine_cyclic_buffer(struct vub300_mmc_host *vub300,
+				 struct mmc_command *cmd, u8 Function)
+{
+	/*
+	 * cmd_mutex is held by vub300_mmc_request
+	 */
+	u8 cmd0 = 0xFF & (cmd->arg >> 24);
+	u8 cmd1 = 0xFF & (cmd->arg >> 16);
+	u8 cmd2 = 0xFF & (cmd->arg >> 8);
+	u8 cmd3 = 0xFF & (cmd->arg >> 0);
+	int first = MAXREGMASK & vub300->fn[Function].offload_point;
+	struct Offload_Registers_Access *rf = &vub300->fn[Function].reg[first];
+	if (cmd0 == rf->Command_Byte[0]
+	    && cmd1 == rf->Command_Byte[1]
+	    && cmd2 == rf->Command_Byte[2]
+	    && cmd3 == rf->Command_Byte[3]
+	    ) {
+		u8 checksum = 0x00;
+		cmd->resp[1] = checksum << 24;
+		cmd->resp[0] = (rf->Respond_Byte[0] << 24)
+		    | (rf->Respond_Byte[1] << 16)
+		    | (rf->Respond_Byte[2] << 8)
+		    | (rf->Respond_Byte[3] << 0);
+		vub300->fn[Function].offload_point += 1;
+		vub300->fn[Function].offload_count -= 1;
+		vub300->total_offload_count -= 1;
+		return 1;
+	} else {
+		int delta = 1;	/* because it does not match the first one */
+		u8 register_count = vub300->fn[Function].offload_count - 1;
+		u32 register_point = vub300->fn[Function].offload_point + 1;
+		while (0 < register_count) {
+			int point = MAXREGMASK & register_point;
+			struct Offload_Registers_Access *r =
+			    &vub300->fn[Function].reg[point];
+			if (cmd0 == r->Command_Byte[0]
+			    && cmd1 == r->Command_Byte[1]
+			    && cmd2 == r->Command_Byte[2]
+			    && cmd3 == r->Command_Byte[3]
+			    ) {
+				u8 checksum = 0x00;
+				cmd->resp[1] = checksum << 24;
+				cmd->resp[0] = (r->Respond_Byte[0] << 24)
+				    | (r->Respond_Byte[1] << 16)
+				    | (r->Respond_Byte[2] << 8)
+				    | (r->Respond_Byte[3] << 0);
+				vub300->fn[Function].offload_point += delta;
+				vub300->fn[Function].offload_count -= delta;
+				vub300->total_offload_count -= delta;
+				return 1;
+			} else {
+				register_point += 1;
+				register_count -= 1;
+				delta += 1;
+				continue;
+			}
+		}
+		return 0;
+	}
+}
+
+static int satisfy_request_from_offloaded_data(struct vub300_mmc_host *vub300,
+					       struct mmc_command *cmd)
+{
+	/*
+	 * cmd_mutex is held by vub300_mmc_request
+	 */
+	u8 regs = vub300->dynamic_register_count;
+	u8 i = 0;
+	u8 Function = FUN(cmd);
+	u32 Register = REG(cmd);
+	while (0 < regs--) {
+		if ((vub300->sdio_register[i].func_num == Function)
+		    && (vub300->sdio_register[i].sdio_reg == Register)
+		    ) {
+			if (!vub300->sdio_register[i].prepared) {
+				return 0;
+			} else if (0x80000000 == (0x80000000 & cmd->arg)) {
+				/*
+				 * a write to a dynamic register
+				 * nullifies our offloaded value
+				 */
+				vub300->sdio_register[i].prepared = 0;
+				return 0;
+			} else {
+				u8 checksum = 0x00;
+				u8 rsp0 = 0x00;
+				u8 rsp1 = 0x00;
+				u8 rsp2 = vub300->sdio_register[i].response;
+				u8 rsp3 = vub300->sdio_register[i].regvalue;
+				vub300->sdio_register[i].prepared = 0;
+				cmd->resp[1] = checksum << 24;
+				cmd->resp[0] = (rsp0 << 24)
+				    | (rsp1 << 16)
+				    | (rsp2 << 8)
+				    | (rsp3 << 0);
+				return 1;
+			}
+		} else {
+			i += 1;
+			continue;
+		}
+	};
+	if (0 == vub300->total_offload_count)
+		return 0;
+	else if (0 == vub300->fn[Function].offload_count)
+		return 0;
+	else
+		return examine_cyclic_buffer(vub300, cmd, Function);
+}
+
+static void vub300_mmc_request(struct mmc_host *mmc, struct mmc_request *req)
+{				/* NOT irq */
+	struct mmc_command *cmd = req->cmd;
+	struct vub300_mmc_host *vub300 = mmc_priv(mmc);
+	if (!vub300->interface) {
+		cmd->error = -ESHUTDOWN;
+		mmc_request_done(mmc, req);
+		return;
+	} else {
+		struct mmc_data *data = req->data;
+		if (!vub300->card_powered) {
+			cmd->error = -ENOMEDIUM;
+			mmc_request_done(mmc, req);
+			return;
+		}
+		if (!vub300->card_present) {
+			cmd->error = -ENOMEDIUM;
+			mmc_request_done(mmc, req);
+			return;
+		}
+		if (vub300->usb_transport_fail) {
+			cmd->error = vub300->usb_transport_fail;
+			mmc_request_done(mmc, req);
+			return;
+		}
+		if (!vub300->interface) {
+			cmd->error = -ENODEV;
+			mmc_request_done(mmc, req);
+			return;
+		}
+		kref_get(&vub300->kref);
+		mutex_lock(&vub300->cmd_mutex);
+		mod_timer(&vub300->inactivity_timer, jiffies + HZ);
+		/*
+		 * for performance we have to return immediately
+		 * if the requested data has been offloaded
+		 */
+		if ((52 == cmd->opcode)
+		    && satisfy_request_from_offloaded_data(vub300, cmd)) {
+			cmd->error = 0;
+			mutex_unlock(&vub300->cmd_mutex);
+			kref_put(&vub300->kref, vub300_delete);
+			mmc_request_done(mmc, req);
+			return;
+		} else {
+			vub300->cmd = cmd;
+			vub300->req = req;
+			vub300->data = data;
+			if (data)
+				vub300->datasize = data->blksz * data->blocks;
+			else
+				vub300->datasize = 0;
+			vub300_queue_cmnd_work(vub300);
+			mutex_unlock(&vub300->cmd_mutex);
+			kref_put(&vub300->kref, vub300_delete);
+			/*
+			 * the kernel lock diagnostics complain
+			 * if the cmd_mutex * is "passed on"
+			 * to the cmndwork thread,
+			 * so we must release it now
+			 * and re-acquire it in the cmndwork thread
+			 */
+		}
+	}
+}
+
+static void __set_clock_speed(struct vub300_mmc_host *vub300, u8 * buf,
+			      struct mmc_ios *ios)
+{
+	int retval;
+	u32 kHzClock;
+	if (ios->clock >= 48000000)
+		kHzClock = 48000;
+	else if (ios->clock >= 24000000)
+		kHzClock = 24000;
+	else if (ios->clock >= 20000000)
+		kHzClock = 20000;
+	else if (ios->clock >= 15000000)
+		kHzClock = 15000;
+	else if (ios->clock >= 200000)
+		kHzClock = 200;
+	else
+		kHzClock = 0;
+	buf[0] = 0xFF & (kHzClock >> 0);
+	buf[1] = 0xFF & (kHzClock >> 8);
+	buf[2] = 0xFF & (kHzClock >> 16);
+	buf[3] = 0xFF & (kHzClock >> 24);
+	buf[4] = 0;
+	buf[5] = 0;
+	buf[6] = 0;
+	buf[7] = 0;
+	retval =
+	    usb_control_msg(vub300->udev, usb_sndctrlpipe(vub300->udev, 0),
+			    SET_CLOCK_SPEED,
+			    USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			    0x00, 0x00, buf, 8, HZ);
+	if (8 != retval) {
+		dev_err(&vub300->udev->dev, "SET_CLOCK_SPEED"
+			" %dkHz failed with retval=%d", kHzClock, retval);
+	}
+}
+
+static void vub300_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{				/* NOT irq */
+	struct vub300_mmc_host *vub300 = mmc_priv(mmc);
+	if (!vub300->interface)
+		return;
+	kref_get(&vub300->kref);
+	mutex_lock(&vub300->cmd_mutex);
+	if ((MMC_POWER_OFF == ios->power_mode) && vub300->card_powered) {
+		int retval;
+		vub300->card_powered = 0;
+		retval =
+		    usb_control_msg(vub300->udev,
+				    usb_sndctrlpipe(vub300->udev, 0),
+				    SET_SD_POWER,
+				    USB_DIR_OUT | USB_TYPE_VENDOR |
+				    USB_RECIP_DEVICE, 0x0000, 0x0000, NULL, 0,
+				    HZ);
+		/*
+		 * must wait for the VUB300 u-proc to boot up
+		 */
+		msleep(600);
+	} else if ((MMC_POWER_UP == ios->power_mode) && !vub300->card_powered) {
+		int retval;
+		retval =
+		    usb_control_msg(vub300->udev,
+				    usb_sndctrlpipe(vub300->udev, 0),
+				    SET_SD_POWER,
+				    USB_DIR_OUT | USB_TYPE_VENDOR |
+				    USB_RECIP_DEVICE, 0x0001, 0x0000, NULL, 0,
+				    HZ);
+		msleep(600);
+		vub300->card_powered = 1;
+	} else if (MMC_POWER_ON == ios->power_mode) {
+		u8 *buf = kmalloc(8, GFP_KERNEL);
+		if (buf) {
+			__set_clock_speed(vub300, buf, ios);
+			kfree(buf);
+		}
+	} else {
+		/*
+		 * this should mean no change of state
+		 */
+	}
+	mutex_unlock(&vub300->cmd_mutex);
+	kref_put(&vub300->kref, vub300_delete);
+}
+
+static int vub300_mmc_get_ro(struct mmc_host *mmc)
+{
+	struct vub300_mmc_host *vub300 = mmc_priv(mmc);
+	return vub300->read_only;
+}
+
+static void vub300_enable_sdio_irq(struct mmc_host *mmc, int enable)
+{				/* NOT irq */
+	struct vub300_mmc_host *vub300 = mmc_priv(mmc);
+	if (!vub300->interface)
+		return;
+	kref_get(&vub300->kref);
+	if (enable) {
+		mutex_lock(&vub300->irq_mutex);
+		if (vub300->irqs_queued) {
+			vub300->irqs_queued -= 1;
+			mmc_signal_sdio_irq(vub300->mmc);
+		} else if (vub300->irq_disabled) {
+			vub300->irq_disabled = 0;
+			vub300->irq_enabled = 1;
+			vub300_queue_poll_work(vub300, 0);
+		} else if (vub300->irq_enabled) {
+			/*
+			 * this should not happen
+			 * so we will just ignore it
+			 */
+		} else {
+			vub300->irq_enabled = 1;
+			vub300_queue_poll_work(vub300, 0);
+		}
+		mutex_unlock(&vub300->irq_mutex);
+	} else {
+		vub300->irq_enabled = 0;
+	}
+	kref_put(&vub300->kref, vub300_delete);
+}
+
+void vub300_init_card(struct mmc_host *mmc, struct mmc_card *card)
+{				/* NOT irq */
+	struct vub300_mmc_host *vub300 = mmc_priv(mmc);
+	dev_info(&vub300->udev->dev, "NO host QUIRKS for this card");
+}
+
+static struct mmc_host_ops vub300_mmc_ops = {
+	.request = vub300_mmc_request,
+	.set_ios = vub300_mmc_set_ios,
+	.get_ro = vub300_mmc_get_ro,
+	.enable_sdio_irq = vub300_enable_sdio_irq,
+	.init_card = vub300_init_card,
+};
+
+static int vub300_probe(struct usb_interface *interface,
+			const struct usb_device_id *id)
+{				/* NOT irq */
+	struct vub300_mmc_host *vub300 = NULL;
+	struct usb_host_interface *iface_desc;
+	struct usb_device *udev = usb_get_dev(interface_to_usbdev(interface));
+	int i;
+	int retval = -ENOMEM;
+	struct urb *command_out_urb;
+	struct urb *command_res_urb;
+	struct mmc_host *mmc;
+	char Manufacturer[48];
+	char Product[32];
+	char SerialNumber[32];
+	usb_string(udev, udev->descriptor.iManufacturer, Manufacturer,
+		   sizeof(Manufacturer));
+	usb_string(udev, udev->descriptor.iProduct, Product, sizeof(Product));
+	usb_string(udev, udev->descriptor.iSerialNumber, SerialNumber,
+		   sizeof(SerialNumber));
+	dev_info(&udev->dev, "probing VID:PID(%04X:%04X) %s %s %s",
+		 udev->descriptor.idVendor, udev->descriptor.idProduct,
+		 Manufacturer, Product, SerialNumber);
+	command_out_urb = usb_alloc_urb(0, GFP_KERNEL);
+	if (!command_out_urb) {
+		retval = -ENOMEM;
+		dev_err(&vub300->udev->dev,
+			"not enough memory for the command_out_urb");
+		goto error0;
+	}
+	command_res_urb = usb_alloc_urb(0, GFP_KERNEL);
+	if (!command_res_urb) {
+		retval = -ENOMEM;
+		dev_err(&vub300->udev->dev,
+			"not enough memory for the command_res_urb");
+		goto error1;
+	}
+	/* this also allocates memory for our VUB300 mmc host device */
+	mmc = mmc_alloc_host(sizeof(struct vub300_mmc_host), &udev->dev);
+	if (!mmc) {
+		retval = -ENOMEM;
+		dev_err(&vub300->udev->dev,
+			"not enough memory for the mmc_host");
+		goto error4;
+	}
+	/* MMC core transfer sizes tunable parameters */
+	mmc->caps = 0;
+	if (!force_1_bit_data_xfers)
+		mmc->caps |= MMC_CAP_4_BIT_DATA;
+	if (!force_polling_for_irqs)
+		mmc->caps |= MMC_CAP_SDIO_IRQ;
+	mmc->caps &= ~MMC_CAP_NEEDS_POLL;
+	/*
+	 * MMC_CAP_NEEDS_POLL causes core.c:mmc_rescan() to poll
+	 * for devices which results in spurious CMD7's being
+	 * issued which stops some SDIO cards from working
+	 */
+	if (limit_speed_to_24_MHz) {
+		mmc->caps |= MMC_CAP_MMC_HIGHSPEED;
+		mmc->caps |= MMC_CAP_SD_HIGHSPEED;
+		mmc->f_max = 24000000;
+		dev_info(&udev->dev, "limiting SDIO speed to 24_MHz");
+	} else {
+		mmc->caps |= MMC_CAP_MMC_HIGHSPEED;
+		mmc->caps |= MMC_CAP_SD_HIGHSPEED;
+		mmc->f_max = 48000000;
+	}
+	mmc->f_min = 200000;
+	mmc->max_blk_count = 511;
+	mmc->max_blk_size = 512;
+	mmc->max_hw_segs = 128;
+	mmc->max_phys_segs = 128;
+	if (force_max_req_size)
+		mmc->max_req_size = force_max_req_size * 1024;
+	else
+		mmc->max_req_size = 64 * 1024;
+	mmc->max_seg_size = mmc->max_req_size;
+	mmc->ocr_avail = 0;
+	mmc->ocr_avail |= MMC_VDD_165_195;
+	mmc->ocr_avail |= MMC_VDD_20_21;
+	mmc->ocr_avail |= MMC_VDD_21_22;
+	mmc->ocr_avail |= MMC_VDD_22_23;
+	mmc->ocr_avail |= MMC_VDD_23_24;
+	mmc->ocr_avail |= MMC_VDD_24_25;
+	mmc->ocr_avail |= MMC_VDD_25_26;
+	mmc->ocr_avail |= MMC_VDD_26_27;
+	mmc->ocr_avail |= MMC_VDD_27_28;
+	mmc->ocr_avail |= MMC_VDD_28_29;
+	mmc->ocr_avail |= MMC_VDD_29_30;
+	mmc->ocr_avail |= MMC_VDD_30_31;
+	mmc->ocr_avail |= MMC_VDD_31_32;
+	mmc->ocr_avail |= MMC_VDD_32_33;
+	mmc->ocr_avail |= MMC_VDD_33_34;
+	mmc->ocr_avail |= MMC_VDD_34_35;
+	mmc->ocr_avail |= MMC_VDD_35_36;
+	mmc->ops = &vub300_mmc_ops;
+	vub300 = mmc_priv(mmc);
+	vub300->mmc = mmc;
+	vub300->card_powered = 0;
+	vub300->bus_width = 0;
+	vub300->cmnd.head.BlockSize[0] = 0x00;
+	vub300->cmnd.head.BlockSize[1] = 0x00;
+	vub300->app_spec = 0;
+	mutex_init(&vub300->cmd_mutex);
+	mutex_init(&vub300->irq_mutex);
+	vub300->command_out_urb = command_out_urb;
+	vub300->command_res_urb = command_res_urb;
+	vub300->usb_timed_out = 0;
+	vub300->dynamic_register_count = 0;
+	{
+		int i = 0;
+		do {
+			vub300->fn[i].offload_point = 0;
+			vub300->fn[i].offload_count = 0;
+		} while (++i < 8);
+	}
+	vub300->total_offload_count = 0;
+	vub300->irq_enabled = 0;
+	vub300->irq_disabled = 0;
+	vub300->irqs_queued = 0;
+	{
+		int i = 0;
+		int I = ARRAY_SIZE(vub300->sdio_register);
+		while (I--)
+			vub300->sdio_register[i++].activate = 0;
+	}
+	vub300->udev = udev;
+	vub300->interface = interface;
+	vub300->cmnd_res_ep = 0;
+	vub300->cmnd_out_ep = 0;
+	vub300->data_inp_ep = 0;
+	vub300->data_out_ep = 0;
+	vub300->fbs[0] = 512;
+	vub300->fbs[1] = 512;
+	vub300->fbs[2] = 512;
+	vub300->fbs[3] = 512;
+	vub300->fbs[4] = 512;
+	vub300->fbs[5] = 512;
+	vub300->fbs[6] = 512;
+	vub300->fbs[7] = 512;
+	/*
+	 *      set up the endpoint information
+	 *
+	 * use the first pair of bulk-in and bulk-out
+	 *     endpoints for Command/Response+Interrupt
+	 *
+	 * use the second pair of bulk-in and bulk-out
+	 *     endpoints for Data In/Out
+	 */
+	vub300->large_usb_packets = 0;
+	iface_desc = interface->cur_altsetting;
+	for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {
+		struct usb_endpoint_descriptor *endpoint =
+		    &iface_desc->endpoint[i].desc;
+		dev_info(&vub300->udev->dev,
+			 "vub300 testing %s EndPoint(%d) %02X",
+			 usb_endpoint_is_bulk_in(endpoint) ? "BULK IN" :
+			 usb_endpoint_is_bulk_out(endpoint) ? "BULK OUT" :
+			 "UNKNOWN", i, endpoint->bEndpointAddress);
+		if (64 < endpoint->wMaxPacketSize)
+			vub300->large_usb_packets = 1;
+		if (usb_endpoint_is_bulk_in(endpoint)) {
+			if (!vub300->cmnd_res_ep) {
+				vub300->cmnd_res_ep =
+				    endpoint->bEndpointAddress;
+			} else if (!vub300->data_inp_ep) {
+				vub300->data_inp_ep =
+				    endpoint->bEndpointAddress;
+			} else {
+				dev_warn(&vub300->udev->dev,
+					 "ignoring"
+					 " unexpected bulk_in endpoint");
+			}
+		} else if (usb_endpoint_is_bulk_out(endpoint)) {
+			if (!vub300->cmnd_out_ep) {
+				vub300->cmnd_out_ep =
+				    endpoint->bEndpointAddress;
+			} else if (!vub300->data_out_ep) {
+				vub300->data_out_ep =
+				    endpoint->bEndpointAddress;
+			} else {
+				dev_warn(&vub300->udev->dev,
+					 "ignoring"
+					 " unexpected bulk_out endpoint");
+			}
+		} else {
+			dev_warn(&vub300->udev->dev,
+				 "vub300 ignoring EndPoint(%d) %02X", i,
+				 endpoint->bEndpointAddress);
+		}
+	}
+	if (vub300->cmnd_res_ep
+	    && vub300->cmnd_out_ep
+	    && vub300->data_inp_ep && vub300->data_out_ep) {
+		dev_info(&vub300->udev->dev,
+			 "vub300 %s packets"
+			 " using EndPoints %02X %02X %02X %02X",
+			 vub300->large_usb_packets ? "LARGE" : "SMALL",
+			 vub300->cmnd_out_ep, vub300->cmnd_res_ep,
+			 vub300->data_out_ep, vub300->data_inp_ep);
+		/*
+		 * we have the expected EndPoints
+		 */
+	} else {
+		dev_err(&vub300->udev->dev,
+			"Could not find two sets of bulk-in/out endpoint pairs");
+		retval = -EINVAL;
+		goto error5;
+	}
+	retval =
+	    usb_control_msg(vub300->udev, usb_rcvctrlpipe(vub300->udev, 0),
+			    GET_HC_INF0,
+			    USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			    0x0000, 0x0000, &vub300->hc_info,
+			    sizeof(vub300->hc_info), HZ);
+	if (retval < 0)
+		goto error5;
+	retval =
+	    usb_control_msg(vub300->udev, usb_rcvctrlpipe(vub300->udev, 0),
+			    SET_ROM_WAIT_STATES,
+			    USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			    firmware_rom_wait_states, 0x0000, NULL, 0, HZ);
+	if (retval < 0)
+		goto error5;
+	dev_info(&vub300->udev->dev,
+		 "OperatingMode = %s %s %d MHz %s %d byte USB packets",
+		 (mmc->caps & MMC_CAP_SDIO_IRQ) ? "IRQs" : "POLL",
+		 (mmc->caps & MMC_CAP_4_BIT_DATA) ? "4-bit" : "1-bit",
+		 mmc->f_max / 1000000,
+		 pad_input_to_usb_pkt ? "padding input data to" : "with",
+		 vub300->large_usb_packets ? 512 : 64);
+	retval =
+	    usb_control_msg(vub300->udev, usb_rcvctrlpipe(vub300->udev, 0),
+			    GET_SYSTEM_PORT_STATUS,
+			    USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			    0x0000, 0x0000, &vub300->system_port_status,
+			    sizeof(vub300->system_port_status), HZ);
+	if (retval < 0) {
+		goto error4;
+	} else if (sizeof(vub300->system_port_status) == retval) {
+		vub300->card_present =
+		    (0x0001 & vub300->system_port_status.PortFlags) ? 1 : 0;
+		vub300->read_only =
+		    (0x0010 & vub300->system_port_status.PortFlags) ? 1 : 0;
+	} else {
+		goto error4;
+	}
+	usb_set_intfdata(interface, vub300);
+	INIT_DELAYED_WORK(&vub300->pollwork, vub300_pollwork_thread);
+	INIT_WORK(&vub300->cmndwork, vub300_cmndwork_thread);
+	INIT_WORK(&vub300->deadwork, vub300_deadwork_thread);
+	kref_init(&vub300->kref);
+	init_timer(&vub300->sg_transfer_timer);
+	vub300->sg_transfer_timer.data = (unsigned long)vub300;
+	vub300->sg_transfer_timer.function = vub300_sg_timed_out;
+	kref_get(&vub300->kref);
+	init_timer(&vub300->inactivity_timer);
+	vub300->inactivity_timer.data = (unsigned long)vub300;
+	vub300->inactivity_timer.function = vub300_inactivity_timer_expired;
+	vub300->inactivity_timer.expires = jiffies + HZ;
+	add_timer(&vub300->inactivity_timer);
+	if (vub300->card_present)
+		dev_info(&vub300->udev->dev,
+			 "USB vub300 remote SDIO host controller[%d]"
+			 "connected with SD/SDIO card inserted",
+			 interface_to_InterfaceNumber(interface));
+	else
+		dev_info(&vub300->udev->dev,
+			 "USB vub300 remote SDIO host controller[%d]"
+			 "connected with no SD/SDIO card inserted",
+			 interface_to_InterfaceNumber(interface));
+	retval = sysfs_create_group(&interface->dev.kobj, &vub300_attr_grp);
+	mmc_add_host(mmc);
+	return 0;
+error5:
+	mmc_free_host(mmc);
+	/*
+	 * and hence also frees vub300
+	 * which is contained at the end of struct mmc
+	 */
+error4:
+	usb_free_urb(command_out_urb);
+error1:
+	usb_free_urb(command_res_urb);
+error0:
+	return retval;
+}
+
+static void vub300_disconnect(struct usb_interface *interface)
+{				/* NOT irq */
+	struct vub300_mmc_host *vub300 = usb_get_intfdata(interface);
+	if (!vub300 || !vub300->mmc) {
+		return;
+	} else {
+		struct mmc_host *mmc = vub300->mmc;
+		if (!vub300->mmc) {
+			return;
+		} else {
+			int ifnum = interface_to_InterfaceNumber(interface);
+			sysfs_remove_group(&interface->dev.kobj,
+					   &vub300_attr_grp);
+			usb_set_intfdata(interface, NULL);
+			/* prevent more I/O from starting */
+			vub300->interface = NULL;
+			kref_put(&vub300->kref, vub300_delete);
+			mmc_remove_host(mmc);
+			pr_info("USB vub300 remote SDIO host controller[%d]"
+				" now disconnected", ifnum);
+			return;
+		}
+	}
+}
+
+#ifdef CONFIG_PM
+static int vub300_suspend(struct usb_interface *intf, pm_message_t message)
+{
+	struct vub300_mmc_host *vub300 = usb_get_intfdata(intf);
+	if (!vub300 || !vub300->mmc) {
+		return 0;
+	} else {
+		struct mmc_host *mmc = vub300->mmc;
+		mmc_suspend_host(mmc);
+		return 0;
+	}
+}
+
+static int vub300_resume(struct usb_interface *intf)
+{
+	struct vub300_mmc_host *vub300 = usb_get_intfdata(intf);
+	if (!vub300 || !vub300->mmc) {
+		return 0;
+	} else {
+		struct mmc_host *mmc = vub300->mmc;
+		mmc_resume_host(mmc);
+		return 0;
+	}
+}
+#else
+#define vub300_suspend NULL
+#define vub300_resume NULL
+#endif
+static int vub300_pre_reset(struct usb_interface *intf)
+{				/* NOT irq */
+	struct vub300_mmc_host *vub300 = usb_get_intfdata(intf);
+	mutex_lock(&vub300->cmd_mutex);
+	return 0;
+}
+
+static int vub300_post_reset(struct usb_interface *intf)
+{				/* NOT irq */
+	struct vub300_mmc_host *vub300 = usb_get_intfdata(intf);
+	/* we are sure no URBs are active - no locking needed */
+	vub300->errors = -EPIPE;
+	mutex_unlock(&vub300->cmd_mutex);
+	return 0;
+}
+
+static struct usb_driver vub300_driver = {
+	.name = "vub300",
+	.probe = vub300_probe,
+	.disconnect = vub300_disconnect,
+	.suspend = vub300_suspend,
+	.resume = vub300_resume,
+	.pre_reset = vub300_pre_reset,
+	.post_reset = vub300_post_reset,
+	.id_table = vub300_table,
+	.supports_autosuspend = 1,
+};
+
+static int __init vub300_init(void)
+{				/* NOT irq */
+	int result;
+	pr_info("VUB300 Driver rom wait states = %02X irqpoll timeout = %04X",
+		firmware_rom_wait_states, 0x0FFFF & firmware_irqpoll_timeout);
+	cmndworkqueue = create_singlethread_workqueue("kvub300c");
+	if (!cmndworkqueue) {
+		pr_err("not enough memory for the REQUEST workqueue");
+		result = -ENOMEM;
+		goto out1;
+	}
+	pollworkqueue = create_singlethread_workqueue("kvub300p");
+	if (!pollworkqueue) {
+		pr_err("not enough memory for the IRQPOLL workqueue");
+		result = -ENOMEM;
+		goto out2;
+	}
+	deadworkqueue = create_singlethread_workqueue("kvub300d");
+	if (!deadworkqueue) {
+		pr_err("not enough memory for the EXPIRED workqueue");
+		result = -ENOMEM;
+		goto out3;
+	}
+	result = usb_register(&vub300_driver);
+	if (result) {
+		pr_err("usb_register failed. Error number %d", result);
+		goto out4;
+	}
+	return 0;
+out4:
+	destroy_workqueue(deadworkqueue);
+out3:
+	destroy_workqueue(pollworkqueue);
+out2:
+	destroy_workqueue(cmndworkqueue);
+out1:
+	return result;
+}
+
+static void __exit vub300_exit(void)
+{
+	usb_deregister(&vub300_driver);
+	flush_workqueue(cmndworkqueue);
+	flush_workqueue(pollworkqueue);
+	flush_workqueue(deadworkqueue);
+	destroy_workqueue(cmndworkqueue);
+	destroy_workqueue(pollworkqueue);
+	destroy_workqueue(deadworkqueue);
+}
+
+module_init(vub300_init);
+module_exit(vub300_exit);
+MODULE_LICENSE("GPL");

-- 
Chris Ball   <cjb@laptop.org>   <http://printf.net/>
One Laptop Per Child

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH] mmc: USB SDIO/SD/MMC Host Controller (VUB300) driver
  2010-11-30  6:15             ` Chris Ball
@ 2010-11-30 12:23               ` David Vrabel
  2010-12-17  0:43                 ` Chris Ball
  2010-12-21 15:03                 ` Tony Olech
  0 siblings, 2 replies; 41+ messages in thread
From: David Vrabel @ 2010-11-30 12:23 UTC (permalink / raw)
  To: Chris Ball; +Cc: Tony Olech, linux-mmc

Chris Ball wrote:
> Hi David,
> 
> Any interest in reviewing this USB-SD driver?  I'm especially curious
> about whether you think there are worthwhile possibilities to share 
> code with USHC.

A quick look suggests that there is unlikely to be any commonality
between the two drivers.

Tony, where can I find the datasheet or developer's manual describing
the protocol/operation of this chip?

David
-- 
David Vrabel, Senior Software Engineer, Drivers
CSR, Churchill House, Cambridge Business Park,  Tel: +44 (0)1223 692562
Cowley Road, Cambridge, CB4 0WZ                 http://www.csr.com/


Member of the CSR plc group of companies. CSR plc registered in England and Wales, registered number 4187346, registered office Churchill House, Cambridge Business Park, Cowley Road, Cambridge, CB4 0WZ, United Kingdom

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH] mmc: USB SDIO/SD/MMC Host Controller (VUB300) driver
  2010-11-30 12:23               ` David Vrabel
@ 2010-12-17  0:43                 ` Chris Ball
  2010-12-21 15:03                 ` Tony Olech
  1 sibling, 0 replies; 41+ messages in thread
From: Chris Ball @ 2010-12-17  0:43 UTC (permalink / raw)
  To: Tony Olech; +Cc: David Vrabel, linux-mmc

Hi Tony,

On Tue, Nov 30, 2010 at 12:23:06PM +0000, David Vrabel wrote:
> > Any interest in reviewing this USB-SD driver?  I'm especially curious
> > about whether you think there are worthwhile possibilities to share 
> > code with USHC.
> 
> A quick look suggests that there is unlikely to be any commonality
> between the two drivers.
> 
> Tony, where can I find the datasheet or developer's manual describing
> the protocol/operation of this chip?

Ping -- any answer for David here?  Thanks,

-- 
Chris Ball   <cjb@laptop.org>   <http://printf.net/>
One Laptop Per Child

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH] mmc: USB SDIO/SD/MMC Host Controller (VUB300) driver
  2010-11-30 12:23               ` David Vrabel
  2010-12-17  0:43                 ` Chris Ball
@ 2010-12-21 15:03                 ` Tony Olech
  2011-01-06  4:56                   ` Chris Ball
  1 sibling, 1 reply; 41+ messages in thread
From: Tony Olech @ 2010-12-21 15:03 UTC (permalink / raw)
  To: David Vrabel; +Cc: Chris Ball, linux-mmc

On 30/11/2010 12:23, David Vrabel wrote:
> Chris Ball wrote:
>> Hi David,
>>
>> Any interest in reviewing this USB-SD driver?  I'm especially curious
>> about whether you think there are worthwhile possibilities to share
>> code with USHC.
> A quick look suggests that there is unlikely to be any commonality
> between the two drivers.
>
> Tony, where can I find the datasheet or developer's manual describing
> the protocol/operation of this chip?
>
> David
Hi,
I finally persuaded my boss that our interface document should put released.
It is the PDF in 
http://www.elandigitalsystems.com/eng/drivers/vub300/Linux-v2-10.zip
and it describes the protocol between the customized SMSC chip and the 
host PC.
And I agree that there will be virtually nothing in common with the USHC 
driver.
Tony Olech
Elan Digital Systems Limited


^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH] mmc: USB SDIO/SD/MMC Host Controller (VUB300) driver
  2010-12-21 15:03                 ` Tony Olech
@ 2011-01-06  4:56                   ` Chris Ball
  2011-01-06 13:18                     ` David Vrabel
  0 siblings, 1 reply; 41+ messages in thread
From: Chris Ball @ 2011-01-06  4:56 UTC (permalink / raw)
  To: Tony Olech; +Cc: David Vrabel, linux-mmc

Hi David,

On Tue, Dec 21, 2010 at 03:03:58PM +0000, Tony Olech wrote:
>>> Any interest in reviewing this USB-SD driver?  I'm especially curious
>>> about whether you think there are worthwhile possibilities to share
>>> code with USHC.
>> A quick look suggests that there is unlikely to be any commonality
>> between the two drivers.
>>
>> Tony, where can I find the datasheet or developer's manual describing
>> the protocol/operation of this chip?
>
> I finally persuaded my boss that our interface document should put released.
> It is the PDF in  
> http://www.elandigitalsystems.com/eng/drivers/vub300/Linux-v2-10.zip
> and it describes the protocol between the customized SMSC chip and the  
> host PC.
> And I agree that there will be virtually nothing in common with the USHC  
> driver.

Might you be able to find time to review this soon?

Thanks very much,

-- 
Chris Ball   <cjb@laptop.org>   <http://printf.net/>
One Laptop Per Child

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH] mmc: USB SDIO/SD/MMC Host Controller (VUB300) driver
  2010-11-22 15:05           ` [PATCH] mmc: USB SDIO/SD/MMC Host Controller (VUB300) driver Tony Olech
  2010-11-30  6:15             ` Chris Ball
@ 2011-01-06 13:17             ` David Vrabel
  2011-01-20 16:09               ` Tony Olech
  2011-01-20 16:11               ` Tony Olech
  2011-01-21 10:50             ` [PATCH] mmc: USB SDIO/SD/MMC Host Controller (VUB300) driver Resubmission Tony Olech
  2 siblings, 2 replies; 41+ messages in thread
From: David Vrabel @ 2011-01-06 13:17 UTC (permalink / raw)
  To: tony.olech; +Cc: Chris Ball, linux-mmc

I've not given this driver a detailed review, just skimmed it and
noticed a few things.

Tony Olech wrote:
> 
> +
> +	  Some SDIO cards will need a firmware file to be loaded and
> +	  sent to VUB300 chip in order to achieve better data throughput.
> +	  Download these "Offload Pseudocode" from Elan Digital Systems'
> +	  web-site http://www.elandigitalsystems.com/support/downloads.php
> +	  and put them in /lib/firmware. Note that without these additional
> +	  firmware files the VUB300 chip will still function, but not at
> +	  the best obtainable data rate.

It would be useful to document (in the driver) what this "offload
pseudocode" does.  Looking at the driver it looks like it's pre-fetching
SDIO register values, yes?

> +#pragma pack(1)

You probably want __attribute__((packed)) on each structure here.  (Not
sure if GCC even supports this #pragma).

> +static DEVICE_ATTR(OperatingMode, S_IRUGO, show_OperatingMode, NULL);
> +static struct attribute *vub300_attrs[] = {
> +	&dev_attr_OperatingMode.attr,
> +	NULL,
> +};

Suggest removing this unless it's actually useful. If it is useful, it
should use all lower case for the name and it should be documented in
Documentation/ABI.

> +static void snoop_block_size_and_bus_width(struct vub300_mmc_host *vub300,
> +					   u32 cmd_arg)

This doesn't seem necessary.  The block size is available in the data
request and the bus width is supplied to the set_ios() call.

> +		if (6 == cmd->opcode) {
> +			ResponseType = SDRT_1;
> +			vub300->resp_len = 6;

You don't need to check the opcode to determine the response format. See
cmd->flags.

> +
> +static void vub300_enable_sdio_irq(struct mmc_host *mmc, int enable)
> +{				/* NOT irq */
> +	struct vub300_mmc_host *vub300 = mmc_priv(mmc);
> +	if (!vub300->interface)
> +		return;
> +	kref_get(&vub300->kref);
> +	if (enable) {
> +		mutex_lock(&vub300->irq_mutex);
> +		if (vub300->irqs_queued) {
> +			vub300->irqs_queued -= 1;
> +			mmc_signal_sdio_irq(vub300->mmc);

This might signal a spurious interrupt as it's not checking if the
interrupt is still asserted.  This should be avoided.

David
-- 
David Vrabel, Senior Software Engineer, Drivers
CSR, Churchill House, Cambridge Business Park,  Tel: +44 (0)1223 692562
Cowley Road, Cambridge, CB4 0WZ                 http://www.csr.com/


Member of the CSR plc group of companies. CSR plc registered in England and Wales, registered number 4187346, registered office Churchill House, Cambridge Business Park, Cowley Road, Cambridge, CB4 0WZ, United Kingdom

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH] mmc: USB SDIO/SD/MMC Host Controller (VUB300) driver
  2011-01-06  4:56                   ` Chris Ball
@ 2011-01-06 13:18                     ` David Vrabel
  0 siblings, 0 replies; 41+ messages in thread
From: David Vrabel @ 2011-01-06 13:18 UTC (permalink / raw)
  To: Chris Ball; +Cc: Tony Olech, linux-mmc

Chris Ball wrote:
> Hi David,
> 
> On Tue, Dec 21, 2010 at 03:03:58PM +0000, Tony Olech wrote:
>>>> Any interest in reviewing this USB-SD driver?  I'm especially curious
>>>> about whether you think there are worthwhile possibilities to share
>>>> code with USHC.
>>> A quick look suggests that there is unlikely to be any commonality
>>> between the two drivers.
>>>
>>> Tony, where can I find the datasheet or developer's manual describing
>>> the protocol/operation of this chip?
>> I finally persuaded my boss that our interface document should put released.
>> It is the PDF in  
>> http://www.elandigitalsystems.com/eng/drivers/vub300/Linux-v2-10.zip
>> and it describes the protocol between the customized SMSC chip and the  
>> host PC.
>> And I agree that there will be virtually nothing in common with the USHC  
>> driver.
> 
> Might you be able to find time to review this soon?

There is no useful commonality between this driver and the USHC driver.
 I don't have time to do a detailed code review but I have gone through
and highlighted some potential issues (see other email).

David
-- 
David Vrabel, Senior Software Engineer, Drivers
CSR, Churchill House, Cambridge Business Park,  Tel: +44 (0)1223 692562
Cowley Road, Cambridge, CB4 0WZ                 http://www.csr.com/


Member of the CSR plc group of companies. CSR plc registered in England and Wales, registered number 4187346, registered office Churchill House, Cambridge Business Park, Cowley Road, Cambridge, CB4 0WZ, United Kingdom

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH] mmc: USB SDIO/SD/MMC Host Controller (VUB300) driver
  2011-01-06 13:17             ` David Vrabel
@ 2011-01-20 16:09               ` Tony Olech
  2011-01-20 16:11               ` Tony Olech
  1 sibling, 0 replies; 41+ messages in thread
From: Tony Olech @ 2011-01-20 16:09 UTC (permalink / raw)
  To: David Vrabel; +Cc: Chris Ball, linux-mmc

Hi,
the answers to your questions and comments are embedded below.
Because a few changes in the patch are required and also because
the current kernel version is now 2.6.27 I am re-doing the patch
and it will be included in a second e-mail by itself.

Tony Olech
Elan Digital Systems Limited

On Thu, 2011-01-06 at 13:17 +0000, David Vrabel wrote:
> I've not given this driver a detailed review, just skimmed it and
> noticed a few things.
> Tony Olech wrote:
> > +	  Some SDIO cards will need a firmware file to be loaded and
> > +	  sent to VUB300 chip in order to achieve better data throughput.
> > +	  Download these "Offload Pseudocode" from Elan Digital Systems'
> > +	  web-site http://www.elandigitalsystems.com/support/downloads.php
> > +	  and put them in /lib/firmware. Note that without these additional
> > +	  firmware files the VUB300 chip will still function, but not at
> > +	  the best obtainable data rate.
> It would be useful to document (in the driver) what this "offload
> pseudocode" does.  Looking at the driver it looks like it's pre-fetching
> SDIO register values, yes?

Yes it does does pre-fetch and pre-set some register values.
At this point in time the only available documentation is
the interface document referred to in a previous e-mail which
can be downloaded from:
www.elandigitalsystems.com/eng/drivers/vub300/Linux-v2-10.zip

> > +#pragma pack(1)
> You probably want __attribute__((packed)) on each structure here.  (Not
> sure if GCC even supports this #pragma).

Thank you for this comment. I have changed the patch accordingly.

> > +static DEVICE_ATTR(OperatingMode, S_IRUGO, show_OperatingMode, NULL);
> > +static struct attribute *vub300_attrs[] = {
> > +	&dev_attr_OperatingMode.attr,
> > +	NULL,
> > +};
> Suggest removing this unless it's actually useful. If it is useful, it
> should use all lower case for the name and it should be documented in
> Documentation/ABI.

This sysfs interface is a very useful read-only diagnostic and
will enable us to support customers. In particular it identifies
the SDIO bus speed actually being used as well as the identity
of the offload pseudocode firmware file, if any, that has
been downloaded into the VUB300 adapter. I have modified the
patch to use lower case for the sysfs file name (even though
other sysfs users use camel case) and have added documentation
as requested.vub300->irqs_queued

> > +static void snoop_block_size_and_bus_width(struct vub300_mmc_host *vub300,
> > +					   u32 cmd_arg)
> This doesn't seem necessary.  The block size is available in the datavub300->irqs_queued
> request and the bus width is supplied to the set_ios() call.

Unfortunately you are incorrect in your assertion.
The "block size" given in the data request is NOT
ALWAYS equal to the function block size. Therefore
the snooping of the function block size must remain.

> > +		if (6 == cmd->opcode) {
> > +			ResponseType = SDRT_1;
> > +			vub300->resp_len = 6;
> You don't need to check the opcode to determine the response format. Seevub300->irqs_queued
> cmd->flags.

Unfortunately you are incorrect in your assertion.
It is impossible, for example, from a flag setting
of 0x15 to determine whether a response type of
R5, R6 or R7 is to be expected. Therefore the
snooping of the opcode must remain.

> > +static void vub300_enable_sdio_irq(struct mmc_host *mmc, int enable)
> > +{				/* NOT irq */
> > +	struct vub300_mmc_host *vub300 = mmc_priv(mmc);vub300->irqs_queued
> > +	if (!vub300->interface)
> > +		return;
> > +	kref_get(&vub300->kref);
> > +	if (enable) {vub300->irqs_queued
> > +		mutex_lock(&vub300->irq_mutex);
> > +		if (vub300->irqs_queued) {
> > +			vub300->irqs_queued -= 1;
> > +			mmc_signal_sdio_irq(vub300->mmc);
> This might signal a spurious interrupt as it's not checking if the
> interrupt is still asserted.  This should be avoided.

You are correct in your implied assertion that spurious
interrupts cause havoc and ultimately failure in the
operation of the device. However this code segment is
absolutely required for the correct operation of our
offload processing. The key thing to note is that the
variable vub300->irqs_queued prevents spurious interrupts.

> David



^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH] mmc: USB SDIO/SD/MMC Host Controller (VUB300) driver
  2011-01-06 13:17             ` David Vrabel
  2011-01-20 16:09               ` Tony Olech
@ 2011-01-20 16:11               ` Tony Olech
  1 sibling, 0 replies; 41+ messages in thread
From: Tony Olech @ 2011-01-20 16:11 UTC (permalink / raw)
  To: David Vrabel; +Cc: Chris Ball, linux-mmc

Hi,
the answers to your questions and comments are embedded below.
Because a few changes in the patch are required and also because
the current kernel version is now 2.6.27 I am re-doing the patch
and it will be included in a second e-mail by itself.

Tony Olech
Elan Digital Systems Limited

On Thu, 2011-01-06 at 13:17 +0000, David Vrabel wrote: 
> I've not given this driver a detailed review, just skimmed it and
> noticed a few things.
> Tony Olech wrote:
> > +	  Some SDIO cards will need a firmware file to be loaded and
> > +	  sent to VUB300 chip in order to achieve better data throughput.
> > +	  Download these "Offload Pseudocode" from Elan Digital Systems'
> > +	  web-site http://www.elandigitalsystems.com/support/downloads.php
> > +	  and put them in /lib/firmware. Note that without these additional
> > +	  firmware files the VUB300 chip will still function, but not at
> > +	  the best obtainable data rate.
> It would be useful to document (in the driver) what this "offload
> pseudocode" does.  Looking at the driver it looks like it's pre-fetching
> SDIO register values, yes?

Yes it does does pre-fetch and pre-set some register values.
At this point in time the only available documentation is
the interface document referred to in a previous e-mail which
can be downloaded from:
www.elandigitalsystems.com/eng/drivers/vub300/Linux-v2-10.zip

> > +#pragma pack(1)
> You probably want __attribute__((packed)) on each structure here.  (Not
> sure if GCC even supports this #pragma).

Thank you for this comment. I have changed the patch accordingly.

> > +static DEVICE_ATTR(OperatingMode, S_IRUGO, show_OperatingMode, NULL);
> > +static struct attribute *vub300_attrs[] = {
> > +	&dev_attr_OperatingMode.attr,
> > +	NULL,
> > +};
> Suggest removing this unless it's actually useful. If it is useful, it
> should use all lower case for the name and it should be documented in
> Documentation/ABI.

This sysfs interface is a very useful read-only diagnostic and
will enable us to support customers. In particular it identifies
the SDIO bus speed actually being used as well as the identity
of the offload pseudocode firmware file, if any, that has
been downloaded into the VUB300 adapter. I have modified the
patch to use lower case for the sysfs file name (even though
other sysfs users use camel case) and have added documentation
as requested.vub300->irqs_queued

> > +static void snoop_block_size_and_bus_width(struct vub300_mmc_host *vub300,
> > +					   u32 cmd_arg)
> This doesn't seem necessary.  The block size is available in the datavub300->irqs_queued
> request and the bus width is supplied to the set_ios() call.

Unfortunately you are incorrect in your assertion.
The "block size" given in the data request is NOT
ALWAYS equal to the function block size. Therefore
the snooping of the function block size must remain.

> > +		if (6 == cmd->opcode) {
> > +			ResponseType = SDRT_1;
> > +			vub300->resp_len = 6;
> You don't need to check the opcode to determine the response format. Seevub300->irqs_queued
> cmd->flags.

Unfortunately you are incorrect in your assertion.
It is impossible, for example, from a flag setting
of 0x15 to determine whether a response type of
R5, R6 or R7 is to be expected. Therefore the
snooping of the opcode must remain.

> > +static void vub300_enable_sdio_irq(struct mmc_host *mmc, int enable)
> > +{				/* NOT irq */
> > +	struct vub300_mmc_host *vub300 = mmc_priv(mmc);vub300->irqs_queued
> > +	if (!vub300->interface)
> > +		return;
> > +	kref_get(&vub300->kref);
> > +	if (enable) {vub300->irqs_queued
> > +		mutex_lock(&vub300->irq_mutex);
> > +		if (vub300->irqs_queued) {
> > +			vub300->irqs_queued -= 1;
> > +			mmc_signal_sdio_irq(vub300->mmc);
> This might signal a spurious interrupt as it's not checking if the
> interrupt is still asserted.  This should be avoided.

You are correct in your implied assertion that spurious
interrupts cause havoc and ultimately failure in the
operation of the device. However this code segment is
absolutely required for the correct operation of our
offload processing. The key thing to note is that the
variable vub300->irqs_queued prevents spurious interrupts.

> David




^ permalink raw reply	[flat|nested] 41+ messages in thread

* [PATCH] mmc: USB SDIO/SD/MMC Host Controller (VUB300) driver Resubmission
  2010-11-22 15:05           ` [PATCH] mmc: USB SDIO/SD/MMC Host Controller (VUB300) driver Tony Olech
  2010-11-30  6:15             ` Chris Ball
  2011-01-06 13:17             ` David Vrabel
@ 2011-01-21 10:50             ` Tony Olech
  2011-01-21 21:14               ` Nicolas Pitre
  2011-03-10 16:13               ` [PATCH] mmc: USB SDIO/SD/MMC Host Controller (VUB300) driver Re-Resubmission Tony Olech
  2 siblings, 2 replies; 41+ messages in thread
From: Tony Olech @ 2011-01-21 10:50 UTC (permalink / raw)
  To: Chris Ball; +Cc: linux-mmc, David Vrabel

[-- Attachment #1: Type: text/plain, Size: 1 bytes --]



[-- Attachment #2: vub300-driver-3.patch --]
[-- Type: text/x-patch, Size: 85644 bytes --]

Add a driver for Elan Digital System's VUB300 chip
which is a USB connected SDIO/SDmem/MMC host controller.
A VUB300 chip enables a USB 2.0 or USB 1.1 connected host
computer to use SDIO/SD/MMC cards without the need for
a directly connected, for example via PCI, SDIO host
controller.

Signed-off-by: Anthony F Olech <tony.olech@elandigitalsystems.com>
---
This is the second submission attempt.
There are 5 "do not initialise statics" errors reported by scripts/checkpatch.pl
This driver has been tested on
a) 32bit x86
b) 64bit x86
c) dual processor
d) PowerPC
---
--- linux-2.6.37-vanilla/drivers/mmc/host/Makefile	2011-01-05 00:50:19.000000000 +0000
+++ linux-2.6.37-vub300/drivers/mmc/host/Makefile	2011-01-19 13:47:10.000000000 +0000
@@ -33,6 +33,7 @@ obj-$(CONFIG_MMC_VIA_SDMMC)	+= via-sdmmc
 obj-$(CONFIG_SDH_BFIN)		+= bfin_sdh.o
 obj-$(CONFIG_MMC_SH_MMCIF)	+= sh_mmcif.o
 obj-$(CONFIG_MMC_JZ4740)	+= jz4740_mmc.o
+obj-$(CONFIG_MMC_VUB300)	+= vub300.o
 obj-$(CONFIG_MMC_USHC)		+= ushc.o
 
 obj-$(CONFIG_MMC_SDHCI_PLTFM)			+= sdhci-platform.o
--- linux-2.6.37-vanilla/drivers/mmc/host/Kconfig	2011-01-05 00:50:19.000000000 +0000
+++ linux-2.6.37-vub300/drivers/mmc/host/Kconfig	2011-01-19 13:53:06.000000000 +0000
@@ -475,6 +475,37 @@ config MMC_JZ4740
 	  If you have a board based on such a SoC and with a SD/MMC slot,
 	  say Y or M here.
 
+config MMC_VUB300
+	tristate "VUB300 USB to SDIO/SD/MMC Host Controller support"
+	depends on USB
+	help
+	  This selects support for Elan Digital Systems' VUB300 chip.
+
+	  The VUB300 is a USB-SDIO Host Controller Interface chip
+	  that enables the host computer to use SDIO/SD/MMC cards
+	  via a USB 2.0 or USB 1.1 host.
+
+	  The VUB300 chip will be found in both physically separate
+	  USB to SDIO/SD/MMC adapters and embedded on some motherboards.
+
+	  The VUB300 chip supports SD and MMC memory cards in addition
+	  to single and multifunction SDIO cards.
+
+	  Some SDIO cards will need a firmware file to be loaded and
+	  sent to VUB300 chip in order to achieve better data throughput.
+	  Download these "Offload Pseudocode" from Elan Digital Systems'
+	  web-site http://www.elandigitalsystems.com/support/downloads.php
+	  and put them in /lib/firmware. Note that without these additional
+	  firmware files the VUB300 chip will still function, but not at
+	  the best obtainable data rate.
+
+	  To compile this mmc host controller driver as a module,
+	  choose M here: the module will be called vub300.
+
+	  If you have a computer with an embedded VUB300 chip
+	  or if you intend connecting a USB adapter based on a
+	  VUB300 chip say Y or M here.
+
 config MMC_USHC
 	tristate "USB SD Host Controller (USHC) support"
 	depends on USB
--- /dev/null	2011-01-20 07:52:00.496000002 +0000
+++ linux-2.6.37-vub300/Documentation/ABI/stable/vub300	2011-01-20 13:53:53.000000000 +0000
@@ -0,0 +1,11 @@
+
+What:		/sys/class/mmc_host/mmc<N>/operating_mode
+Date:		January 2011
+KernelVersion:	2.6.38
+Contact:	Tony Olech <tony.olech@elandigitalsystems.com>
+Description:	A read only diagnostic showing the state of the
+		vub300 sdio/mmc host controller driver, in particular
+		it shows which offload processing firmware file,
+		if any, is being used.
+Users:		-
+
--- /dev/null	2011-01-20 07:52:00.496000002 +0000
+++ linux-2.6.37-vub300/drivers/mmc/host/vub300.c	2011-01-20 16:29:48.000000000 +0000
@@ -0,0 +1,2616 @@
+/*
+ * Remote VUB300 SDIO/SDmem Host Controller Driver
+ *
+ * Copyright (C) 2010 Elan Digital Systems Limited
+ *
+ * based on USB Skeleton driver - 2.2
+ *
+ * Copyright (C) 2001-2004 Greg Kroah-Hartman (greg@kroah.com)
+ *
+ *	This program is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU General Public License as
+ *	published by the Free Software Foundation, version 2
+ *
+ * VUB300: is a USB 2.0 client device with a single SDIO/SDmem/MMC slot
+ *         Any SDIO/SDmem/MMC device plugged into the VUB300 will appear,
+ *         by virtue of this driver, to have been plugged into a local
+ *         SDIO host controller, similar to, say, a PCI Ricoh controller
+ *         This is because this kernel device driver is both a USB 2.0
+ *         client device driver AND an MMC host controller driver. Thus
+ *         if there is an existing driver for the inserted SDIO/SDmem/MMC
+ *         device then that driver will be used by the kernel to manage
+ *         the device in exactly the same fashion as if it had been
+ *         directly plugged into, say, a local pci bus Ricoh controller
+ *
+ * RANT: this driver was written using a display 128x48 - converting it
+ *       to a line width of 80 makes it very difficult to support. In
+ *       particular functions have been broken down into sub functions
+ *       and the original meaningful names have been shortened into
+ *       cryptic ones.
+ *       The problem is that executing a fragment of code subject to
+ *       two conditions means an indentation of 24, thus leaving only
+ *       56 characters for a C statement. And that is quite ridiculus!
+ *
+ * Data types: data passed to/from the VUB300 is fixed to a number of
+ *             bits and driver data fields reflect that limit by using
+ *             u8, u16, u32
+ */
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/kref.h>
+#include <linux/uaccess.h>
+#include <linux/usb.h>
+#include <linux/mutex.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/card.h>
+#include <linux/mmc/sdio_func.h>
+#include <linux/mmc/sdio_ids.h>
+#include <linux/workqueue.h>
+#include <linux/ctype.h>
+#include <linux/firmware.h>
+#include <linux/scatterlist.h>
+struct HostController_Info {
+	u8 Size;
+	u16 FirmwareVer;
+	u8 NumberOfPorts;
+} __attribute__((packed));
+struct SD_Command_Header {
+	u8 HeaderSize;
+	u8 HeaderType;
+	u8 PortNumber;
+	u8 Command_Type;	/* Bit7 - Rd/Wr */
+	u8 CommandIndex;
+	u8 TransferSize[4];	/* ReadSize + ReadSize */
+	u8 ResponseType;
+	u8 Arguments[4];
+	u8 BlockCount[2];
+	u8 BlockSize[2];
+	u8 BlockBoundary[2];
+	u8 Reserved[44];	/* to pad out to 64 bytes */
+} __attribute__((packed));
+struct SD_IRQpoll_Header {
+	u8 HeaderSize;
+	u8 HeaderType;
+	u8 PortNumber;
+	u8 Command_Type;	/* Bit7 - Rd/Wr */
+	u8 Padding[16];		/* don't ask why !! */
+	u8 Poll_Timeout_MSB;
+	u8 Poll_Timeout_LSB;
+	u8 Reserved[42];	/* to pad out to 64 bytes */
+} __attribute__((packed));
+struct SD_Common_Header {
+	u8 HeaderSize;
+	u8 HeaderType;
+	u8 PortNumber;
+} __attribute__((packed));
+struct SD_Response_Header {
+	u8 HeaderSize;
+	u8 HeaderType;
+	u8 PortNumber;
+	u8 Command_Type;
+	u8 CommandIndex;
+	u8 CommandResponse[0];
+} __attribute__((packed));
+struct SD_Status_Header {
+	u8 HeaderSize;
+	u8 HeaderType;
+	u8 PortNumber;
+	u16 PortFlags;
+	u32 SDIOclock;
+	u16 HostHeaderSize;
+	u16 FuncHeaderSize;
+	u16 CtrlHeaderSize;
+} __attribute__((packed));
+struct SD_Error_Header {
+	u8 HeaderSize;
+	u8 HeaderType;
+	u8 PortNumber;
+	u8 ErrorCode;
+} __attribute__((packed));
+struct SD_Interrupt_Header {
+	u8 HeaderSize;
+	u8 HeaderType;
+	u8 PortNumber;
+} __attribute__((packed));
+struct Offload_Registers_Access {
+	u8 Command_Byte[4];
+	u8 Respond_Byte[4];
+} __attribute__((packed));
+#define INTERRUPT_REGISTER_ACCESSES 15
+struct SD_Offloaded_Interrupt {
+	u8 HeaderSize;
+	u8 HeaderType;
+	u8 PortNumber;
+	struct Offload_Registers_Access reg[INTERRUPT_REGISTER_ACCESSES];
+} __attribute__((packed));
+struct SD_Register_Header {
+	u8 HeaderSize;
+	u8 HeaderType;
+	u8 PortNumber;
+	u8 Command_Type;
+	u8 CommandIndex;
+	u8 CommandResponse[6];
+} __attribute__((packed));
+#define PIGGYBACK_REGISTER_ACCESSES 14
+struct SD_Offloaded_Piggyback {
+	struct SD_Register_Header sdio;
+	struct Offload_Registers_Access reg[PIGGYBACK_REGISTER_ACCESSES];
+} __attribute__((packed));
+union SD_Response {
+	struct SD_Common_Header common;
+	struct SD_Status_Header status;
+	struct SD_Error_Header error;
+	struct SD_Interrupt_Header interrupt;
+	struct SD_Response_Header response;
+	struct SD_Offloaded_Interrupt irq;
+	struct SD_Offloaded_Piggyback pig;
+} __attribute__((packed));
+union SD_Command {
+	struct SD_Command_Header head;
+	struct SD_IRQpoll_Header poll;
+} __attribute__((packed));
+enum SD_RESPONSE_TYPE {
+	SDRT_UNSPECIFIED = 0,
+	SDRT_NONE,
+	SDRT_1,
+	SDRT_1B,
+	SDRT_2,
+	SDRT_3,
+	SDRT_4,
+	SDRT_5,
+	SDRT_5B,
+	SDRT_6,
+	SDRT_7,
+} __attribute__((packed));
+#define RESPONSE_INTERRUPT 0x01
+#define RESPONSE_ERROR 0x02
+#define RESPONSE_STATUS 0x03
+#define RESPONSE_IRQ_DISABLED 0x05
+#define RESPONSE_IRQ_ENABLED 0x06
+#define RESPONSE_PIGGYBACKED 0x07
+#define RESPONSE_NO_INTERRUPT 0x08
+#define RESPONSE_PIG_DISABLED 0x09
+#define RESPONSE_PIG_ENABLED 0x0A
+#define SD_ERROR_1BIT_TIMEOUT   0x01
+#define SD_ERROR_4BIT_TIMEOUT   0x02
+#define SD_ERROR_1BIT_CRC_WRONG 0x03
+#define SD_ERROR_4BIT_CRC_WRONG 0x04
+#define SD_ERROR_1BIT_CRC_ERROR 0x05
+#define SD_ERROR_4BIT_CRC_ERROR 0x06
+#define SD_ERROR_NO_CMD_ENDBIT  0x07
+#define SD_ERROR_NO_1BIT_DATEND 0x08
+#define SD_ERROR_NO_4BIT_DATEND 0x09
+#define SD_ERROR_1BIT_UNEXPECTED_TIMEOUT    0x0A
+#define SD_ERROR_4BIT_UNEXPECTED_TIMEOUT    0x0B
+#define SD_ERROR_ILLEGAL_COMMAND    0x0C
+#define SD_ERROR_NO_DEVICE 0x0D
+#define SD_ERROR_TRANSFER_LENGTH    0x0E
+#define SD_ERROR_1BIT_DATA_TIMEOUT  0x0F
+#define SD_ERROR_4BIT_DATA_TIMEOUT  0x10
+#define SD_ERROR_ILLEGAL_STATE  0x11
+#define SD_ERROR_UNKNOWN_ERROR  0x12
+#define SD_ERROR_RESERVED_ERROR 0x13
+#define SD_ERROR_INVALID_FUNCTION   0x14
+#define SD_ERROR_OUT_OF_RANGE   0x15
+#define SD_ERROR_STAT_CMD 0x16
+#define SD_ERROR_STAT_DATA 0x17
+#define SD_ERROR_STAT_CMD_TIMEOUT 0x18
+#define SD_ERROR_SDCRDY_STUCK 0x19
+#define SD_ERROR_UNHANDLED 0x1A
+#define SD_ERROR_OVERRUN 0x1B
+#define SD_ERROR_PIO_TIMEOUT 0x1C
+MODULE_AUTHOR("Tony Olech <tony.olech@elandigitalsystems.com>");
+MODULE_DESCRIPTION("VUB300 USB to SD/MMC/SDIO adapter driver");
+MODULE_LICENSE("GPL");
+#define FUN(c) (0x000007 & (c->arg>>28))
+#define REG(c) (0x01FFFF & (c->arg>>9))
+static int limit_speed_to_24_MHz = 0;
+module_param(limit_speed_to_24_MHz, bool, 0644);
+MODULE_PARM_DESC(limit_speed_to_24_MHz, "Limit Max SDIO Clock Speed to 24 MHz");
+static int pad_input_to_usb_pkt = 0;
+module_param(pad_input_to_usb_pkt, bool, 0644);
+MODULE_PARM_DESC(pad_input_to_usb_pkt,
+		 "Pad USB data input transfers to whole USB Packet");
+static int disable_offload_processing = 0;
+module_param(disable_offload_processing, bool, 0644);
+MODULE_PARM_DESC(disable_offload_processing, "Disable Offload Processing");
+static int force_1_bit_data_xfers = 0;
+module_param(force_1_bit_data_xfers, bool, 0644);
+MODULE_PARM_DESC(force_1_bit_data_xfers,
+		 "Force SDIO Data Transfers to 1-bit Mode");
+static int force_polling_for_irqs = 0;
+module_param(force_polling_for_irqs, bool, 0644);
+MODULE_PARM_DESC(force_polling_for_irqs, "Force Polling for SDIO interrupts");
+static int firmware_irqpoll_timeout = 1024;
+module_param(firmware_irqpoll_timeout, int, 0644);
+MODULE_PARM_DESC(firmware_irqpoll_timeout, "VUB300 firmware irqpoll timeout");
+static int force_max_req_size = 128;
+module_param(force_max_req_size, int, 0644);
+MODULE_PARM_DESC(force_max_req_size, "set max request size in kBytes");
+#ifdef SMSC_DEVELOPMENT_BOARD
+static int firmware_rom_wait_states = 0x04;
+#else
+static int firmware_rom_wait_states = 0x1C;
+#endif
+module_param(firmware_rom_wait_states, bool, 0644);
+MODULE_PARM_DESC(firmware_rom_wait_states,
+		 "ROM wait states byte=RRRIIEEE (Reserved Internal External)");
+/* Define these values to match your devices */
+#define ELAN_VENDOR_ID			0x2201
+#define VUB300_VENDOR_ID		0x0424
+#define VUB300_PRODUCT_ID		0x012C
+#define FIRMWARE_BLOCK_BOUNDARY 1024
+/* table of devices that work with this driver */
+static struct usb_device_id vub300_table[] = {
+	{USB_DEVICE(ELAN_VENDOR_ID, VUB300_PRODUCT_ID)},
+	{USB_DEVICE(VUB300_VENDOR_ID, VUB300_PRODUCT_ID)},
+	{}			/* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE(usb, vub300_table);
+static struct workqueue_struct *cmndworkqueue;
+static struct workqueue_struct *pollworkqueue;
+static struct workqueue_struct *deadworkqueue;
+static inline int interface_to_InterfaceNumber(struct usb_interface *interface)
+{
+	if (!interface)
+		return -1;
+	if (!interface->cur_altsetting)
+		return -1;
+	return interface->cur_altsetting->desc.bInterfaceNumber;
+}
+
+struct sdio_register {
+	unsigned func_num:3;
+	unsigned sdio_reg:17;
+	unsigned activate:1;
+	unsigned prepared:1;
+	unsigned regvalue:8;
+	unsigned response:8;
+	unsigned sparebit:26;
+};
+struct vub300_mmc_host {
+	struct usb_device *udev;
+	struct usb_interface *interface;
+	struct kref kref;
+	struct mutex cmd_mutex;
+	struct mutex irq_mutex;
+	char vub_name[3 + (9 * 8) + 4 + 1];	/* max of 7 sdio fn's */
+	u8 cmnd_out_ep;		/* EndPoint for commands */
+	u8 cmnd_res_ep;		/* EndPoint for responses */
+	u8 data_out_ep;		/* EndPoint for out data */
+	u8 data_inp_ep;		/* EndPoint for inp data */
+	unsigned card_powered:1;
+	unsigned card_present:1;
+	unsigned read_only:1;
+	unsigned large_usb_packets:1;
+	unsigned app_spec:1;	/* ApplicationSpecific */
+	unsigned irq_enabled:1;	/* by the MMC CORE */
+	unsigned irq_disabled:1;	/* in the firmware */
+	unsigned bus_width:4;
+	u8 total_offload_count;
+	u8 dynamic_register_count;
+	u8 resp_len;
+	u32 datasize;
+	int errors;
+	int usb_transport_fail;
+	int usb_timed_out;
+	int irqs_queued;
+	struct sdio_register sdio_register[16];
+	struct offload_interrupt_function_register {
+#define MAXREGBITS 4
+#define MAXREGS (1<<MAXREGBITS)
+#define MAXREGMASK (MAXREGS-1)
+		u8 offload_count;
+		u32 offload_point;
+		struct Offload_Registers_Access reg[MAXREGS];
+	} fn[8];
+	u16 fbs[8];		/* Function Block Size */
+	struct mmc_command *cmd;
+	struct mmc_request *req;
+	struct mmc_data *data;
+	struct mmc_host *mmc;
+	struct urb *urb;
+	struct urb *command_out_urb;
+	struct urb *command_res_urb;
+	struct completion command_complete;
+	struct completion irqpoll_complete;
+	union SD_Command cmnd;
+	union SD_Response resp;
+	struct timer_list sg_transfer_timer;
+	struct usb_sg_request sg_request;
+	struct timer_list inactivity_timer;
+	struct work_struct deadwork;
+	struct work_struct cmndwork;
+	struct delayed_work pollwork;
+	struct HostController_Info hc_info;
+	struct SD_Status_Header system_port_status;
+	u8 padded_buffer[64];
+};
+#define kref_to_vub300_mmc_host(d) container_of(d, struct vub300_mmc_host, kref)
+#define SET_TRANSFER_PSEUDOCODE 21
+#define SET_INTERRUPT_PSEUDOCODE 20
+#define SET_FAILURE_MODE 18
+#define SET_ROM_WAIT_STATES 16
+#define SET_IRQ_ENABLE 13
+#define SET_CLOCK_SPEED 11
+#define SET_FUNCTION_BLOCK_SIZE 9
+#define SET_SD_DATA_MODE 6
+#define SET_SD_POWER 4
+#define ENTER_DFU_MODE 3
+#define GET_HC_INF0 1
+#define GET_SYSTEM_PORT_STATUS 0
+static void vub300_delete(struct kref *kref)
+{				/* kref callback - softirq */
+	struct vub300_mmc_host *vub300 = kref_to_vub300_mmc_host(kref);
+	struct mmc_host *mmc = vub300->mmc;
+	usb_free_urb(vub300->command_out_urb);
+	vub300->command_out_urb = NULL;
+	usb_free_urb(vub300->command_res_urb);
+	vub300->command_res_urb = NULL;
+	usb_put_dev(vub300->udev);
+	mmc_free_host(mmc);
+	/*
+	 * and hence also frees vub300
+	 * which is contained at the end of struct mmc
+	 */
+}
+
+static ssize_t __show_operating_mode(struct vub300_mmc_host *vub300,
+				    struct mmc_host *mmc, char *buf)
+{
+	int usb_packet_size = vub300->large_usb_packets ? 512 : 64;
+	if (vub300->vub_name[0])
+		return sprintf(buf, "VUB %s %s %d MHz %s %d byte USB packets"
+				" using %s\n",
+		       (mmc->caps & MMC_CAP_SDIO_IRQ) ? "IRQs" : "POLL",
+		       (mmc->caps & MMC_CAP_4_BIT_DATA) ? "4-bit" : "1-bit",
+		       mmc->f_max / 1000000,
+		       pad_input_to_usb_pkt ? "padding input data to" : "with",
+		       usb_packet_size, vub300->vub_name);
+	else
+		return sprintf(buf, "VUB %s %s %d MHz %s %d byte USB packets"
+				" and no offload processing\n",
+		       (mmc->caps & MMC_CAP_SDIO_IRQ) ? "IRQs" : "POLL",
+		       (mmc->caps & MMC_CAP_4_BIT_DATA) ? "4-bit" : "1-bit",
+		       mmc->f_max / 1000000,
+		       pad_input_to_usb_pkt ? "padding input data to" : "with",
+		       usb_packet_size);
+}
+
+static ssize_t show_operating_mode(struct device *dev,
+				  struct device_attribute *attr, char *buf)
+{
+	struct mmc_host *mmc = container_of(dev, struct mmc_host, class_dev);
+	if (mmc) {
+		struct vub300_mmc_host *vub300 = mmc_priv(mmc);
+		return __show_operating_mode(vub300, mmc, buf);
+	} else {
+		return sprintf(buf, "VUB driver has no attached device");
+	}
+}
+
+static DEVICE_ATTR(operating_mode, S_IRUGO, show_operating_mode, NULL);
+static struct attribute *vub300_attrs[] = {
+	&dev_attr_operating_mode.attr,
+	NULL,
+};
+
+static struct attribute_group vub300_attr_grp = {
+	.attrs = vub300_attrs,
+};
+
+static void vub300_queue_cmnd_work(struct vub300_mmc_host *vub300)
+{
+	kref_get(&vub300->kref);
+	if (queue_work(cmndworkqueue, &vub300->cmndwork)) {
+		/*
+		 * then the cmndworkqueue was not previously
+		 * running and the above get ref is obvious
+		 * required and will be put when the thread
+		 * terminates by a specific call
+		 */
+	} else {
+		/*
+		 * the cmndworkqueue was already running from
+		 * a previous invocation and thus to keep the
+		 * kref counts correct we must undo the get
+		 */
+		kref_put(&vub300->kref, vub300_delete);
+	}
+}
+
+static void vub300_queue_poll_work(struct vub300_mmc_host *vub300, int delay)
+{
+	kref_get(&vub300->kref);
+	if (queue_delayed_work(pollworkqueue, &vub300->pollwork, delay)) {
+		/*
+		 * then the pollworkqueue was not previously
+		 * running and the above get ref is obvious
+		 * required and will be put when the thread
+		 * terminates by a specific call
+		 */
+	} else {
+		/*
+		 * the pollworkqueue was already running from
+		 * a previous invocation and thus to keep the
+		 * kref counts correct we must undo the get
+		 */
+		kref_put(&vub300->kref, vub300_delete);
+	}
+}
+
+static void vub300_queue_dead_work(struct vub300_mmc_host *vub300)
+{
+	kref_get(&vub300->kref);
+	if (queue_work(deadworkqueue, &vub300->deadwork)) {
+		/*
+		 * then the deadworkqueue was not previously
+		 * running and the above get ref is obvious
+		 * required and will be put when the thread
+		 * terminates by a specific call
+		 */
+	} else {
+		/*
+		 * the deadworkqueue was already running from
+		 * a previous invocation and thus to keep the
+		 * kref counts correct we must undo the get
+		 */
+		kref_put(&vub300->kref, vub300_delete);
+	}
+}
+
+static void irqpoll_res_completed(struct urb *urb)
+{				/* urb completion handler - hardirq */
+	/*
+	 * cmd_mutex is held by vub300_pollwork_thread
+	 */
+	struct vub300_mmc_host *vub300 = (struct vub300_mmc_host *)urb->context;
+	if (urb->status)
+		vub300->usb_transport_fail = urb->status;
+	complete(&vub300->irqpoll_complete);
+}
+
+static void irqpoll_out_completed(struct urb *urb)
+{				/* urb completion handler - hardirq */
+	/*
+	 * cmd_mutex is held by vub300_pollwork_thread
+	 */
+	struct vub300_mmc_host *vub300 = (struct vub300_mmc_host *)urb->context;
+	if (urb->status) {
+		vub300->usb_transport_fail = urb->status;
+		complete(&vub300->irqpoll_complete);
+		return;
+	} else {
+		int ret;
+		unsigned int pipe =
+		    usb_rcvbulkpipe(vub300->udev, vub300->cmnd_res_ep);
+		usb_fill_bulk_urb(vub300->command_res_urb, vub300->udev, pipe,
+				  &vub300->resp, sizeof(vub300->resp),
+				  irqpoll_res_completed, vub300);
+		vub300->command_res_urb->actual_length = 0;
+		ret = usb_submit_urb(vub300->command_res_urb, GFP_NOIO);
+		if (ret) {
+			vub300->usb_transport_fail = ret;
+			complete(&vub300->irqpoll_complete);
+		}
+		return;
+	}
+}
+
+static void send_irqpoll(struct vub300_mmc_host *vub300)
+{
+	/*
+	 * cmd_mutex is held by vub300_pollwork_thread
+	 */
+	int retval;
+	int timeout = 0xFFFF & (0x0001FFFF - firmware_irqpoll_timeout);
+	vub300->cmnd.poll.HeaderSize = 22;
+	vub300->cmnd.poll.HeaderType = 1;
+	vub300->cmnd.poll.PortNumber = 0;
+	vub300->cmnd.poll.Command_Type = 2;
+	vub300->cmnd.poll.Poll_Timeout_LSB = 0xFF & (unsigned)timeout;
+	vub300->cmnd.poll.Poll_Timeout_MSB = 0xFF & (unsigned)(timeout >> 8);
+	usb_fill_bulk_urb(vub300->command_out_urb, vub300->udev,
+			  usb_sndbulkpipe(vub300->udev, vub300->cmnd_out_ep)
+			  , &vub300->cmnd, sizeof(vub300->cmnd)
+			  , irqpoll_out_completed, vub300);
+	retval = usb_submit_urb(vub300->command_out_urb, GFP_ATOMIC);
+	if (0 > retval) {
+		vub300->usb_transport_fail = retval;
+		vub300_queue_poll_work(vub300, 1);
+		complete(&vub300->irqpoll_complete);
+		return;
+	} else {
+		return;
+	}
+}
+
+static void new_system_port_status(struct vub300_mmc_host *vub300)
+{
+	int old_card_present = vub300->card_present;
+	int new_card_present =
+	    (0x0001 & vub300->system_port_status.PortFlags) ? 1 : 0;
+	vub300->read_only =
+	    (0x0010 & vub300->system_port_status.PortFlags) ? 1 : 0;
+	if (new_card_present && !old_card_present) {
+		dev_info(&vub300->udev->dev, "card just inserted\n");
+		vub300->card_present = 1;
+		vub300->bus_width = 0;
+		if (disable_offload_processing)
+			strncpy(vub300->vub_name, "EMPTY Processing Disabled",
+				sizeof(vub300->vub_name));
+		else
+			vub300->vub_name[0] = 0;
+		mmc_detect_change(vub300->mmc, 1);
+		return;
+	} else if (!new_card_present && old_card_present) {
+		dev_info(&vub300->udev->dev, "card just ejected\n");
+		vub300->card_present = 0;
+		mmc_detect_change(vub300->mmc, 0);
+		return;
+	} else {
+		return;
+	}
+}
+
+static void __add_offloaded_reg_to_fifo(struct vub300_mmc_host *vub300,
+					struct Offload_Registers_Access
+					*register_access, u8 Function)
+{
+	u8 r =
+	    vub300->fn[Function].offload_point +
+	    vub300->fn[Function].offload_count;
+	memcpy(&vub300->fn[Function].reg[MAXREGMASK & r]
+	       , register_access, sizeof(struct Offload_Registers_Access));
+	vub300->fn[Function].offload_count += 1;
+	vub300->total_offload_count += 1;
+}
+
+static void add_offloaded_reg(struct vub300_mmc_host *vub300,
+			      struct Offload_Registers_Access *register_access)
+{
+	u32 Register = ((0x03 & register_access->Command_Byte[0]) << 15)
+	    | ((0xFF & register_access->Command_Byte[1]) << 7)
+	    | ((0xFE & register_access->Command_Byte[2]) >> 1);
+	u8 Function = ((0x70 & register_access->Command_Byte[0]) >> 4);
+	u8 regs = vub300->dynamic_register_count;
+	u8 i = 0;
+	while (0 < regs-- && 1 == vub300->sdio_register[i].activate) {
+		if ((vub300->sdio_register[i].func_num == Function)
+		    && (vub300->sdio_register[i].sdio_reg == Register)
+		    ) {
+			if (0 == vub300->sdio_register[i].prepared)
+				vub300->sdio_register[i].prepared = 1;
+			vub300->sdio_register[i].response =
+			    register_access->Respond_Byte[2];
+			vub300->sdio_register[i].regvalue =
+			    register_access->Respond_Byte[3];
+			return;
+		} else {
+			i += 1;
+			continue;
+		}
+	};
+	__add_offloaded_reg_to_fifo(vub300, register_access, Function);
+}
+
+static void check_vub300_port_status(struct vub300_mmc_host *vub300)
+{
+	/*
+	 * cmd_mutex is held by vub300_pollwork_thread,
+	 * vub300_deadwork_thread or vub300_cmndwork_thread
+	 */
+	int retval;
+	retval =
+	    usb_control_msg(vub300->udev, usb_rcvctrlpipe(vub300->udev, 0),
+			    GET_SYSTEM_PORT_STATUS,
+			    USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			    0x0000, 0x0000, &vub300->system_port_status,
+			    sizeof(vub300->system_port_status), HZ);
+	if (sizeof(vub300->system_port_status) == retval)
+		new_system_port_status(vub300);
+}
+
+static void __vub300_irqpoll_response(struct vub300_mmc_host *vub300)
+{
+	/*
+	 * cmd_mutex is held by vub300_pollwork_thread
+	 */
+	if (0 == vub300->command_res_urb->actual_length) {
+		mutex_unlock(&vub300->cmd_mutex);
+		kref_put(&vub300->kref, vub300_delete);
+		return;
+	} else if (RESPONSE_INTERRUPT == vub300->resp.common.HeaderType) {
+		mutex_lock(&vub300->irq_mutex);
+		if (vub300->irq_enabled)
+			mmc_signal_sdio_irq(vub300->mmc);
+		else
+			vub300->irqs_queued += 1;
+		vub300->irq_disabled = 1;
+		mutex_unlock(&vub300->irq_mutex);
+		mutex_unlock(&vub300->cmd_mutex);
+		kref_put(&vub300->kref, vub300_delete);
+		return;
+	} else if (RESPONSE_ERROR == vub300->resp.common.HeaderType) {
+		if (SD_ERROR_NO_DEVICE == vub300->resp.error.ErrorCode)
+			check_vub300_port_status(vub300);
+		mutex_unlock(&vub300->cmd_mutex);
+		vub300_queue_poll_work(vub300, HZ);
+		kref_put(&vub300->kref, vub300_delete);
+		return;
+	} else if (RESPONSE_STATUS == vub300->resp.common.HeaderType) {
+		vub300->system_port_status = vub300->resp.status;
+		new_system_port_status(vub300);
+		mutex_unlock(&vub300->cmd_mutex);
+		if (!vub300->card_present)
+			vub300_queue_poll_work(vub300, HZ / 5);
+		kref_put(&vub300->kref, vub300_delete);
+		return;
+	} else if (RESPONSE_IRQ_DISABLED == vub300->resp.common.HeaderType) {
+		int offloaded_data_length = vub300->resp.common.HeaderSize - 3;
+		int register_count = offloaded_data_length >> 3;
+		int ri = 0;
+		while (register_count--) {
+			add_offloaded_reg(vub300, &vub300->resp.irq.reg[ri]);
+			ri += 1;
+		}
+		mutex_lock(&vub300->irq_mutex);
+		if (vub300->irq_enabled)
+			mmc_signal_sdio_irq(vub300->mmc);
+		else
+			vub300->irqs_queued += 1;
+		vub300->irq_disabled = 1;
+		mutex_unlock(&vub300->irq_mutex);
+		mutex_unlock(&vub300->cmd_mutex);
+		kref_put(&vub300->kref, vub300_delete);
+		return;
+	} else if (RESPONSE_IRQ_ENABLED == vub300->resp.common.HeaderType) {
+		int offloaded_data_length = vub300->resp.common.HeaderSize - 3;
+		int register_count = offloaded_data_length >> 3;
+		int ri = 0;
+		while (register_count--) {
+			add_offloaded_reg(vub300, &vub300->resp.irq.reg[ri]);
+			ri += 1;
+		}
+		mutex_lock(&vub300->irq_mutex);
+		if (vub300->irq_enabled)
+			mmc_signal_sdio_irq(vub300->mmc);
+		else if (vub300->irqs_queued)
+			vub300->irqs_queued += 1;
+		else
+			vub300->irqs_queued += 1;
+		vub300->irq_disabled = 0;
+		mutex_unlock(&vub300->irq_mutex);
+		mutex_unlock(&vub300->cmd_mutex);
+		kref_put(&vub300->kref, vub300_delete);
+		return;
+	} else if (RESPONSE_NO_INTERRUPT == vub300->resp.common.HeaderType) {
+		vub300_queue_poll_work(vub300, 1);
+		mutex_unlock(&vub300->cmd_mutex);
+		kref_put(&vub300->kref, vub300_delete);
+		return;
+	} else {
+		mutex_unlock(&vub300->cmd_mutex);
+		kref_put(&vub300->kref, vub300_delete);
+		return;
+	}
+}
+
+static void __do_poll(struct vub300_mmc_host *vub300)
+{
+	/*
+	 * cmd_mutex is held by vub300_pollwork_thread
+	 */
+	long commretval;
+	mod_timer(&vub300->inactivity_timer, jiffies + HZ);
+	init_completion(&vub300->irqpoll_complete);
+	send_irqpoll(vub300);
+	commretval =
+	    wait_for_completion_timeout(&vub300->irqpoll_complete,
+					msecs_to_jiffies(500));
+	if (vub300->usb_transport_fail) {
+		mutex_unlock(&vub300->cmd_mutex);
+		kref_put(&vub300->kref, vub300_delete);
+		return;
+	} else if (0 == commretval) {
+		vub300->usb_timed_out = 1;
+		usb_kill_urb(vub300->command_out_urb);
+		usb_kill_urb(vub300->command_res_urb);
+		mutex_unlock(&vub300->cmd_mutex);
+		kref_put(&vub300->kref, vub300_delete);
+		return;
+	} else if (0 > commretval) {
+		vub300_queue_poll_work(vub300, 1);
+		mutex_unlock(&vub300->cmd_mutex);
+		kref_put(&vub300->kref, vub300_delete);
+		return;
+	} else {		/*(0 < commretval) */
+
+		__vub300_irqpoll_response(vub300);
+		return;
+	}
+}
+
+/* this thread runs only when the driver
+ * is trying to poll the device for an IRQ
+ */
+static void vub300_pollwork_thread(struct work_struct *work)
+{				/* NOT irq */
+	struct vub300_mmc_host *vub300 =
+	    container_of(work, struct vub300_mmc_host, pollwork.work);
+	if (!vub300->interface) {
+		kref_put(&vub300->kref, vub300_delete);
+		return;
+	}
+	mutex_lock(&vub300->cmd_mutex);
+	if (vub300->cmd) {
+		mutex_unlock(&vub300->cmd_mutex);
+		vub300_queue_poll_work(vub300, 1);
+		kref_put(&vub300->kref, vub300_delete);
+	} else if (!vub300->card_present) {
+		mutex_unlock(&vub300->cmd_mutex);
+		kref_put(&vub300->kref, vub300_delete);
+	} else {		/* vub300->card_present */
+		mutex_lock(&vub300->irq_mutex);
+		if (!vub300->irq_enabled) {
+			mutex_unlock(&vub300->irq_mutex);
+			mutex_unlock(&vub300->cmd_mutex);
+			kref_put(&vub300->kref, vub300_delete);
+		} else if (vub300->irqs_queued) {
+			vub300->irqs_queued -= 1;
+			mmc_signal_sdio_irq(vub300->mmc);
+			mod_timer(&vub300->inactivity_timer, jiffies + HZ);
+			mutex_unlock(&vub300->irq_mutex);
+			mutex_unlock(&vub300->cmd_mutex);
+			kref_put(&vub300->kref, vub300_delete);
+		} else {	/* NOT vub300->irqs_queued */
+			mutex_unlock(&vub300->irq_mutex);
+			__do_poll(vub300);
+		}
+	}
+}
+
+static void vub300_deadwork_thread(struct work_struct *work)
+{				/* NOT irq */
+	struct vub300_mmc_host *vub300 =
+	    container_of(work, struct vub300_mmc_host, deadwork);
+	if (!vub300->interface) {
+		kref_put(&vub300->kref, vub300_delete);
+		return;
+	}
+	mutex_lock(&vub300->cmd_mutex);
+	if (vub300->cmd) {
+		/*
+		 * a command got in as the inactivity
+		 * timer expired - so we just let the
+		 * processing of the command show if
+		 * the device is dead
+		 */
+	} else if (vub300->card_present) {
+		check_vub300_port_status(vub300);
+	} else if (vub300->mmc && vub300->mmc->card
+		   && mmc_card_present(vub300->mmc->card)) {
+		/*
+		 * the MMC core must not have responded
+		 * to the previous indication - lets
+		 * hope that it eventually does so we
+		 * will just ignore this for now
+		 */
+	} else {
+		check_vub300_port_status(vub300);
+	}
+	mod_timer(&vub300->inactivity_timer, jiffies + HZ);
+	mutex_unlock(&vub300->cmd_mutex);
+	kref_put(&vub300->kref, vub300_delete);
+}
+
+static void vub300_inactivity_timer_expired(unsigned long data)
+{				/* softirq */
+	struct vub300_mmc_host *vub300 = (struct vub300_mmc_host *)data;
+	if (!vub300->interface) {
+		kref_put(&vub300->kref, vub300_delete);
+	} else if (vub300->cmd) {
+		mod_timer(&vub300->inactivity_timer, jiffies + HZ);
+	} else {
+		vub300_queue_dead_work(vub300);
+		mod_timer(&vub300->inactivity_timer, jiffies + HZ);
+	}
+}
+
+static int vub300_response_error(u8 ErrorCode)
+{
+	switch (ErrorCode) {
+	case SD_ERROR_PIO_TIMEOUT:
+	case SD_ERROR_1BIT_TIMEOUT:
+	case SD_ERROR_4BIT_TIMEOUT:
+		return -ETIMEDOUT;
+	case SD_ERROR_STAT_DATA:
+	case SD_ERROR_OVERRUN:
+	case SD_ERROR_STAT_CMD:
+	case SD_ERROR_STAT_CMD_TIMEOUT:
+	case SD_ERROR_SDCRDY_STUCK:
+	case SD_ERROR_UNHANDLED:
+	case SD_ERROR_1BIT_CRC_WRONG:
+	case SD_ERROR_4BIT_CRC_WRONG:
+	case SD_ERROR_1BIT_CRC_ERROR:
+	case SD_ERROR_4BIT_CRC_ERROR:
+	case SD_ERROR_NO_CMD_ENDBIT:
+	case SD_ERROR_NO_1BIT_DATEND:
+	case SD_ERROR_NO_4BIT_DATEND:
+	case SD_ERROR_1BIT_DATA_TIMEOUT:
+	case SD_ERROR_4BIT_DATA_TIMEOUT:
+	case SD_ERROR_1BIT_UNEXPECTED_TIMEOUT:
+	case SD_ERROR_4BIT_UNEXPECTED_TIMEOUT:
+		return -EILSEQ;
+	case 33:
+		return -EILSEQ;
+	case SD_ERROR_ILLEGAL_COMMAND:
+		return -EINVAL;
+	case SD_ERROR_NO_DEVICE:
+		return -ENOMEDIUM;
+	default:
+		return -ENODEV;
+	}
+}
+
+static void command_res_completed(struct urb *urb)
+{				/* urb completion handler - hardirq */
+	/*
+	 * cmd_mutex is held by vub300_cmndwork_thread
+	 */
+	struct vub300_mmc_host *vub300 = (struct vub300_mmc_host *)urb->context;
+	if (urb->status) {
+		/*
+		 * we have to let the initiator handle the error
+		 */
+	} else if (0 == vub300->command_res_urb->actual_length) {
+		/*
+		 * we have seen this happen once or twice and
+		 * we suspect a buggy USB host controller
+		 */
+	} else if (!vub300->data) {
+		/*
+		 * this means that the command (typically CMD52) suceeded
+		 */
+	} else if (0x02 != vub300->resp.common.HeaderType) {
+		/*
+		 * this is an error response from the VUB300 chip
+		 * and we let the initiator handle it
+		 */
+	} else if (vub300->urb) {
+		vub300->cmd->error =
+		    vub300_response_error(vub300->resp.error.ErrorCode);
+		usb_unlink_urb(vub300->urb);
+	} else {
+		vub300->cmd->error =
+		    vub300_response_error(vub300->resp.error.ErrorCode);
+		usb_sg_cancel(&vub300->sg_request);
+	}
+	complete(&vub300->command_complete);	/* got_response_in */
+}
+
+static void command_out_completed(struct urb *urb)
+{				/* urb completion handler - hardirq */
+	/*
+	 * cmd_mutex is held by vub300_cmndwork_thread
+	 */
+	struct vub300_mmc_host *vub300 = (struct vub300_mmc_host *)urb->context;
+	if (urb->status) {
+		complete(&vub300->command_complete);
+	} else {
+		int ret;
+		unsigned int pipe =
+		    usb_rcvbulkpipe(vub300->udev, vub300->cmnd_res_ep);
+		usb_fill_bulk_urb(vub300->command_res_urb, vub300->udev, pipe,
+				  &vub300->resp, sizeof(vub300->resp),
+				  command_res_completed, vub300);
+		vub300->command_res_urb->actual_length = 0;
+		ret = usb_submit_urb(vub300->command_res_urb, GFP_NOIO);
+		if (0 == ret) {
+			/*
+			 * the urb completion handler will call
+			 * our completion handler
+			 */
+		} else {
+			/*
+			 * and thus we only call it directly
+			 * when it will not be called
+			 */
+			complete(&vub300->command_complete);
+		}
+	}
+}
+
+/*
+ * the STUFF bits are masked out for the comparisons
+ */
+static void snoop_block_size_and_bus_width(struct vub300_mmc_host *vub300,
+					   u32 cmd_arg)
+{
+	if (0x80022200 == (0xFBFFFE00 & cmd_arg))
+		vub300->fbs[1] = (cmd_arg << 8) | (0x00FF & vub300->fbs[1]);
+	else if (0x80022000 == (0xFBFFFE00 & cmd_arg))
+		vub300->fbs[1] = (0xFF & cmd_arg) | (0xFF00 & vub300->fbs[1]);
+	else if (0x80042200 == (0xFBFFFE00 & cmd_arg))
+		vub300->fbs[2] = (cmd_arg << 8) | (0x00FF & vub300->fbs[2]);
+	else if (0x80042000 == (0xFBFFFE00 & cmd_arg))
+		vub300->fbs[2] = (0xFF & cmd_arg) | (0xFF00 & vub300->fbs[2]);
+	else if (0x80062200 == (0xFBFFFE00 & cmd_arg))
+		vub300->fbs[3] = (cmd_arg << 8) | (0x00FF & vub300->fbs[3]);
+	else if (0x80062000 == (0xFBFFFE00 & cmd_arg))
+		vub300->fbs[3] = (0xFF & cmd_arg) | (0xFF00 & vub300->fbs[3]);
+	else if (0x80082200 == (0xFBFFFE00 & cmd_arg))
+		vub300->fbs[4] = (cmd_arg << 8) | (0x00FF & vub300->fbs[4]);
+	else if (0x80082000 == (0xFBFFFE00 & cmd_arg))
+		vub300->fbs[4] = (0xFF & cmd_arg) | (0xFF00 & vub300->fbs[4]);
+	else if (0x800A2200 == (0xFBFFFE00 & cmd_arg))
+		vub300->fbs[5] = (cmd_arg << 8) | (0x00FF & vub300->fbs[5]);
+	else if (0x800A2000 == (0xFBFFFE00 & cmd_arg))
+		vub300->fbs[5] = (0xFF & cmd_arg) | (0xFF00 & vub300->fbs[5]);
+	else if (0x800C2200 == (0xFBFFFE00 & cmd_arg))
+		vub300->fbs[6] = (cmd_arg << 8) | (0x00FF & vub300->fbs[6]);
+	else if (0x800C2000 == (0xFBFFFE00 & cmd_arg))
+		vub300->fbs[6] = (0xFF & cmd_arg) | (0xFF00 & vub300->fbs[6]);
+	else if (0x800E2200 == (0xFBFFFE00 & cmd_arg))
+		vub300->fbs[7] = (cmd_arg << 8) | (0x00FF & vub300->fbs[7]);
+	else if (0x800E2000 == (0xFBFFFE00 & cmd_arg))
+		vub300->fbs[7] = (0xFF & cmd_arg) | (0xFF00 & vub300->fbs[7]);
+	else if (0x80000E00 == (0xFBFFFE03 & cmd_arg))
+		vub300->bus_width = 1;
+	else if (0x80000E02 == (0xFBFFFE03 & cmd_arg))
+		vub300->bus_width = 4;
+}
+
+static void send_command(struct vub300_mmc_host *vub300)
+{
+	/*
+	 * cmd_mutex is held by vub300_cmndwork_thread
+	 */
+	struct mmc_command *cmd = vub300->cmd;
+	struct mmc_data *data = vub300->data;
+	int retval;
+	u8 ResponseType;
+	if (vub300->app_spec) {
+		if (6 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+			if (0x00000000 == (0x00000003 & cmd->arg))
+				vub300->bus_width = 1;
+			else if (0x00000002 == (0x00000003 & cmd->arg))
+				vub300->bus_width = 4;
+			else
+				dev_err(&vub300->udev->dev,
+					"unexpected ACMD6 bus_width=%d\n",
+					0x00000003 & cmd->arg);
+		} else if (13 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (22 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (23 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (41 == cmd->opcode) {
+			ResponseType = SDRT_3;
+			vub300->resp_len = 6;
+		} else if (42 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (51 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (55 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else {
+			vub300->resp_len = 0;
+			cmd->error = -EINVAL;
+			complete(&vub300->command_complete);
+			return;
+		}
+		vub300->app_spec = 0;
+	} else {
+		if (0 == cmd->opcode) {
+			ResponseType = SDRT_NONE;
+			vub300->resp_len = 0;
+		} else if (1 == cmd->opcode) {
+			ResponseType = SDRT_3;
+			vub300->resp_len = 6;
+		} else if (2 == cmd->opcode) {
+			ResponseType = SDRT_2;
+			vub300->resp_len = 17;
+		} else if (3 == cmd->opcode) {
+			ResponseType = SDRT_6;
+			vub300->resp_len = 6;
+		} else if (4 == cmd->opcode) {
+			ResponseType = SDRT_NONE;
+			vub300->resp_len = 0;
+		} else if (5 == cmd->opcode) {
+			ResponseType = SDRT_4;
+			vub300->resp_len = 6;
+		} else if (6 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (7 == cmd->opcode) {
+			ResponseType = SDRT_1B;
+			vub300->resp_len = 6;
+		} else if (8 == cmd->opcode) {
+			ResponseType = SDRT_7;
+			vub300->resp_len = 6;
+		} else if (9 == cmd->opcode) {
+			ResponseType = SDRT_2;
+			vub300->resp_len = 17;
+		} else if (10 == cmd->opcode) {
+			ResponseType = SDRT_2;
+			vub300->resp_len = 17;
+		} else if (12 == cmd->opcode) {
+			ResponseType = SDRT_1B;
+			vub300->resp_len = 6;
+		} else if (13 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (15 == cmd->opcode) {
+			ResponseType = SDRT_NONE;
+			vub300->resp_len = 0;
+		} else if (16 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+			vub300->fbs[0] = 0xFFFF & cmd->arg;
+			vub300->fbs[1] = 0xFFFF & cmd->arg;
+			vub300->fbs[2] = 0xFFFF & cmd->arg;
+			vub300->fbs[3] = 0xFFFF & cmd->arg;
+			vub300->fbs[4] = 0xFFFF & cmd->arg;
+			vub300->fbs[5] = 0xFFFF & cmd->arg;
+			vub300->fbs[6] = 0xFFFF & cmd->arg;
+			vub300->fbs[7] = 0xFFFF & cmd->arg;
+		} else if (17 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (18 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (24 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (25 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (27 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (28 == cmd->opcode) {
+			ResponseType = SDRT_1B;
+			vub300->resp_len = 6;
+		} else if (29 == cmd->opcode) {
+			ResponseType = SDRT_1B;
+			vub300->resp_len = 6;
+		} else if (30 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (32 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (33 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (38 == cmd->opcode) {
+			ResponseType = SDRT_1B;
+			vub300->resp_len = 6;
+		} else if (42 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (52 == cmd->opcode) {
+			ResponseType = SDRT_5;
+			vub300->resp_len = 6;
+			snoop_block_size_and_bus_width(vub300, cmd->arg);
+		} else if (53 == cmd->opcode) {
+			ResponseType = SDRT_5;
+			vub300->resp_len = 6;
+		} else if (55 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+			vub300->app_spec = 1;
+		} else if (56 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else {
+			vub300->resp_len = 0;
+			cmd->error = -EINVAL;
+			complete(&vub300->command_complete);
+			return;
+		}
+	}
+	/*
+	 * it is a shame that we can not use "sizeof(struct SD_Command_Header)"
+	 * this is because the packet _must_ be padded to 64 bytes
+	 */
+	vub300->cmnd.head.HeaderSize = 20;
+	vub300->cmnd.head.HeaderType = 0x00;
+	vub300->cmnd.head.PortNumber = 0;	/* "0" means port 1 */
+	vub300->cmnd.head.Command_Type = 0x00;	/* standard read command */
+	vub300->cmnd.head.ResponseType = ResponseType;
+	vub300->cmnd.head.CommandIndex = cmd->opcode;
+	vub300->cmnd.head.Arguments[0] = cmd->arg >> 24;
+	vub300->cmnd.head.Arguments[1] = cmd->arg >> 16;
+	vub300->cmnd.head.Arguments[2] = cmd->arg >> 8;
+	vub300->cmnd.head.Arguments[3] = cmd->arg >> 0;
+	if (52 == cmd->opcode) {
+		int fn = 0x7 & (cmd->arg >> 28);
+		vub300->cmnd.head.BlockCount[0] = 0;
+		vub300->cmnd.head.BlockCount[1] = 0;
+		vub300->cmnd.head.BlockSize[0] = (vub300->fbs[fn] >> 8) & 0xFF;
+		vub300->cmnd.head.BlockSize[1] = (vub300->fbs[fn] >> 0) & 0xFF;
+		vub300->cmnd.head.Command_Type = 0x00;
+		vub300->cmnd.head.TransferSize[0] = 0;
+		vub300->cmnd.head.TransferSize[1] = 0;
+		vub300->cmnd.head.TransferSize[2] = 0;
+		vub300->cmnd.head.TransferSize[3] = 0;
+	} else if (!data) {
+		vub300->cmnd.head.BlockCount[0] = 0;
+		vub300->cmnd.head.BlockCount[1] = 0;
+		vub300->cmnd.head.BlockSize[0] = (vub300->fbs[0] >> 8) & 0xFF;
+		vub300->cmnd.head.BlockSize[1] = (vub300->fbs[0] >> 0) & 0xFF;
+		vub300->cmnd.head.Command_Type = 0x00;
+		vub300->cmnd.head.TransferSize[0] = 0;
+		vub300->cmnd.head.TransferSize[1] = 0;
+		vub300->cmnd.head.TransferSize[2] = 0;
+		vub300->cmnd.head.TransferSize[3] = 0;
+	} else if (53 == cmd->opcode) {
+		int fn = 0x7 & (cmd->arg >> 28);
+		if (0x08 & vub300->cmnd.head.Arguments[0]) {	/* BLOCK MODE */
+			vub300->cmnd.head.BlockCount[0] =
+			    (data->blocks >> 8) & 0xFF;
+			vub300->cmnd.head.BlockCount[1] =
+			    (data->blocks >> 0) & 0xFF;
+			vub300->cmnd.head.BlockSize[0] =
+			    (data->blksz >> 8) & 0xFF;
+			vub300->cmnd.head.BlockSize[1] =
+			    (data->blksz >> 0) & 0xFF;
+		} else {	/* BYTE MODE */
+			vub300->cmnd.head.BlockCount[0] = 0;
+			vub300->cmnd.head.BlockCount[1] = 0;
+			vub300->cmnd.head.BlockSize[0] =
+			    (vub300->datasize >> 8) & 0xFF;
+			vub300->cmnd.head.BlockSize[1] =
+			    (vub300->datasize >> 0) & 0xFF;
+		}
+		vub300->cmnd.head.Command_Type =
+		    (MMC_DATA_READ & data->flags) ? 0x00 : 0x80;
+		vub300->cmnd.head.TransferSize[0] =
+		    (vub300->datasize >> 24) & 0xFF;
+		vub300->cmnd.head.TransferSize[1] =
+		    (vub300->datasize >> 16) & 0xFF;
+		vub300->cmnd.head.TransferSize[2] =
+		    (vub300->datasize >> 8) & 0xFF;
+		vub300->cmnd.head.TransferSize[3] =
+		    (vub300->datasize >> 0) & 0xFF;
+		if (vub300->datasize < vub300->fbs[fn]) {
+			vub300->cmnd.head.BlockCount[0] = 0;
+			vub300->cmnd.head.BlockCount[1] = 0;
+		}
+	} else {
+		vub300->cmnd.head.BlockCount[0] = (data->blocks >> 8) & 0xFF;
+		vub300->cmnd.head.BlockCount[1] = (data->blocks >> 0) & 0xFF;
+		vub300->cmnd.head.BlockSize[0] = (data->blksz >> 8) & 0xFF;
+		vub300->cmnd.head.BlockSize[1] = (data->blksz >> 0) & 0xFF;
+		vub300->cmnd.head.Command_Type =
+		    (MMC_DATA_READ & data->flags) ? 0x00 : 0x80;
+		vub300->cmnd.head.TransferSize[0] =
+		    (vub300->datasize >> 24) & 0xFF;
+		vub300->cmnd.head.TransferSize[1] =
+		    (vub300->datasize >> 16) & 0xFF;
+		vub300->cmnd.head.TransferSize[2] =
+		    (vub300->datasize >> 8) & 0xFF;
+		vub300->cmnd.head.TransferSize[3] =
+		    (vub300->datasize >> 0) & 0xFF;
+		if (vub300->datasize < vub300->fbs[0]) {
+			vub300->cmnd.head.BlockCount[0] = 0;
+			vub300->cmnd.head.BlockCount[1] = 0;
+		}
+	}
+	if (vub300->cmnd.head.BlockSize[0] || vub300->cmnd.head.BlockSize[1]) {
+		u16 BlockSize = vub300->cmnd.head.BlockSize[1]
+		    | (vub300->cmnd.head.BlockSize[0] << 8);
+		u16 BlockBoundary =
+		    FIRMWARE_BLOCK_BOUNDARY -
+		    (FIRMWARE_BLOCK_BOUNDARY % BlockSize);
+		vub300->cmnd.head.BlockBoundary[0] =
+		    (BlockBoundary >> 8) & 0xFF;
+		vub300->cmnd.head.BlockBoundary[1] =
+		    (BlockBoundary >> 0) & 0xFF;
+	} else {
+		vub300->cmnd.head.BlockBoundary[0] = 0;
+		vub300->cmnd.head.BlockBoundary[1] = 0;
+	}
+	usb_fill_bulk_urb(vub300->command_out_urb, vub300->udev,
+			  usb_sndbulkpipe(vub300->udev, vub300->cmnd_out_ep),
+			  &vub300->cmnd, sizeof(vub300->cmnd),
+			  command_out_completed, vub300);
+	retval = usb_submit_urb(vub300->command_out_urb, GFP_ATOMIC);
+	if (0 > retval) {
+		cmd->error = retval;
+		complete(&vub300->command_complete);
+		return;
+	} else {
+		return;
+	}
+}
+
+/*
+ * timer callback runs in atomic mode
+ *       so it cannot call usb_kill_urb()
+ */
+static void vub300_sg_timed_out(unsigned long data)
+{
+	struct vub300_mmc_host *vub300 = (struct vub300_mmc_host *)data;
+	vub300->usb_timed_out = 1;
+	usb_sg_cancel(&vub300->sg_request);
+	usb_unlink_urb(vub300->command_out_urb);
+	usb_unlink_urb(vub300->command_res_urb);
+}
+
+static u16 roundup_to_multiple_of_64(u16 number)
+{
+	return 0xFFC0 & (0x3F + number);
+}
+
+/*
+ * this is a separate function to solve the 80 column width restriction
+ */
+static void __download_offload_pseudocode(struct vub300_mmc_host *vub300,
+					  const struct firmware *fw)
+{
+	u8 register_count = 0;
+	u16 transfer_size = 0;
+	u16 interrupt_size = 0;
+	const u8 *data = fw->data;
+	int size = fw->size;
+	u8 c;
+	dev_info(&vub300->udev->dev, "using %s for SDIO offload processing\n",
+		 vub300->vub_name);
+	do {
+		c = *data++;
+	} while (size-- && c);	/* skip comment */
+	dev_info(&vub300->udev->dev, "using offload firmware %s %s\n", fw->data,
+		 vub300->vub_name);
+	if (size < 4) {
+		dev_err(&vub300->udev->dev,
+			"corrupt offload pseudocode in firmware %s\n",
+			vub300->vub_name);
+		strncpy(vub300->vub_name, "corrupt offload pseudocode",
+			sizeof(vub300->vub_name));
+		return;
+	}
+	interrupt_size += *data++;
+	size -= 1;
+	interrupt_size <<= 8;
+	interrupt_size += *data++;
+	size -= 1;
+	if (interrupt_size < size) {
+		u16 xfer_length = roundup_to_multiple_of_64(interrupt_size);
+		u8 *xfer_buffer = kmalloc(xfer_length, GFP_KERNEL);
+		if (xfer_buffer) {
+			int retval;
+			memcpy(xfer_buffer, data, interrupt_size);
+			memset(xfer_buffer + interrupt_size, 0,
+			       xfer_length - interrupt_size);
+			size -= interrupt_size;
+			data += interrupt_size;
+			retval =
+			    usb_control_msg(vub300->udev,
+					    usb_sndctrlpipe(vub300->udev, 0),
+					    SET_INTERRUPT_PSEUDOCODE,
+					    USB_DIR_OUT | USB_TYPE_VENDOR |
+					    USB_RECIP_DEVICE, 0x0000, 0x0000,
+					    xfer_buffer, xfer_length, HZ);
+			kfree(xfer_buffer);
+			if (retval < 0) {
+				strncpy(vub300->vub_name,
+					"SDIO pseudocode download failed",
+					sizeof(vub300->vub_name));
+				return;
+			}
+		} else {
+			dev_err(&vub300->udev->dev,
+				"not enough memory for xfer buffer to send"
+				" INTERRUPT_PSEUDOCODE for %s %s\n", fw->data,
+				vub300->vub_name);
+			strncpy(vub300->vub_name,
+				"SDIO interrupt pseudocode download failed",
+				sizeof(vub300->vub_name));
+			return;
+		}
+	} else {
+		dev_err(&vub300->udev->dev,
+			"corrupt interrupt pseudocode in firmware %s %s\n",
+			fw->data, vub300->vub_name);
+		strncpy(vub300->vub_name, "corrupt interrupt pseudocode",
+			sizeof(vub300->vub_name));
+		return;
+	}
+	transfer_size += *data++;
+	size -= 1;
+	transfer_size <<= 8;
+	transfer_size += *data++;
+	size -= 1;
+	if (transfer_size < size) {
+		u16 xfer_length = roundup_to_multiple_of_64(transfer_size);
+		u8 *xfer_buffer = kmalloc(xfer_length, GFP_KERNEL);
+		if (xfer_buffer) {
+			int retval;
+			memcpy(xfer_buffer, data, transfer_size);
+			memset(xfer_buffer + transfer_size, 0,
+			       xfer_length - transfer_size);
+			size -= transfer_size;
+			data += transfer_size;
+			retval =
+			    usb_control_msg(vub300->udev,
+					    usb_sndctrlpipe(vub300->udev, 0),
+					    SET_TRANSFER_PSEUDOCODE,
+					    USB_DIR_OUT | USB_TYPE_VENDOR |
+					    USB_RECIP_DEVICE, 0x0000, 0x0000,
+					    xfer_buffer, xfer_length, HZ);
+			kfree(xfer_buffer);
+			if (retval < 0) {
+				strncpy(vub300->vub_name,
+					"SDIO pseudocode download failed",
+					sizeof(vub300->vub_name));
+				return;
+			}
+		} else {
+			dev_err(&vub300->udev->dev,
+				"not enough memory for xfer buffer to send"
+				" TRANSFER_PSEUDOCODE for %s %s\n", fw->data,
+				vub300->vub_name);
+			strncpy(vub300->vub_name,
+				"SDIO transfer pseudocode download failed",
+				sizeof(vub300->vub_name));
+			return;
+		}
+	} else {
+		dev_err(&vub300->udev->dev,
+			"corrupt transfer pseudocode in firmware %s %s\n",
+			fw->data, vub300->vub_name);
+		strncpy(vub300->vub_name, "corrupt transfer pseudocode",
+			sizeof(vub300->vub_name));
+		return;
+	}
+	register_count += *data++;
+	size -= 1;
+	if (register_count * 4 == size) {
+		int I = vub300->dynamic_register_count = register_count;
+		int i = 0;
+		while (I--) {
+			unsigned int func_num = 0;
+			vub300->sdio_register[i].func_num = *data++;
+			size -= 1;
+			func_num += *data++;
+			size -= 1;
+			func_num <<= 8;
+			func_num += *data++;
+			size -= 1;
+			func_num <<= 8;
+			func_num += *data++;
+			size -= 1;
+			vub300->sdio_register[i].sdio_reg = func_num;
+			vub300->sdio_register[i].activate = 1;
+			vub300->sdio_register[i].prepared = 0;
+			i += 1;
+		}
+		dev_info(&vub300->udev->dev,
+			 "initialized %d dynamic pseudocode registers\n",
+			 vub300->dynamic_register_count);
+		return;
+	} else {
+		dev_err(&vub300->udev->dev,
+			"corrupt dynamic registers in firmware %s\n",
+			vub300->vub_name);
+		strncpy(vub300->vub_name, "corrupt dynamic registers",
+			sizeof(vub300->vub_name));
+		return;
+	}
+}
+
+/*
+ * if the binary containing the EMPTY PseudoCode can not be found
+ * vub300->vub_name is set anyway in order to prevent an automatic retry
+ */
+static void download_offload_pseudocode(struct vub300_mmc_host *vub300)
+{
+	struct mmc_card *card = vub300->mmc->card;
+	int sdio_funcs = card->sdio_funcs;
+	const struct firmware *fw = NULL;
+	int l = snprintf(vub300->vub_name, sizeof(vub300->vub_name),
+			 "vub_%04X%04X", card->cis.vendor, card->cis.device);
+	int N = sdio_funcs;
+	int n = 0;
+	int retval;
+	while (N--) {
+		struct sdio_func *sf = card->sdio_func[n++];
+		l += snprintf(vub300->vub_name + l,
+			      sizeof(vub300->vub_name) - l, "_%04X%04X",
+			      sf->vendor, sf->device);
+	};
+	snprintf(vub300->vub_name + l, sizeof(vub300->vub_name) - l, ".bin");
+	dev_info(&vub300->udev->dev, "requesting offload firmware %s\n",
+		 vub300->vub_name);
+	retval = request_firmware(&fw, vub300->vub_name, &card->dev);
+	if (0 > retval) {
+		strncpy(vub300->vub_name, "vub_default.bin",
+			sizeof(vub300->vub_name));
+		retval = request_firmware(&fw, vub300->vub_name, &card->dev);
+		if (0 > retval) {
+			strncpy(vub300->vub_name,
+				"no SDIO offload firmware found",
+				sizeof(vub300->vub_name));
+		} else {
+			__download_offload_pseudocode(vub300, fw);
+			release_firmware(fw);
+		}
+	} else {
+		__download_offload_pseudocode(vub300, fw);
+		release_firmware(fw);
+	}
+}
+
+static void vub300_usb_bulk_msg_completion(struct urb *urb)
+{				/* urb completion handler - hardirq */
+	complete((struct completion *)urb->context);
+}
+
+static int vub300_usb_bulk_msg(struct vub300_mmc_host *vub300,
+			       unsigned int pipe, void *data, int len,
+			       int *actual_length, int timeout_msecs)
+{
+	/*
+	 * cmd_mutex is held by vub300_cmndwork_thread
+	 */
+	struct usb_device *usb_dev = vub300->udev;
+	struct completion done;
+	int retval;
+	vub300->urb = usb_alloc_urb(0, GFP_KERNEL);
+	if (!vub300->urb)
+		return -ENOMEM;
+	usb_fill_bulk_urb(vub300->urb, usb_dev, pipe, data, len,
+			  vub300_usb_bulk_msg_completion, NULL);
+	init_completion(&done);
+	vub300->urb->context = &done;
+	vub300->urb->actual_length = 0;
+	retval = usb_submit_urb(vub300->urb, GFP_NOIO);
+	if (unlikely(retval))
+		goto out;
+	if (!wait_for_completion_timeout
+	    (&done, msecs_to_jiffies(timeout_msecs))) {
+		retval = -ETIMEDOUT;
+		usb_kill_urb(vub300->urb);
+	} else {
+		retval = vub300->urb->status;
+	}
+out:
+	*actual_length = vub300->urb->actual_length;
+	usb_free_urb(vub300->urb);
+	vub300->urb = NULL;
+	return retval;
+}
+
+static int __command_read_data(struct vub300_mmc_host *vub300,
+			       struct mmc_command *cmd, struct mmc_data *data)
+{
+	/*
+	 * cmd_mutex is held by vub300_cmndwork_thread
+	 */
+	int linear_length = vub300->datasize;
+	int padded_length = vub300->large_usb_packets ?
+	    ((511 + linear_length) >> 9) << 9 :
+	    ((63 + linear_length) >> 6) << 6;
+	if ((padded_length == linear_length) || !pad_input_to_usb_pkt) {
+		int result;
+		unsigned pipe;
+		pipe = usb_rcvbulkpipe(vub300->udev, vub300->data_inp_ep);
+		result = usb_sg_init(&vub300->sg_request, vub300->udev,
+				     pipe, 0, data->sg,
+				     data->sg_len, 0, GFP_NOIO);
+		if (0 > result) {
+			usb_unlink_urb(vub300->command_out_urb);
+			usb_unlink_urb(vub300->command_res_urb);
+			cmd->error = result;
+			data->bytes_xfered = 0;
+			return 0;
+		} else {
+			vub300->sg_transfer_timer.expires =
+			    jiffies + msecs_to_jiffies(2000 +
+						       (linear_length / 16384));
+			add_timer(&vub300->sg_transfer_timer);
+			usb_sg_wait(&vub300->sg_request);
+			del_timer(&vub300->sg_transfer_timer);
+			if (0 > vub300->sg_request.status) {
+				cmd->error = vub300->sg_request.status;
+				data->bytes_xfered = 0;
+				return 0;
+			} else {
+				data->bytes_xfered = vub300->datasize;
+				return linear_length;
+			}
+		}
+	} else {
+		u8 *buf = kmalloc(padded_length, GFP_KERNEL);
+		if (buf) {
+			int result;
+			unsigned pipe =
+			    usb_rcvbulkpipe(vub300->udev, vub300->data_inp_ep);
+			int actual_length = 0;
+			result =
+			    vub300_usb_bulk_msg(vub300, pipe, buf,
+						padded_length, &actual_length,
+						2000 + (padded_length / 16384));
+			if (0 > result) {
+				cmd->error = result;
+				data->bytes_xfered = 0;
+				kfree(buf);
+				return 0;
+			} else if (actual_length < linear_length) {
+				cmd->error = -EREMOTEIO;
+				data->bytes_xfered = 0;
+				kfree(buf);
+				return 0;
+			} else {
+				sg_copy_from_buffer(data->sg, data->sg_len, buf,
+						    linear_length);
+				kfree(buf);
+				data->bytes_xfered = vub300->datasize;
+				return linear_length;
+			}
+		} else {
+			cmd->error = -ENOMEM;
+			data->bytes_xfered = 0;
+			return 0;
+		}
+	}
+}
+
+static int __command_write_data(struct vub300_mmc_host *vub300,
+				struct mmc_command *cmd, struct mmc_data *data)
+{
+	/*
+	 * cmd_mutex is held by vub300_cmndwork_thread
+	 */
+	unsigned pipe = usb_sndbulkpipe(vub300->udev, vub300->data_out_ep);
+	int linear_length = vub300->datasize;
+	int modulo_64_length = linear_length & 0x003F;
+	int modulo_512_length = linear_length & 0x01FF;
+	if (64 > linear_length) {
+		int result;
+		int actual_length;
+		sg_copy_to_buffer(data->sg, data->sg_len,
+				  vub300->padded_buffer,
+				  sizeof(vub300->padded_buffer));
+		memset(vub300->padded_buffer + linear_length, 0,
+		       sizeof(vub300->padded_buffer) - linear_length);
+		result =
+		    vub300_usb_bulk_msg(vub300, pipe, vub300->padded_buffer,
+					sizeof(vub300->padded_buffer),
+					&actual_length,
+					2000 +
+					(sizeof(vub300->padded_buffer) /
+					 16384));
+		if (0 > result) {
+			cmd->error = result;
+			data->bytes_xfered = 0;
+		} else {
+			data->bytes_xfered = vub300->datasize;
+		}
+	} else if ((!vub300->large_usb_packets && (0 < modulo_64_length))
+		   || (vub300->large_usb_packets && (64 > modulo_512_length))
+	    ) {			/* don't you just love these work-rounds */
+		int padded_length = ((63 + linear_length) >> 6) << 6;
+		u8 *buf = kmalloc(padded_length, GFP_KERNEL);
+		if (buf) {
+			int result;
+			int actual_length;
+			sg_copy_to_buffer(data->sg, data->sg_len, buf,
+					  padded_length);
+			memset(buf + linear_length, 0,
+			       padded_length - linear_length);
+			result =
+			    vub300_usb_bulk_msg(vub300, pipe, buf,
+						padded_length, &actual_length,
+						2000 + padded_length / 16384);
+			kfree(buf);
+			if (0 > result) {
+				cmd->error = result;
+				data->bytes_xfered = 0;
+			} else {
+				data->bytes_xfered = vub300->datasize;
+			}
+		} else {
+			cmd->error = -ENOMEM;
+			data->bytes_xfered = 0;
+		}
+	} else {		/* no data padding required */
+		int result;
+		unsigned char buf[64 * 4];
+		sg_copy_to_buffer(data->sg, data->sg_len, buf, sizeof(buf));
+		result = usb_sg_init(&vub300->sg_request, vub300->udev,
+				     pipe, 0, data->sg,
+				     data->sg_len, 0, GFP_NOIO);
+		if (0 > result) {
+			usb_unlink_urb(vub300->command_out_urb);
+			usb_unlink_urb(vub300->command_res_urb);
+			cmd->error = result;
+			data->bytes_xfered = 0;
+		} else {
+			vub300->sg_transfer_timer.expires =
+			    jiffies + msecs_to_jiffies(2000 +
+						       linear_length / 16384);
+			add_timer(&vub300->sg_transfer_timer);
+			usb_sg_wait(&vub300->sg_request);
+			if (cmd->error) {
+				data->bytes_xfered = 0;
+			} else {
+				del_timer(&vub300->sg_transfer_timer);
+				if (0 > vub300->sg_request.status) {
+					cmd->error = vub300->sg_request.status;
+					data->bytes_xfered = 0;
+				} else {
+					data->bytes_xfered = vub300->datasize;
+				}
+			}
+		}
+	}
+	return linear_length;
+}
+
+static void __vub300_command_response(struct vub300_mmc_host *vub300,
+				      struct mmc_command *cmd,
+				      struct mmc_data *data, int data_length)
+{
+	/*
+	 * cmd_mutex is held by vub300_cmndwork_thread
+	 */
+	long respretval;
+	int msec_timeout = 1000 + data_length / 4;
+	respretval =
+	    wait_for_completion_timeout(&vub300->command_complete,
+					msecs_to_jiffies(msec_timeout));
+	if (0 == respretval) {	/* TIMED OUT */
+		/*
+		 * we don't know which of "out" and "res" if any failed
+		 */
+		int result;
+		vub300->usb_timed_out = 1;
+		usb_kill_urb(vub300->command_out_urb);
+		usb_kill_urb(vub300->command_res_urb);
+		cmd->error = -ETIMEDOUT;
+		result =
+		    usb_lock_device_for_reset(vub300->udev, vub300->interface);
+		if (result == 0) {
+			result = usb_reset_device(vub300->udev);
+			usb_unlock_device(vub300->udev);
+		}
+	} else if (0 > respretval) {
+		/*
+		 * we don't know which of "out" and "res" if any failed
+		 */
+		usb_kill_urb(vub300->command_out_urb);
+		usb_kill_urb(vub300->command_res_urb);
+		cmd->error = respretval;
+	} else if (cmd->error) {
+		/*
+		 * the error occured sending the command
+		 * or recieving the response
+		 */
+	} else if (vub300->command_out_urb->status) {
+		vub300->usb_transport_fail = vub300->command_out_urb->status;
+		cmd->error = -EPROTO == vub300->command_out_urb->status ?
+		    -ESHUTDOWN : vub300->command_out_urb->status;
+	} else if (vub300->command_res_urb->status) {
+		vub300->usb_transport_fail = vub300->command_res_urb->status;
+		cmd->error = -EPROTO == vub300->command_res_urb->status ?
+		    -ESHUTDOWN : vub300->command_res_urb->status;
+	} else if (0x00 == vub300->resp.common.HeaderType) {
+		/*
+		 * the command completed successfully
+		 * and there was no piggybacked data
+		 */
+	} else if (RESPONSE_ERROR == vub300->resp.common.HeaderType) {
+		cmd->error =
+		    vub300_response_error(vub300->resp.error.ErrorCode);
+		if (vub300->data)
+			usb_sg_cancel(&vub300->sg_request);
+	} else if (RESPONSE_PIGGYBACKED == vub300->resp.common.HeaderType) {
+		int offloaded_data_length =
+		    vub300->resp.common.HeaderSize -
+		    sizeof(struct SD_Register_Header);
+		int register_count = offloaded_data_length >> 3;
+		int ri = 0;
+		while (register_count--) {
+			add_offloaded_reg(vub300, &vub300->resp.pig.reg[ri]);
+			ri += 1;
+		}
+		vub300->resp.common.HeaderSize =
+		    sizeof(struct SD_Register_Header);
+		vub300->resp.common.HeaderType = 0x00;
+		cmd->error = 0;
+	} else if (RESPONSE_PIG_DISABLED == vub300->resp.common.HeaderType) {
+		int offloaded_data_length =
+		    vub300->resp.common.HeaderSize -
+		    sizeof(struct SD_Register_Header);
+		int register_count = offloaded_data_length >> 3;
+		int ri = 0;
+		while (register_count--) {
+			add_offloaded_reg(vub300, &vub300->resp.pig.reg[ri]);
+			ri += 1;
+		}
+		mutex_lock(&vub300->irq_mutex);
+		if (vub300->irqs_queued) {
+			vub300->irqs_queued += 1;
+		} else if (vub300->irq_enabled) {
+			vub300->irqs_queued += 1;
+			vub300_queue_poll_work(vub300, 0);
+		} else {
+			vub300->irqs_queued += 1;
+		}
+		vub300->irq_disabled = 1;
+		mutex_unlock(&vub300->irq_mutex);
+		vub300->resp.common.HeaderSize =
+		    sizeof(struct SD_Register_Header);
+		vub300->resp.common.HeaderType = 0x00;
+		cmd->error = 0;
+	} else if (RESPONSE_PIG_ENABLED == vub300->resp.common.HeaderType) {
+		int offloaded_data_length =
+		    vub300->resp.common.HeaderSize -
+		    sizeof(struct SD_Register_Header);
+		int register_count = offloaded_data_length >> 3;
+		int ri = 0;
+		while (register_count--) {
+			add_offloaded_reg(vub300, &vub300->resp.pig.reg[ri]);
+			ri += 1;
+		}
+		mutex_lock(&vub300->irq_mutex);
+		if (vub300->irqs_queued) {
+			vub300->irqs_queued += 1;
+		} else if (vub300->irq_enabled) {
+			vub300->irqs_queued += 1;
+			vub300_queue_poll_work(vub300, 0);
+		} else {
+			vub300->irqs_queued += 1;
+		}
+		vub300->irq_disabled = 0;
+		mutex_unlock(&vub300->irq_mutex);
+		vub300->resp.common.HeaderSize =
+		    sizeof(struct SD_Register_Header);
+		vub300->resp.common.HeaderType = 0x00;
+		cmd->error = 0;
+	} else {
+		cmd->error = -EINVAL;
+	}
+}
+
+static void construct_request_response(struct vub300_mmc_host *vub300,
+				       struct mmc_command *cmd)
+{
+	int resp_len = vub300->resp_len;
+	int less_cmd = (17 == resp_len) ? resp_len : resp_len - 1;
+	int bytes = 3 & less_cmd;
+	int words = less_cmd >> 2;
+	u8 *r = vub300->resp.response.CommandResponse;
+	if (3 == bytes) {
+		cmd->resp[words] = (r[1 + (words << 2)] << 24)
+		    | (r[2 + (words << 2)] << 16)
+		    | (r[3 + (words << 2)] << 8);
+	} else if (2 == bytes) {
+		cmd->resp[words] = (r[1 + (words << 2)] << 24)
+		    | (r[2 + (words << 2)] << 16);
+	} else if (1 == bytes) {
+		cmd->resp[words] = (r[1 + (words << 2)] << 24);
+	}
+	while (words-- > 0) {
+		cmd->resp[words] = (r[1 + (words << 2)] << 24)
+		    | (r[2 + (words << 2)] << 16)
+		    | (r[3 + (words << 2)] << 8)
+		    | (r[4 + (words << 2)] << 0);
+	}
+	if ((53 == cmd->opcode) && (0x000000FF & cmd->resp[0]))
+		cmd->resp[0] &= 0xFFFFFF00;
+}
+
+/*
+ * this thread runs only when there
+ * is an upper level command req outstanding
+ */
+static void vub300_cmndwork_thread(struct work_struct *work)
+{
+	struct vub300_mmc_host *vub300 =
+	    container_of(work, struct vub300_mmc_host, cmndwork);
+	if (!vub300->interface) {
+		kref_put(&vub300->kref, vub300_delete);
+		return;
+	} else {
+		struct mmc_request *req = vub300->req;
+		struct mmc_command *cmd = vub300->cmd;
+		struct mmc_data *data = vub300->data;
+		int data_length;
+		mutex_lock(&vub300->cmd_mutex);
+		init_completion(&vub300->command_complete);
+		if (likely(vub300->vub_name[0])
+		    || !vub300->mmc->card
+		    || !mmc_card_present(vub300->mmc->card)) {
+			/*
+			 * the name of the EMPTY Pseudo firmware file
+			 * is used as a flag to indicate that the file
+			 * has been already downloaded to the VUB300 chip
+			 */
+		} else if (0 == vub300->mmc->card->sdio_funcs) {
+			strncpy(vub300->vub_name, "SD memory device",
+				sizeof(vub300->vub_name));
+		} else {
+			download_offload_pseudocode(vub300);
+		}
+		send_command(vub300);
+		if (!data)
+			data_length = 0;
+		else if (MMC_DATA_READ & data->flags)
+			data_length = __command_read_data(vub300, cmd, data);
+		else
+			data_length = __command_write_data(vub300, cmd, data);
+		__vub300_command_response(vub300, cmd, data, data_length);
+		vub300->req = NULL;
+		vub300->cmd = NULL;
+		vub300->data = NULL;
+		if (cmd->error) {
+			if (-ENOMEDIUM == cmd->error)
+				check_vub300_port_status(vub300);
+			mutex_unlock(&vub300->cmd_mutex);
+			mmc_request_done(vub300->mmc, req);
+			kref_put(&vub300->kref, vub300_delete);
+			return;
+		} else {
+			construct_request_response(vub300, cmd);
+			vub300->resp_len = 0;
+			mutex_unlock(&vub300->cmd_mutex);
+			kref_put(&vub300->kref, vub300_delete);
+			mmc_request_done(vub300->mmc, req);
+			return;
+		}
+	}
+}
+
+static int examine_cyclic_buffer(struct vub300_mmc_host *vub300,
+				 struct mmc_command *cmd, u8 Function)
+{
+	/*
+	 * cmd_mutex is held by vub300_mmc_request
+	 */
+	u8 cmd0 = 0xFF & (cmd->arg >> 24);
+	u8 cmd1 = 0xFF & (cmd->arg >> 16);
+	u8 cmd2 = 0xFF & (cmd->arg >> 8);
+	u8 cmd3 = 0xFF & (cmd->arg >> 0);
+	int first = MAXREGMASK & vub300->fn[Function].offload_point;
+	struct Offload_Registers_Access *rf = &vub300->fn[Function].reg[first];
+	if (cmd0 == rf->Command_Byte[0]
+	    && cmd1 == rf->Command_Byte[1]
+	    && cmd2 == rf->Command_Byte[2]
+	    && cmd3 == rf->Command_Byte[3]
+	    ) {
+		u8 checksum = 0x00;
+		cmd->resp[1] = checksum << 24;
+		cmd->resp[0] = (rf->Respond_Byte[0] << 24)
+		    | (rf->Respond_Byte[1] << 16)
+		    | (rf->Respond_Byte[2] << 8)
+		    | (rf->Respond_Byte[3] << 0);
+		vub300->fn[Function].offload_point += 1;
+		vub300->fn[Function].offload_count -= 1;
+		vub300->total_offload_count -= 1;
+		return 1;
+	} else {
+		int delta = 1;	/* because it does not match the first one */
+		u8 register_count = vub300->fn[Function].offload_count - 1;
+		u32 register_point = vub300->fn[Function].offload_point + 1;
+		while (0 < register_count) {
+			int point = MAXREGMASK & register_point;
+			struct Offload_Registers_Access *r =
+			    &vub300->fn[Function].reg[point];
+			if (cmd0 == r->Command_Byte[0]
+			    && cmd1 == r->Command_Byte[1]
+			    && cmd2 == r->Command_Byte[2]
+			    && cmd3 == r->Command_Byte[3]
+			    ) {
+				u8 checksum = 0x00;
+				cmd->resp[1] = checksum << 24;
+				cmd->resp[0] = (r->Respond_Byte[0] << 24)
+				    | (r->Respond_Byte[1] << 16)
+				    | (r->Respond_Byte[2] << 8)
+				    | (r->Respond_Byte[3] << 0);
+				vub300->fn[Function].offload_point += delta;
+				vub300->fn[Function].offload_count -= delta;
+				vub300->total_offload_count -= delta;
+				return 1;
+			} else {
+				register_point += 1;
+				register_count -= 1;
+				delta += 1;
+				continue;
+			}
+		}
+		return 0;
+	}
+}
+
+static int satisfy_request_from_offloaded_data(struct vub300_mmc_host *vub300,
+					       struct mmc_command *cmd)
+{
+	/*
+	 * cmd_mutex is held by vub300_mmc_request
+	 */
+	u8 regs = vub300->dynamic_register_count;
+	u8 i = 0;
+	u8 Function = FUN(cmd);
+	u32 Register = REG(cmd);
+	while (0 < regs--) {
+		if ((vub300->sdio_register[i].func_num == Function)
+		    && (vub300->sdio_register[i].sdio_reg == Register)
+		    ) {
+			if (!vub300->sdio_register[i].prepared) {
+				return 0;
+			} else if (0x80000000 == (0x80000000 & cmd->arg)) {
+				/*
+				 * a write to a dynamic register
+				 * nullifies our offloaded value
+				 */
+				vub300->sdio_register[i].prepared = 0;
+				return 0;
+			} else {
+				u8 checksum = 0x00;
+				u8 rsp0 = 0x00;
+				u8 rsp1 = 0x00;
+				u8 rsp2 = vub300->sdio_register[i].response;
+				u8 rsp3 = vub300->sdio_register[i].regvalue;
+				vub300->sdio_register[i].prepared = 0;
+				cmd->resp[1] = checksum << 24;
+				cmd->resp[0] = (rsp0 << 24)
+				    | (rsp1 << 16)
+				    | (rsp2 << 8)
+				    | (rsp3 << 0);
+				return 1;
+			}
+		} else {
+			i += 1;
+			continue;
+		}
+	};
+	if (0 == vub300->total_offload_count)
+		return 0;
+	else if (0 == vub300->fn[Function].offload_count)
+		return 0;
+	else
+		return examine_cyclic_buffer(vub300, cmd, Function);
+}
+
+static void vub300_mmc_request(struct mmc_host *mmc, struct mmc_request *req)
+{				/* NOT irq */
+	struct mmc_command *cmd = req->cmd;
+	struct vub300_mmc_host *vub300 = mmc_priv(mmc);
+	if (!vub300->interface) {
+		cmd->error = -ESHUTDOWN;
+		mmc_request_done(mmc, req);
+		return;
+	} else {
+		struct mmc_data *data = req->data;
+		if (!vub300->card_powered) {
+			cmd->error = -ENOMEDIUM;
+			mmc_request_done(mmc, req);
+			return;
+		}
+		if (!vub300->card_present) {
+			cmd->error = -ENOMEDIUM;
+			mmc_request_done(mmc, req);
+			return;
+		}
+		if (vub300->usb_transport_fail) {
+			cmd->error = vub300->usb_transport_fail;
+			mmc_request_done(mmc, req);
+			return;
+		}
+		if (!vub300->interface) {
+			cmd->error = -ENODEV;
+			mmc_request_done(mmc, req);
+			return;
+		}
+		kref_get(&vub300->kref);
+		mutex_lock(&vub300->cmd_mutex);
+		mod_timer(&vub300->inactivity_timer, jiffies + HZ);
+		/*
+		 * for performance we have to return immediately
+		 * if the requested data has been offloaded
+		 */
+		if ((52 == cmd->opcode)
+		    && satisfy_request_from_offloaded_data(vub300, cmd)) {
+			cmd->error = 0;
+			mutex_unlock(&vub300->cmd_mutex);
+			kref_put(&vub300->kref, vub300_delete);
+			mmc_request_done(mmc, req);
+			return;
+		} else {
+			vub300->cmd = cmd;
+			vub300->req = req;
+			vub300->data = data;
+			if (data)
+				vub300->datasize = data->blksz * data->blocks;
+			else
+				vub300->datasize = 0;
+			vub300_queue_cmnd_work(vub300);
+			mutex_unlock(&vub300->cmd_mutex);
+			kref_put(&vub300->kref, vub300_delete);
+			/*
+			 * the kernel lock diagnostics complain
+			 * if the cmd_mutex * is "passed on"
+			 * to the cmndwork thread,
+			 * so we must release it now
+			 * and re-acquire it in the cmndwork thread
+			 */
+		}
+	}
+}
+
+static void __set_clock_speed(struct vub300_mmc_host *vub300, u8 * buf,
+			      struct mmc_ios *ios)
+{
+	int retval;
+	u32 kHzClock;
+	if (ios->clock >= 48000000)
+		kHzClock = 48000;
+	else if (ios->clock >= 24000000)
+		kHzClock = 24000;
+	else if (ios->clock >= 20000000)
+		kHzClock = 20000;
+	else if (ios->clock >= 15000000)
+		kHzClock = 15000;
+	else if (ios->clock >= 200000)
+		kHzClock = 200;
+	else
+		kHzClock = 0;
+	buf[0] = 0xFF & (kHzClock >> 0);
+	buf[1] = 0xFF & (kHzClock >> 8);
+	buf[2] = 0xFF & (kHzClock >> 16);
+	buf[3] = 0xFF & (kHzClock >> 24);
+	buf[4] = 0;
+	buf[5] = 0;
+	buf[6] = 0;
+	buf[7] = 0;
+	retval =
+	    usb_control_msg(vub300->udev, usb_sndctrlpipe(vub300->udev, 0),
+			    SET_CLOCK_SPEED,
+			    USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			    0x00, 0x00, buf, 8, HZ);
+	if (8 != retval) {
+		dev_err(&vub300->udev->dev, "SET_CLOCK_SPEED"
+			" %dkHz failed with retval=%d\n", kHzClock, retval);
+	} else {
+		dev_info(&vub300->udev->dev, "SET_CLOCK_SPEED"
+			 " %dkHz\n", kHzClock);
+	}
+}
+
+static void vub300_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{				/* NOT irq */
+	struct vub300_mmc_host *vub300 = mmc_priv(mmc);
+	if (!vub300->interface)
+		return;
+	kref_get(&vub300->kref);
+	mutex_lock(&vub300->cmd_mutex);
+	if ((MMC_POWER_OFF == ios->power_mode) && vub300->card_powered) {
+		int retval;
+		vub300->card_powered = 0;
+		retval =
+		    usb_control_msg(vub300->udev,
+				    usb_sndctrlpipe(vub300->udev, 0),
+				    SET_SD_POWER,
+				    USB_DIR_OUT | USB_TYPE_VENDOR |
+				    USB_RECIP_DEVICE, 0x0000, 0x0000, NULL, 0,
+				    HZ);
+		/*
+		 * must wait for the VUB300 u-proc to boot up
+		 */
+		msleep(600);
+	} else if ((MMC_POWER_UP == ios->power_mode) && !vub300->card_powered) {
+		int retval;
+		retval =
+		    usb_control_msg(vub300->udev,
+				    usb_sndctrlpipe(vub300->udev, 0),
+				    SET_SD_POWER,
+				    USB_DIR_OUT | USB_TYPE_VENDOR |
+				    USB_RECIP_DEVICE, 0x0001, 0x0000, NULL, 0,
+				    HZ);
+		msleep(600);
+		vub300->card_powered = 1;
+	} else if (MMC_POWER_ON == ios->power_mode) {
+		u8 *buf = kmalloc(8, GFP_KERNEL);
+		if (buf) {
+			__set_clock_speed(vub300, buf, ios);
+			kfree(buf);
+		}
+	} else {
+		/*
+		 * this should mean no change of state
+		 */
+	}
+	mutex_unlock(&vub300->cmd_mutex);
+	kref_put(&vub300->kref, vub300_delete);
+}
+
+static int vub300_mmc_get_ro(struct mmc_host *mmc)
+{
+	struct vub300_mmc_host *vub300 = mmc_priv(mmc);
+	return vub300->read_only;
+}
+
+static void vub300_enable_sdio_irq(struct mmc_host *mmc, int enable)
+{				/* NOT irq */
+	struct vub300_mmc_host *vub300 = mmc_priv(mmc);
+	if (!vub300->interface)
+		return;
+	kref_get(&vub300->kref);
+	if (enable) {
+		mutex_lock(&vub300->irq_mutex);
+		if (vub300->irqs_queued) {
+			vub300->irqs_queued -= 1;
+			mmc_signal_sdio_irq(vub300->mmc);
+		} else if (vub300->irq_disabled) {
+			vub300->irq_disabled = 0;
+			vub300->irq_enabled = 1;
+			vub300_queue_poll_work(vub300, 0);
+		} else if (vub300->irq_enabled) {
+			/*
+			 * this should not happen
+			 * so we will just ignore it
+			 */
+		} else {
+			vub300->irq_enabled = 1;
+			vub300_queue_poll_work(vub300, 0);
+		}
+		mutex_unlock(&vub300->irq_mutex);
+	} else {
+		vub300->irq_enabled = 0;
+	}
+	kref_put(&vub300->kref, vub300_delete);
+}
+
+void vub300_init_card(struct mmc_host *mmc, struct mmc_card *card)
+{				/* NOT irq */
+	struct vub300_mmc_host *vub300 = mmc_priv(mmc);
+	dev_info(&vub300->udev->dev, "NO host QUIRKS for this card\n");
+}
+
+static struct mmc_host_ops vub300_mmc_ops = {
+	.request = vub300_mmc_request,
+	.set_ios = vub300_mmc_set_ios,
+	.get_ro = vub300_mmc_get_ro,
+	.enable_sdio_irq = vub300_enable_sdio_irq,
+	.init_card = vub300_init_card,
+};
+
+static int vub300_probe(struct usb_interface *interface,
+			const struct usb_device_id *id)
+{				/* NOT irq */
+	struct vub300_mmc_host *vub300 = NULL;
+	struct usb_host_interface *iface_desc;
+	struct usb_device *udev = usb_get_dev(interface_to_usbdev(interface));
+	int i;
+	int retval = -ENOMEM;
+	struct urb *command_out_urb;
+	struct urb *command_res_urb;
+	struct mmc_host *mmc;
+	char Manufacturer[48];
+	char Product[32];
+	char SerialNumber[32];
+	usb_string(udev, udev->descriptor.iManufacturer, Manufacturer,
+		   sizeof(Manufacturer));
+	usb_string(udev, udev->descriptor.iProduct, Product, sizeof(Product));
+	usb_string(udev, udev->descriptor.iSerialNumber, SerialNumber,
+		   sizeof(SerialNumber));
+	dev_info(&udev->dev, "probing VID:PID(%04X:%04X) %s %s %s\n",
+		 udev->descriptor.idVendor, udev->descriptor.idProduct,
+		 Manufacturer, Product, SerialNumber);
+	command_out_urb = usb_alloc_urb(0, GFP_KERNEL);
+	if (!command_out_urb) {
+		retval = -ENOMEM;
+		dev_err(&vub300->udev->dev,
+			"not enough memory for the command_out_urb\n");
+		goto error0;
+	}
+	command_res_urb = usb_alloc_urb(0, GFP_KERNEL);
+	if (!command_res_urb) {
+		retval = -ENOMEM;
+		dev_err(&vub300->udev->dev,
+			"not enough memory for the command_res_urb\n");
+		goto error1;
+	}
+	/* this also allocates memory for our VUB300 mmc host device */
+	mmc = mmc_alloc_host(sizeof(struct vub300_mmc_host), &udev->dev);
+	if (!mmc) {
+		retval = -ENOMEM;
+		dev_err(&vub300->udev->dev,
+			"not enough memory for the mmc_host\n");
+		goto error4;
+	}
+	/* MMC core transfer sizes tunable parameters */
+	mmc->caps = 0;
+	if (!force_1_bit_data_xfers)
+		mmc->caps |= MMC_CAP_4_BIT_DATA;
+	if (!force_polling_for_irqs)
+		mmc->caps |= MMC_CAP_SDIO_IRQ;
+	mmc->caps &= ~MMC_CAP_NEEDS_POLL;
+	/*
+	 * MMC_CAP_NEEDS_POLL causes core.c:mmc_rescan() to poll
+	 * for devices which results in spurious CMD7's being
+	 * issued which stops some SDIO cards from working
+	 */
+	if (limit_speed_to_24_MHz) {
+		mmc->caps |= MMC_CAP_MMC_HIGHSPEED;
+		mmc->caps |= MMC_CAP_SD_HIGHSPEED;
+		mmc->f_max = 24000000;
+		dev_info(&udev->dev, "limiting SDIO speed to 24_MHz\n");
+	} else {
+		mmc->caps |= MMC_CAP_MMC_HIGHSPEED;
+		mmc->caps |= MMC_CAP_SD_HIGHSPEED;
+		mmc->f_max = 48000000;
+	}
+	mmc->f_min = 200000;
+	mmc->max_blk_count = 511;
+	mmc->max_blk_size = 512;
+	mmc->max_segs = 128;
+	if (force_max_req_size)
+		mmc->max_req_size = force_max_req_size * 1024;
+	else
+		mmc->max_req_size = 64 * 1024;
+	mmc->max_seg_size = mmc->max_req_size;
+	mmc->ocr_avail = 0;
+	mmc->ocr_avail |= MMC_VDD_165_195;
+	mmc->ocr_avail |= MMC_VDD_20_21;
+	mmc->ocr_avail |= MMC_VDD_21_22;
+	mmc->ocr_avail |= MMC_VDD_22_23;
+	mmc->ocr_avail |= MMC_VDD_23_24;
+	mmc->ocr_avail |= MMC_VDD_24_25;
+	mmc->ocr_avail |= MMC_VDD_25_26;
+	mmc->ocr_avail |= MMC_VDD_26_27;
+	mmc->ocr_avail |= MMC_VDD_27_28;
+	mmc->ocr_avail |= MMC_VDD_28_29;
+	mmc->ocr_avail |= MMC_VDD_29_30;
+	mmc->ocr_avail |= MMC_VDD_30_31;
+	mmc->ocr_avail |= MMC_VDD_31_32;
+	mmc->ocr_avail |= MMC_VDD_32_33;
+	mmc->ocr_avail |= MMC_VDD_33_34;
+	mmc->ocr_avail |= MMC_VDD_34_35;
+	mmc->ocr_avail |= MMC_VDD_35_36;
+	mmc->ops = &vub300_mmc_ops;
+	vub300 = mmc_priv(mmc);
+	vub300->mmc = mmc;
+	vub300->card_powered = 0;
+	vub300->bus_width = 0;
+	vub300->cmnd.head.BlockSize[0] = 0x00;
+	vub300->cmnd.head.BlockSize[1] = 0x00;
+	vub300->app_spec = 0;
+	mutex_init(&vub300->cmd_mutex);
+	mutex_init(&vub300->irq_mutex);
+	vub300->command_out_urb = command_out_urb;
+	vub300->command_res_urb = command_res_urb;
+	vub300->usb_timed_out = 0;
+	vub300->dynamic_register_count = 0;
+	{
+		int i = 0;
+		do {
+			vub300->fn[i].offload_point = 0;
+			vub300->fn[i].offload_count = 0;
+		} while (++i < 8);
+	}
+	vub300->total_offload_count = 0;
+	vub300->irq_enabled = 0;
+	vub300->irq_disabled = 0;
+	vub300->irqs_queued = 0;
+	{
+		int i = 0;
+		int I = ARRAY_SIZE(vub300->sdio_register);
+		while (I--)
+			vub300->sdio_register[i++].activate = 0;
+	}
+	vub300->udev = udev;
+	vub300->interface = interface;
+	vub300->cmnd_res_ep = 0;
+	vub300->cmnd_out_ep = 0;
+	vub300->data_inp_ep = 0;
+	vub300->data_out_ep = 0;
+	vub300->fbs[0] = 512;
+	vub300->fbs[1] = 512;
+	vub300->fbs[2] = 512;
+	vub300->fbs[3] = 512;
+	vub300->fbs[4] = 512;
+	vub300->fbs[5] = 512;
+	vub300->fbs[6] = 512;
+	vub300->fbs[7] = 512;
+	/*
+	 *      set up the endpoint information
+	 *
+	 * use the first pair of bulk-in and bulk-out
+	 *     endpoints for Command/Response+Interrupt
+	 *
+	 * use the second pair of bulk-in and bulk-out
+	 *     endpoints for Data In/Out
+	 */
+	vub300->large_usb_packets = 0;
+	iface_desc = interface->cur_altsetting;
+	for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {
+		struct usb_endpoint_descriptor *endpoint =
+		    &iface_desc->endpoint[i].desc;
+		dev_info(&vub300->udev->dev,
+			 "vub300 testing %s EndPoint(%d) %02X\n",
+			 usb_endpoint_is_bulk_in(endpoint) ? "BULK IN" :
+			 usb_endpoint_is_bulk_out(endpoint) ? "BULK OUT" :
+			 "UNKNOWN", i, endpoint->bEndpointAddress);
+		if (64 < endpoint->wMaxPacketSize)
+			vub300->large_usb_packets = 1;
+		if (usb_endpoint_is_bulk_in(endpoint)) {
+			if (!vub300->cmnd_res_ep) {
+				vub300->cmnd_res_ep =
+				    endpoint->bEndpointAddress;
+			} else if (!vub300->data_inp_ep) {
+				vub300->data_inp_ep =
+				    endpoint->bEndpointAddress;
+			} else {
+				dev_warn(&vub300->udev->dev,
+					 "ignoring"
+					 " unexpected bulk_in endpoint");
+			}
+		} else if (usb_endpoint_is_bulk_out(endpoint)) {
+			if (!vub300->cmnd_out_ep) {
+				vub300->cmnd_out_ep =
+				    endpoint->bEndpointAddress;
+			} else if (!vub300->data_out_ep) {
+				vub300->data_out_ep =
+				    endpoint->bEndpointAddress;
+			} else {
+				dev_warn(&vub300->udev->dev,
+					 "ignoring"
+					 " unexpected bulk_out endpoint");
+			}
+		} else {
+			dev_warn(&vub300->udev->dev,
+				 "vub300 ignoring EndPoint(%d) %02X", i,
+				 endpoint->bEndpointAddress);
+		}
+	}
+	if (vub300->cmnd_res_ep
+	    && vub300->cmnd_out_ep
+	    && vub300->data_inp_ep && vub300->data_out_ep) {
+		dev_info(&vub300->udev->dev,
+			 "vub300 %s packets"
+			 " using EndPoints %02X %02X %02X %02X\n",
+			 vub300->large_usb_packets ? "LARGE" : "SMALL",
+			 vub300->cmnd_out_ep, vub300->cmnd_res_ep,
+			 vub300->data_out_ep, vub300->data_inp_ep);
+		/*
+		 * we have the expected EndPoints
+		 */
+	} else {
+		dev_err(&vub300->udev->dev,
+			"Could not find two sets of bulk-in/out endpoint pairs\n");
+		retval = -EINVAL;
+		goto error5;
+	}
+	retval =
+	    usb_control_msg(vub300->udev, usb_rcvctrlpipe(vub300->udev, 0),
+			    GET_HC_INF0,
+			    USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			    0x0000, 0x0000, &vub300->hc_info,
+			    sizeof(vub300->hc_info), HZ);
+	if (retval < 0)
+		goto error5;
+	retval =
+	    usb_control_msg(vub300->udev, usb_rcvctrlpipe(vub300->udev, 0),
+			    SET_ROM_WAIT_STATES,
+			    USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			    firmware_rom_wait_states, 0x0000, NULL, 0, HZ);
+	if (retval < 0)
+		goto error5;
+	dev_info(&vub300->udev->dev,
+		 "operating_mode = %s %s %d MHz %s %d byte USB packets\n",
+		 (mmc->caps & MMC_CAP_SDIO_IRQ) ? "IRQs" : "POLL",
+		 (mmc->caps & MMC_CAP_4_BIT_DATA) ? "4-bit" : "1-bit",
+		 mmc->f_max / 1000000,
+		 pad_input_to_usb_pkt ? "padding input data to" : "with",
+		 vub300->large_usb_packets ? 512 : 64);
+	retval =
+	    usb_control_msg(vub300->udev, usb_rcvctrlpipe(vub300->udev, 0),
+			    GET_SYSTEM_PORT_STATUS,
+			    USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			    0x0000, 0x0000, &vub300->system_port_status,
+			    sizeof(vub300->system_port_status), HZ);
+	if (retval < 0) {
+		goto error4;
+	} else if (sizeof(vub300->system_port_status) == retval) {
+		vub300->card_present =
+		    (0x0001 & vub300->system_port_status.PortFlags) ? 1 : 0;
+		vub300->read_only =
+		    (0x0010 & vub300->system_port_status.PortFlags) ? 1 : 0;
+	} else {
+		goto error4;
+	}
+	usb_set_intfdata(interface, vub300);
+	INIT_DELAYED_WORK(&vub300->pollwork, vub300_pollwork_thread);
+	INIT_WORK(&vub300->cmndwork, vub300_cmndwork_thread);
+	INIT_WORK(&vub300->deadwork, vub300_deadwork_thread);
+	kref_init(&vub300->kref);
+	init_timer(&vub300->sg_transfer_timer);
+	vub300->sg_transfer_timer.data = (unsigned long)vub300;
+	vub300->sg_transfer_timer.function = vub300_sg_timed_out;
+	kref_get(&vub300->kref);
+	init_timer(&vub300->inactivity_timer);
+	vub300->inactivity_timer.data = (unsigned long)vub300;
+	vub300->inactivity_timer.function = vub300_inactivity_timer_expired;
+	vub300->inactivity_timer.expires = jiffies + HZ;
+	add_timer(&vub300->inactivity_timer);
+	if (vub300->card_present)
+		dev_info(&vub300->udev->dev,
+			 "USB vub300 remote SDIO host controller[%d]"
+			 "connected with SD/SDIO card inserted\n",
+			 interface_to_InterfaceNumber(interface));
+	else
+		dev_info(&vub300->udev->dev,
+			 "USB vub300 remote SDIO host controller[%d]"
+			 "connected with no SD/SDIO card inserted\n",
+			 interface_to_InterfaceNumber(interface));
+	mmc_add_host(mmc);
+	retval = sysfs_create_group(&mmc->class_dev.kobj, &vub300_attr_grp);
+	return 0;
+error5:
+	mmc_free_host(mmc);
+	/*
+	 * and hence also frees vub300
+	 * which is contained at the end of struct mmc
+	 */
+error4:
+	usb_free_urb(command_out_urb);
+error1:
+	usb_free_urb(command_res_urb);
+error0:
+	return retval;
+}
+
+static void vub300_disconnect(struct usb_interface *interface)
+{				/* NOT irq */
+	struct vub300_mmc_host *vub300 = usb_get_intfdata(interface);
+	if (!vub300 || !vub300->mmc) {
+		return;
+	} else {
+		struct mmc_host *mmc = vub300->mmc;
+		if (!vub300->mmc) {
+			return;
+		} else {
+			int ifnum = interface_to_InterfaceNumber(interface);
+			sysfs_remove_group(&interface->dev.kobj,
+					   &vub300_attr_grp);
+			usb_set_intfdata(interface, NULL);
+			/* prevent more I/O from starting */
+			vub300->interface = NULL;
+			kref_put(&vub300->kref, vub300_delete);
+			mmc_remove_host(mmc);
+			pr_info("USB vub300 remote SDIO host controller[%d]"
+				" now disconnected", ifnum);
+			return;
+		}
+	}
+}
+
+#ifdef CONFIG_PM
+static int vub300_suspend(struct usb_interface *intf, pm_message_t message)
+{
+	struct vub300_mmc_host *vub300 = usb_get_intfdata(intf);
+	if (!vub300 || !vub300->mmc) {
+		return 0;
+	} else {
+		struct mmc_host *mmc = vub300->mmc;
+		mmc_suspend_host(mmc);
+		return 0;
+	}
+}
+
+static int vub300_resume(struct usb_interface *intf)
+{
+	struct vub300_mmc_host *vub300 = usb_get_intfdata(intf);
+	if (!vub300 || !vub300->mmc) {
+		return 0;
+	} else {
+		struct mmc_host *mmc = vub300->mmc;
+		mmc_resume_host(mmc);
+		return 0;
+	}
+}
+#else
+#define vub300_suspend NULL
+#define vub300_resume NULL
+#endif
+static int vub300_pre_reset(struct usb_interface *intf)
+{				/* NOT irq */
+	struct vub300_mmc_host *vub300 = usb_get_intfdata(intf);
+	mutex_lock(&vub300->cmd_mutex);
+	return 0;
+}
+
+static int vub300_post_reset(struct usb_interface *intf)
+{				/* NOT irq */
+	struct vub300_mmc_host *vub300 = usb_get_intfdata(intf);
+	/* we are sure no URBs are active - no locking needed */
+	vub300->errors = -EPIPE;
+	mutex_unlock(&vub300->cmd_mutex);
+	return 0;
+}
+
+static struct usb_driver vub300_driver = {
+	.name = "vub300",
+	.probe = vub300_probe,
+	.disconnect = vub300_disconnect,
+	.suspend = vub300_suspend,
+	.resume = vub300_resume,
+	.pre_reset = vub300_pre_reset,
+	.post_reset = vub300_post_reset,
+	.id_table = vub300_table,
+	.supports_autosuspend = 1,
+};
+
+static int __init vub300_init(void)
+{				/* NOT irq */
+	int result;
+	pr_info("VUB300 Driver rom wait states = %02X irqpoll timeout = %04X",
+		firmware_rom_wait_states, 0x0FFFF & firmware_irqpoll_timeout);
+	cmndworkqueue = create_singlethread_workqueue("kvub300c");
+	if (!cmndworkqueue) {
+		pr_err("not enough memory for the REQUEST workqueue");
+		result = -ENOMEM;
+		goto out1;
+	}
+	pollworkqueue = create_singlethread_workqueue("kvub300p");
+	if (!pollworkqueue) {
+		pr_err("not enough memory for the IRQPOLL workqueue");
+		result = -ENOMEM;
+		goto out2;
+	}
+	deadworkqueue = create_singlethread_workqueue("kvub300d");
+	if (!deadworkqueue) {
+		pr_err("not enough memory for the EXPIRED workqueue");
+		result = -ENOMEM;
+		goto out3;
+	}
+	result = usb_register(&vub300_driver);
+	if (result) {
+		pr_err("usb_register failed. Error number %d", result);
+		goto out4;
+	}
+	return 0;
+out4:
+	destroy_workqueue(deadworkqueue);
+out3:
+	destroy_workqueue(pollworkqueue);
+out2:
+	destroy_workqueue(cmndworkqueue);
+out1:
+	return result;
+}
+
+static void __exit vub300_exit(void)
+{
+	usb_deregister(&vub300_driver);
+	flush_workqueue(cmndworkqueue);
+	flush_workqueue(pollworkqueue);
+	flush_workqueue(deadworkqueue);
+	destroy_workqueue(cmndworkqueue);
+	destroy_workqueue(pollworkqueue);
+	destroy_workqueue(deadworkqueue);
+}
+
+module_init(vub300_init);
+module_exit(vub300_exit);
+MODULE_LICENSE("GPL");

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH] mmc: USB SDIO/SD/MMC Host Controller (VUB300) driver Resubmission
  2011-01-21 10:50             ` [PATCH] mmc: USB SDIO/SD/MMC Host Controller (VUB300) driver Resubmission Tony Olech
@ 2011-01-21 21:14               ` Nicolas Pitre
  2011-01-22 14:21                 ` Wolfram Sang
  2011-01-24  8:49                 ` Tony Olech
  2011-03-10 16:13               ` [PATCH] mmc: USB SDIO/SD/MMC Host Controller (VUB300) driver Re-Resubmission Tony Olech
  1 sibling, 2 replies; 41+ messages in thread
From: Nicolas Pitre @ 2011-01-21 21:14 UTC (permalink / raw)
  To: Tony Olech; +Cc: Chris Ball, linux-mmc, David Vrabel

On Fri, 21 Jan 2011, Tony Olech wrote:

> Add a driver for Elan Digital System's VUB300 chip
> which is a USB connected SDIO/SDmem/MMC host controller.
> A VUB300 chip enables a USB 2.0 or USB 1.1 connected host
> computer to use SDIO/SD/MMC cards without the need for
> a directly connected, for example via PCI, SDIO host
> controller.
> 
> Signed-off-by: Anthony F Olech <tony.olech@elandigitalsystems.com>
> ---
> This is the second submission attempt.
> There are 5 "do not initialise statics" errors reported by scripts/checkpatch.pl

you probably should fix those.  They are due to lines such as:

static int pad_input_to_usb_pkt = 0;
static int disable_offload_processing = 0;
static int force_1_bit_data_xfers = 0;
static int force_polling_for_irqs = 0;

Since those are global variables, you may omit to initialize them to 0 
which would then allocate them to the .bss section which is not included 
in the compiled binary, and automatically cleared to zero at run time.


Nicolas

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH] mmc: USB SDIO/SD/MMC Host Controller (VUB300) driver Resubmission
  2011-01-21 21:14               ` Nicolas Pitre
@ 2011-01-22 14:21                 ` Wolfram Sang
  2011-01-22 19:07                   ` Nicolas Pitre
  2011-01-24  8:49                 ` Tony Olech
  1 sibling, 1 reply; 41+ messages in thread
From: Wolfram Sang @ 2011-01-22 14:21 UTC (permalink / raw)
  To: Nicolas Pitre; +Cc: Tony Olech, Chris Ball, linux-mmc, David Vrabel

[-- Attachment #1: Type: text/plain, Size: 1208 bytes --]

On Fri, Jan 21, 2011 at 04:14:46PM -0500, Nicolas Pitre wrote:
> On Fri, 21 Jan 2011, Tony Olech wrote:
> 
> > Add a driver for Elan Digital System's VUB300 chip
> > which is a USB connected SDIO/SDmem/MMC host controller.
> > A VUB300 chip enables a USB 2.0 or USB 1.1 connected host
> > computer to use SDIO/SD/MMC cards without the need for
> > a directly connected, for example via PCI, SDIO host
> > controller.
> > 
> > Signed-off-by: Anthony F Olech <tony.olech@elandigitalsystems.com>
> > ---
> > This is the second submission attempt.
> > There are 5 "do not initialise statics" errors reported by scripts/checkpatch.pl
> 
> you probably should fix those.  They are due to lines such as:
> 
> static int pad_input_to_usb_pkt = 0;
> static int disable_offload_processing = 0;
> static int force_1_bit_data_xfers = 0;
> static int force_polling_for_irqs = 0;

Should those be static anyhow? Being USB, you could probably hook up two of
those and want to operate one of them in this and the other one in another
mode?

-- 
Pengutronix e.K.                           | Wolfram Sang                |
Industrial Linux Solutions                 | http://www.pengutronix.de/  |

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 197 bytes --]

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH] mmc: USB SDIO/SD/MMC Host Controller (VUB300) driver Resubmission
  2011-01-22 14:21                 ` Wolfram Sang
@ 2011-01-22 19:07                   ` Nicolas Pitre
  2011-01-23 10:09                     ` Wolfram Sang
  2011-01-24 16:17                     ` Tony Olech
  0 siblings, 2 replies; 41+ messages in thread
From: Nicolas Pitre @ 2011-01-22 19:07 UTC (permalink / raw)
  To: Wolfram Sang; +Cc: Tony Olech, Chris Ball, linux-mmc, David Vrabel

On Sat, 22 Jan 2011, Wolfram Sang wrote:

> On Fri, Jan 21, 2011 at 04:14:46PM -0500, Nicolas Pitre wrote:
> > On Fri, 21 Jan 2011, Tony Olech wrote:
> > 
> > > Add a driver for Elan Digital System's VUB300 chip
> > > which is a USB connected SDIO/SDmem/MMC host controller.
> > > A VUB300 chip enables a USB 2.0 or USB 1.1 connected host
> > > computer to use SDIO/SD/MMC cards without the need for
> > > a directly connected, for example via PCI, SDIO host
> > > controller.
> > > 
> > > Signed-off-by: Anthony F Olech <tony.olech@elandigitalsystems.com>
> > > ---
> > > This is the second submission attempt.
> > > There are 5 "do not initialise statics" errors reported by scripts/checkpatch.pl
> > 
> > you probably should fix those.  They are due to lines such as:
> > 
> > static int pad_input_to_usb_pkt = 0;
> > static int disable_offload_processing = 0;
> > static int force_1_bit_data_xfers = 0;
> > static int force_polling_for_irqs = 0;
> 
> Should those be static anyhow? Being USB, you could probably hook up two of
> those and want to operate one of them in this and the other one in another
> mode?

You could.  Whether or not you would is another question.  Until then I 
don't think it is worth bothering with such corner cases for the initial 
merging of this driver.


Nicolas

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH] mmc: USB SDIO/SD/MMC Host Controller (VUB300) driver Resubmission
  2011-01-22 19:07                   ` Nicolas Pitre
@ 2011-01-23 10:09                     ` Wolfram Sang
  2011-01-23 14:01                       ` Nicolas Pitre
  2011-01-24 16:21                       ` Tony Olech
  2011-01-24 16:17                     ` Tony Olech
  1 sibling, 2 replies; 41+ messages in thread
From: Wolfram Sang @ 2011-01-23 10:09 UTC (permalink / raw)
  To: Nicolas Pitre; +Cc: Tony Olech, Chris Ball, linux-mmc, David Vrabel

[-- Attachment #1: Type: text/plain, Size: 2043 bytes --]

On Sat, Jan 22, 2011 at 02:07:21PM -0500, Nicolas Pitre wrote:
> On Sat, 22 Jan 2011, Wolfram Sang wrote:
> 
> > On Fri, Jan 21, 2011 at 04:14:46PM -0500, Nicolas Pitre wrote:
> > > On Fri, 21 Jan 2011, Tony Olech wrote:
> > > 
> > > > Add a driver for Elan Digital System's VUB300 chip
> > > > which is a USB connected SDIO/SDmem/MMC host controller.
> > > > A VUB300 chip enables a USB 2.0 or USB 1.1 connected host
> > > > computer to use SDIO/SD/MMC cards without the need for
> > > > a directly connected, for example via PCI, SDIO host
> > > > controller.
> > > > 
> > > > Signed-off-by: Anthony F Olech <tony.olech@elandigitalsystems.com>
> > > > ---
> > > > This is the second submission attempt.
> > > > There are 5 "do not initialise statics" errors reported by scripts/checkpatch.pl
> > > 
> > > you probably should fix those.  They are due to lines such as:
> > > 
> > > static int pad_input_to_usb_pkt = 0;
> > > static int disable_offload_processing = 0;
> > > static int force_1_bit_data_xfers = 0;
> > > static int force_polling_for_irqs = 0;
> > 
> > Should those be static anyhow? Being USB, you could probably hook up two of
> > those and want to operate one of them in this and the other one in another
> > mode?
> 
> You could.  Whether or not you would is another question.  Until then I 
> don't think it is worth bothering with such corner cases for the initial 
> merging of this driver.

A static variable which should be per-device is a corner-case? Frankly, I'd
think it is a flaw. Unless, of course, these module_params are only rarely
used. Which would lead to the question if they are really needed. There are
quite a lot.

Also, I am not convinced of the need of a custom sysfs-file. Seeing its use
cases might help to identify a need which is probably better solved
generically.

Kind regards,

   Wolfram

-- 
Pengutronix e.K.                           | Wolfram Sang                |
Industrial Linux Solutions                 | http://www.pengutronix.de/  |

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 197 bytes --]

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH] mmc: USB SDIO/SD/MMC Host Controller (VUB300) driver Resubmission
  2011-01-23 10:09                     ` Wolfram Sang
@ 2011-01-23 14:01                       ` Nicolas Pitre
  2011-01-24 15:35                         ` Wolfram Sang
  2011-01-24 16:27                         ` Tony Olech
  2011-01-24 16:21                       ` Tony Olech
  1 sibling, 2 replies; 41+ messages in thread
From: Nicolas Pitre @ 2011-01-23 14:01 UTC (permalink / raw)
  To: Wolfram Sang; +Cc: Tony Olech, Chris Ball, linux-mmc, David Vrabel

On Sun, 23 Jan 2011, Wolfram Sang wrote:

> On Sat, Jan 22, 2011 at 02:07:21PM -0500, Nicolas Pitre wrote:
> > On Sat, 22 Jan 2011, Wolfram Sang wrote:
> > 
> > > On Fri, Jan 21, 2011 at 04:14:46PM -0500, Nicolas Pitre wrote:
> > > > On Fri, 21 Jan 2011, Tony Olech wrote:
> > > > 
> > > > > Add a driver for Elan Digital System's VUB300 chip
> > > > > which is a USB connected SDIO/SDmem/MMC host controller.
> > > > > A VUB300 chip enables a USB 2.0 or USB 1.1 connected host
> > > > > computer to use SDIO/SD/MMC cards without the need for
> > > > > a directly connected, for example via PCI, SDIO host
> > > > > controller.
> > > > > 
> > > > > Signed-off-by: Anthony F Olech <tony.olech@elandigitalsystems.com>
> > > > > ---
> > > > > This is the second submission attempt.
> > > > > There are 5 "do not initialise statics" errors reported by scripts/checkpatch.pl
> > > > 
> > > > you probably should fix those.  They are due to lines such as:
> > > > 
> > > > static int pad_input_to_usb_pkt = 0;
> > > > static int disable_offload_processing = 0;
> > > > static int force_1_bit_data_xfers = 0;
> > > > static int force_polling_for_irqs = 0;
> > > 
> > > Should those be static anyhow? Being USB, you could probably hook up two of
> > > those and want to operate one of them in this and the other one in another
> > > mode?
> > 
> > You could.  Whether or not you would is another question.  Until then I 
> > don't think it is worth bothering with such corner cases for the initial 
> > merging of this driver.
> 
> A static variable which should be per-device is a corner-case? Frankly, I'd
> think it is a flaw. Unless, of course, these module_params are only rarely
> used. Which would lead to the question if they are really needed. There are
> quite a lot.

Given their name, I'd say they are there only for debugging purposes.  
So yes, they probably are rarely used, which doesn't mean they're 
useless.  Same thing goes for maxfreq and nodma in mvsdio.c for example.

> Also, I am not convinced of the need of a custom sysfs-file. Seeing its use
> cases might help to identify a need which is probably better solved
> generically.

I'm sure the author of this driver would be eager to hear from you with 
a concrete suggestion.


Nicolas

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH] mmc: USB SDIO/SD/MMC Host Controller (VUB300) driver Resubmission
  2011-01-21 21:14               ` Nicolas Pitre
  2011-01-22 14:21                 ` Wolfram Sang
@ 2011-01-24  8:49                 ` Tony Olech
  2011-01-24 14:43                   ` Nicolas Pitre
  1 sibling, 1 reply; 41+ messages in thread
From: Tony Olech @ 2011-01-24  8:49 UTC (permalink / raw)
  To: Nicolas Pitre; +Cc: Chris Ball, linux-mmc, David Vrabel

On Fri, 2011-01-21 at 16:14 -0500, Nicolas Pitre wrote:
> On Fri, 21 Jan 2011, Tony Olech wrote:
> > There are 5 "do not initialise statics" errors reported by scripts/checkpatch.pl
> 
> you probably should fix those.  They are due to lines such as:
> 
> static int pad_input_to_usb_pkt = 0;
> static int disable_offload_processing = 0;
> static int force_1_bit_data_xfers = 0;
> static int force_polling_for_irqs = 0;
> 
> Since those are global variables, you may omit to initialize them to 0 
> which would then allocate them to the .bss section which is not included 
> in the compiled binary, and automatically cleared to zero at run time
> Nicolas
Yes they are due to exactly that, BUT I  have a great aversion to using
uninitialized variables. How can initializing all one's variables be
considered a bug????
Tony Olech
> --
> To unsubscribe from this list: send the line "unsubscribe linux-mmc" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html



^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH] mmc: USB SDIO/SD/MMC Host Controller (VUB300) driver Resubmission
  2011-01-24  8:49                 ` Tony Olech
@ 2011-01-24 14:43                   ` Nicolas Pitre
  2011-01-24 15:10                     ` Tony Olech
  0 siblings, 1 reply; 41+ messages in thread
From: Nicolas Pitre @ 2011-01-24 14:43 UTC (permalink / raw)
  To: Tony Olech; +Cc: Chris Ball, linux-mmc, David Vrabel

On Mon, 24 Jan 2011, Tony Olech wrote:

> On Fri, 2011-01-21 at 16:14 -0500, Nicolas Pitre wrote:
> > On Fri, 21 Jan 2011, Tony Olech wrote:
> > > There are 5 "do not initialise statics" errors reported by scripts/checkpatch.pl
> > 
> > you probably should fix those.  They are due to lines such as:
> > 
> > static int pad_input_to_usb_pkt = 0;
> > static int disable_offload_processing = 0;
> > static int force_1_bit_data_xfers = 0;
> > static int force_polling_for_irqs = 0;
> > 
> > Since those are global variables, you may omit to initialize them to 0 
> > which would then allocate them to the .bss section which is not included 
> > in the compiled binary, and automatically cleared to zero at run time
> > Nicolas
> Yes they are due to exactly that, BUT I  have a great aversion to using
> uninitialized variables. How can initializing all one's variables be
> considered a bug????

Let me repeat myself.

Uninitialized global scope variables are by definition assigned to the 
.bss section.  The .bss section is dynamically allocated at run time 
rather than being stored in the compiled binary, and also cleared to 
zero at run time.  So the preference is for zero-initialized global 
variables not to be initialized at all because 1) they are implicitly 
initialized to zero anyway, and 2) that makes the resulting binaries 
smaller.

So this is not about fixing a bug, but rather to conform to the adopted 
policy for kernel code.


Nicolas

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH] mmc: USB SDIO/SD/MMC Host Controller (VUB300) driver Resubmission
  2011-01-24 14:43                   ` Nicolas Pitre
@ 2011-01-24 15:10                     ` Tony Olech
  2011-01-24 15:55                       ` Nicolas Pitre
  0 siblings, 1 reply; 41+ messages in thread
From: Tony Olech @ 2011-01-24 15:10 UTC (permalink / raw)
  To: Nicolas Pitre; +Cc: Chris Ball, linux-mmc, David Vrabel

On Mon, 2011-01-24 at 09:43 -0500, Nicolas Pitre wrote:
> On Mon, 24 Jan 2011, Tony Olech wrote:
> > On Fri, 2011-01-21 at 16:14 -0500, Nicolas Pitre wrote:
> > > On Fri, 21 Jan 2011, Tony Olech wrote:
> > > > There are 5 "do not initialise statics" errors reported by scripts/checkpatch.pl
> > > 
> > > you probably should fix those.  They are due to lines such as:
> > > 
> > > static int pad_input_to_usb_pkt = 0;
> > > static int disable_offload_processing = 0;
> > > static int force_1_bit_data_xfers = 0;
> > > static int force_polling_for_irqs = 0;
> > > 
> > > Since those are global variables, you may omit to initialize them to 0 
> > > which would then allocate them to the .bss section which is not included 
> > > in the compiled binary, and automatically cleared to zero at run time
> > > Nicolas
> > Yes they are due to exactly that, BUT I  have a great aversion to using
> > uninitialized variables. How can initializing all one's variables be
> > considered a bug????
> 
> Let me repeat myself.
> 
> Uninitialized global scope variables are by definition assigned to the 
> .bss section.  The .bss section is dynamically allocated at run time 
> rather than being stored in the compiled binary, and also cleared to 
> zero at run time.  So the preference is for zero-initialized global 
> variables not to be initialized at all because 1) they are implicitly 
> initialized to zero anyway, and 2) that makes the resulting binaries 
> smaller.
> So this is not about fixing a bug, but rather to conform to the adopted 
> policy for kernel code.
> Nicolas
Thanks for the reply. I had not realized that saving 20 bytes on the
binary size was so important. How then can one do static code analysis
to determine which uninitialized variables are uninitialized as
a result of a bug?
Tony Olech


^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH] mmc: USB SDIO/SD/MMC Host Controller (VUB300) driver Resubmission
  2011-01-23 14:01                       ` Nicolas Pitre
@ 2011-01-24 15:35                         ` Wolfram Sang
  2011-01-24 16:27                         ` Tony Olech
  1 sibling, 0 replies; 41+ messages in thread
From: Wolfram Sang @ 2011-01-24 15:35 UTC (permalink / raw)
  To: Nicolas Pitre; +Cc: Tony Olech, Chris Ball, linux-mmc, David Vrabel

[-- Attachment #1: Type: text/plain, Size: 1776 bytes --]


> > > > Should those be static anyhow? Being USB, you could probably hook up two of
> > > > those and want to operate one of them in this and the other one in another
> > > > mode?
> > > 
> > > You could.  Whether or not you would is another question.  Until then I 
> > > don't think it is worth bothering with such corner cases for the initial 
> > > merging of this driver.
> > 
> > A static variable which should be per-device is a corner-case? Frankly, I'd
> > think it is a flaw. Unless, of course, these module_params are only rarely
> > used. Which would lead to the question if they are really needed. There are
> > quite a lot.
> 
> Given their name, I'd say they are there only for debugging purposes.  
> So yes, they probably are rarely used, which doesn't mean they're 
> useless.  Same thing goes for maxfreq and nodma in mvsdio.c for example.

OK, I still wonder if all eight of these debugging-aids really need exposure to all
users. There is no documentation how and when to use them. But well...

About the "static" thing: Yes, there are already drivers having one or more
quirk parameters. That probably justifies it (personal taste aside).

> 
> > Also, I am not convinced of the need of a custom sysfs-file. Seeing its use
> > cases might help to identify a need which is probably better solved
> > generically.
> 
> I'm sure the author of this driver would be eager to hear from you with 
> a concrete suggestion.

? This is exactly why I asked for a use case? To see if something should be
exposed from the core and not the driver?

Regards,

   Wolfram

-- 
Pengutronix e.K.                           | Wolfram Sang                |
Industrial Linux Solutions                 | http://www.pengutronix.de/  |

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 197 bytes --]

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH] mmc: USB SDIO/SD/MMC Host Controller (VUB300) driver Resubmission
  2011-01-24 15:10                     ` Tony Olech
@ 2011-01-24 15:55                       ` Nicolas Pitre
  2011-01-24 16:08                         ` Tony Olech
  0 siblings, 1 reply; 41+ messages in thread
From: Nicolas Pitre @ 2011-01-24 15:55 UTC (permalink / raw)
  To: Tony Olech; +Cc: Chris Ball, linux-mmc, David Vrabel

On Mon, 24 Jan 2011, Tony Olech wrote:

> On Mon, 2011-01-24 at 09:43 -0500, Nicolas Pitre wrote:
> > Uninitialized global scope variables are by definition assigned to the 
> > .bss section.  The .bss section is dynamically allocated at run time 
> > rather than being stored in the compiled binary, and also cleared to 
> > zero at run time.  So the preference is for zero-initialized global 
> > variables not to be initialized at all because 1) they are implicitly 
> > initialized to zero anyway, and 2) that makes the resulting binaries 
> > smaller.
> > So this is not about fixing a bug, but rather to conform to the adopted 
> > policy for kernel code.
> > Nicolas
> Thanks for the reply. I had not realized that saving 20 bytes on the
> binary size was so important.

With the size of the kernel they add up.

> How then can one do static code analysis
> to determine which uninitialized variables are uninitialized as
> a result of a bug?

A good static code analysis tool should know already that uninitialized 
global variables are implicitly initialized to zero.  This is not the 
case for local variables of course.


Nicolas

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH] mmc: USB SDIO/SD/MMC Host Controller (VUB300) driver Resubmission
  2011-01-24 15:55                       ` Nicolas Pitre
@ 2011-01-24 16:08                         ` Tony Olech
  2011-01-24 16:28                           ` Nicolas Pitre
  0 siblings, 1 reply; 41+ messages in thread
From: Tony Olech @ 2011-01-24 16:08 UTC (permalink / raw)
  To: Nicolas Pitre; +Cc: Chris Ball, linux-mmc, David Vrabel

On Mon, 2011-01-24 at 10:55 -0500, Nicolas Pitre wrote:
> On Mon, 24 Jan 2011, Tony Olech wrote:
> 
> > On Mon, 2011-01-24 at 09:43 -0500, Nicolas Pitre wrote:
> > > Uninitialized global scope variables are by definition assigned to the 
> > > .bss section.  The .bss section is dynamically allocated at run time 
> > > rather than being stored in the compiled binary, and also cleared to 
> > > zero at run time.  So the preference is for zero-initialized global 
> > > variables not to be initialized at all because 1) they are implicitly 
> > > initialized to zero anyway, and 2) that makes the resulting binaries 
> > > smaller.
> > > So this is not about fixing a bug, but rather to conform to the adopted 
> > > policy for kernel code.
> > > Nicolas
> > Thanks for the reply. I had not realized that saving 20 bytes on the
> > binary size was so important.
> With the size of the kernel they add up.
> > How then can one do static code analysis
> > to determine which uninitialized variables are uninitialized as
> > a result of a bug?
> A good static code analysis tool should know already that uninitialized 
> global variables are implicitly initialized to zero.  This is not the 
> case for local variables of course
> Nicolas
Thanks for the reply.
I had assumed that for a module the global initialized variables were
loaded at module load. That then is a problem given that new devices
(and drivers) are appearing quite rapidly. I therefore do not understand
how the global initialized variables from an externally compiled kernel
module get added into the kernel image - does "depmod" do that?
A good static code analysis tool surely can only show a list of
uninitialized static global variables leaving it for the user to
trawl through and decide which are OK and which are not - so much
easier if one has no uninitialized static global variables to start
with.
Tony Olech



^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH] mmc: USB SDIO/SD/MMC Host Controller (VUB300) driver Resubmission
  2011-01-22 19:07                   ` Nicolas Pitre
  2011-01-23 10:09                     ` Wolfram Sang
@ 2011-01-24 16:17                     ` Tony Olech
  1 sibling, 0 replies; 41+ messages in thread
From: Tony Olech @ 2011-01-24 16:17 UTC (permalink / raw)
  To: Nicolas Pitre; +Cc: Wolfram Sang, Chris Ball, linux-mmc, David Vrabel

On Sat, 2011-01-22 at 14:07 -0500, Nicolas Pitre wrote:
> On Sat, 22 Jan 2011, Wolfram Sang wrote:
> > On Fri, Jan 21, 2011 at 04:14:46PM -0500, Nicolas Pitre wrote:
> > > On Fri, 21 Jan 2011, Tony Olech wrote:
> > > > Add a driver for Elan Digital System's VUB300 chip
> > > > which is a USB connected SDIO/SDmem/MMC host controller.
> > > > A VUB300 chip enables a USB 2.0 or USB 1.1 connected host
> > > > computer to use SDIO/SD/MMC cards without the need for
> > > > a directly connected, for example via PCI, SDIO host
> > > > controller.
> > > > There are 5 "do not initialise statics" errors reported by scripts/checkpatch.pl
> > > you probably should fix those.  They are due to lines such as:
> > > static int pad_input_to_usb_pkt = 0;
> > > static int disable_offload_processing = 0;
> > > static int force_1_bit_data_xfers = 0;
> > > static int force_polling_for_irqs = 0;
> > Should those be static anyhow? Being USB, you could probably hook up two of
> > those and want to operate one of them in this and the other one in another
> > mode?
> You could.  Whether or not you would is another question.  Until then I 
> don't think it is worth bothering with such corner cases for the initial 
> merging of this driver.
> Nicolas
Some of the module parameters are indeed per device, but how could
that be implemented?
Tony Olech


^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH] mmc: USB SDIO/SD/MMC Host Controller (VUB300) driver Resubmission
  2011-01-23 10:09                     ` Wolfram Sang
  2011-01-23 14:01                       ` Nicolas Pitre
@ 2011-01-24 16:21                       ` Tony Olech
  2011-01-25  9:13                         ` Wolfram Sang
  1 sibling, 1 reply; 41+ messages in thread
From: Tony Olech @ 2011-01-24 16:21 UTC (permalink / raw)
  To: Wolfram Sang; +Cc: Nicolas Pitre, Chris Ball, linux-mmc, David Vrabel

On Sun, 2011-01-23 at 11:09 +0100, Wolfram Sang wrote:
> On Sat, Jan 22, 2011 at 02:07:21PM -0500, Nicolas Pitre wrote:
> > On Sat, 22 Jan 2011, Wolfram Sang wrote:
> > > On Fri, Jan 21, 2011 at 04:14:46PM -0500, Nicolas Pitre wrote:
> > > > On Fri, 21 Jan 2011, Tony Olech wrote:
> > > > > Add a driver for Elan Digital System's VUB300 chip
> > > > > which is a USB connected SDIO/SDmem/MMC host controller.
> > > > > A VUB300 chip enables a USB 2.0 or USB 1.1 connected host
> > > > > computer to use SDIO/SD/MMC cards without the need for
> > > > > a directly connected, for example via PCI, SDIO host
> > > > > controller.t.
> > > > > There are 5 "do not initialise statics" errors reported by scripts/checkpatch.pl
> > > > you probably should fix those.  They are due to lines such as:
> > > > static int pad_input_to_usb_pkt = 0;
> > > > static int disable_offload_processing = 0;
> > > > static int force_1_bit_data_xfers = 0;
> > > > static int force_polling_for_irqs = 0;
> > > Should those be static anyhow? Being USB, you could probably hook up two of
> > > those and want to operate one of them in this and the other one in another
> > > mode?
> > You could.  Whether or not you would is another question.  Until then I 
> > don't think it is worth bothering with such corner cases
> Kind regards,
>    Wolfram
The intended use of the read-only sysfs file is to enable first-line
support staff to determine quickly what actual firmware file is being
used.
Tony Olech


^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH] mmc: USB SDIO/SD/MMC Host Controller (VUB300) driver Resubmission
  2011-01-23 14:01                       ` Nicolas Pitre
  2011-01-24 15:35                         ` Wolfram Sang
@ 2011-01-24 16:27                         ` Tony Olech
  1 sibling, 0 replies; 41+ messages in thread
From: Tony Olech @ 2011-01-24 16:27 UTC (permalink / raw)
  To: Nicolas Pitre; +Cc: Wolfram Sang, Chris Ball, linux-mmc, David Vrabel

On Sun, 2011-01-23 at 09:01 -0500, Nicolas Pitre wrote:
> On Sun, 23 Jan 2011, Wolfram Sang wrote:
> > On Sat, Jan 22, 2011 at 02:07:21PM -0500, Nicolas Pitre wrote:
> > > On Sat, 22 Jan 2011, Wolfram Sang wrote:
> > > > On Fri, Jan 21, 2011 at 04:14:46PM -0500, Nicolas Pitre wrote:
> > > > > On Fri, 21 Jan 2011, Tony Olech wrote:
> > > > > > Add a driver for Elan Digital System's VUB300 chip
> > > > > > which is a USB connected SDIO/SDmem/MMC host controller.
> > > > > > A VUB300 chip enables a USB 2.0 or USB 1.1 connected host
> > > > > > computer to use SDIO/SD/MMC cards without the need for
> > > > > > a directly connected, for example via PCI, SDIO host
> > > > > > controller.
> > > > > > There are 5 "do not initialise statics" errors reported by scripts/checkpatch.pl
> > > > > you probably should fix those.  They are due to lines such as:
> > > > > static int pad_input_to_usb_pkt = 0;
> > > > > static int disable_offload_processing = 0;
> > > > > static int force_1_bit_data_xfers = 0;
> > > > > static int force_polling_for_irqs = 0;
> > > > Should those be static anyhow? Being USB, you could probably hook up two of
> > > > those and want to operate one of them in this and the other one in another
> > > > mode?
> > > You could.  Whether or not you would is another question.  Until then I 
> > > don't think it is worth bothering with such corner cases for the initial 
> > > merging of this driver.
> > A static variable which should be per-device is a corner-case? Frankly, I'd
> > think it is a flaw. Unless, of course, these module_params are only rarely
> > used. Which would lead to the question if they are really needed. There are
> > quite a lot.
> Given their name, I'd say they are there only for debugging purposes.  
> So yes, they probably are rarely used, which doesn't mean they're 
> useless.  Same thing goes for maxfreq and nodma in mvsdio.c for example.

Basically the are diagnostic, but their use might enable a
non-functioning device to actually work.

> I'm sure the author of this driver would be eager to hear from you with 
> a concrete suggestion.
> Nicolas

YES

Tony olech


^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH] mmc: USB SDIO/SD/MMC Host Controller (VUB300) driver Resubmission
  2011-01-24 16:08                         ` Tony Olech
@ 2011-01-24 16:28                           ` Nicolas Pitre
  2011-01-24 16:43                             ` Tony Olech
  0 siblings, 1 reply; 41+ messages in thread
From: Nicolas Pitre @ 2011-01-24 16:28 UTC (permalink / raw)
  To: Tony Olech; +Cc: Chris Ball, linux-mmc, David Vrabel

On Mon, 24 Jan 2011, Tony Olech wrote:

> On Mon, 2011-01-24 at 10:55 -0500, Nicolas Pitre wrote:
> > On Mon, 24 Jan 2011, Tony Olech wrote:
> > 
> > > On Mon, 2011-01-24 at 09:43 -0500, Nicolas Pitre wrote:
> > > > Uninitialized global scope variables are by definition assigned to the 
> > > > .bss section.  The .bss section is dynamically allocated at run time 
> > > > rather than being stored in the compiled binary, and also cleared to 
> > > > zero at run time.  So the preference is for zero-initialized global 
> > > > variables not to be initialized at all because 1) they are implicitly 
> > > > initialized to zero anyway, and 2) that makes the resulting binaries 
> > > > smaller.
> > > > So this is not about fixing a bug, but rather to conform to the adopted 
> > > > policy for kernel code.
> > > > Nicolas
> > > Thanks for the reply. I had not realized that saving 20 bytes on the
> > > binary size was so important.
> > With the size of the kernel they add up.
> > > How then can one do static code analysis
> > > to determine which uninitialized variables are uninitialized as
> > > a result of a bug?
> > A good static code analysis tool should know already that uninitialized 
> > global variables are implicitly initialized to zero.  This is not the 
> > case for local variables of course
> > Nicolas
> Thanks for the reply.
> I had assumed that for a module the global initialized variables were
> loaded at module load. That then is a problem given that new devices
> (and drivers) are appearing quite rapidly. I therefore do not understand
> how the global initialized variables from an externally compiled kernel
> module get added into the kernel image - does "depmod" do that?


Sorry... I don't follow you here.

> A good static code analysis tool surely can only show a list of
> uninitialized static global variables leaving it for the user to
> trawl through and decide which are OK and which are not

Why?  Uninitialized static global variables should be assumed to be 
initialized to zero.  This is part of the C standard.  Nothing wrong 
here.  And the kernel coding style simply pushes it further by 
requesting it.


Nicolas

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH] mmc: USB SDIO/SD/MMC Host Controller (VUB300) driver Resubmission
  2011-01-24 16:28                           ` Nicolas Pitre
@ 2011-01-24 16:43                             ` Tony Olech
  0 siblings, 0 replies; 41+ messages in thread
From: Tony Olech @ 2011-01-24 16:43 UTC (permalink / raw)
  To: Nicolas Pitre; +Cc: Chris Ball, linux-mmc, David Vrabel

On Mon, 2011-01-24 at 11:28 -0500, Nicolas Pitre wrote:
> > I had assumed that for a module the global initialized variables were
> > loaded at module load. That then is a problem given that new devices
> > (and drivers) are appearing quite rapidly. I therefore do not understand
> > how the global initialized variables from an externally compiled kernel
> > module get added into the kernel image - does "depmod" do that?
> 
> Sorry... I don't follow you here.
> Nicolas

I obviously do not understand how the kernel build tools deal
with the global initialized variables, and at the present time
I have too many other priorities. But thanks for your explanation
which I partially understood (I think)

Tony Olech


^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH] mmc: USB SDIO/SD/MMC Host Controller (VUB300) driver Resubmission
  2011-01-24 16:21                       ` Tony Olech
@ 2011-01-25  9:13                         ` Wolfram Sang
  2011-01-25  9:35                           ` Tony Olech
  0 siblings, 1 reply; 41+ messages in thread
From: Wolfram Sang @ 2011-01-25  9:13 UTC (permalink / raw)
  To: Tony Olech; +Cc: Nicolas Pitre, Chris Ball, linux-mmc, David Vrabel

[-- Attachment #1: Type: text/plain, Size: 517 bytes --]


> The intended use of the read-only sysfs file is to enable first-line
> support staff to determine quickly what actual firmware file is being
> used.

You have that in the driver (and similar ones):

+	dev_info(&vub300->udev->dev, "requesting offload firmware %s\n",
+		 vub300->vub_name);

Won't checking the logs do?

Regards,

   Wolfram

-- 
Pengutronix e.K.                           | Wolfram Sang                |
Industrial Linux Solutions                 | http://www.pengutronix.de/  |

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 198 bytes --]

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH] mmc: USB SDIO/SD/MMC Host Controller (VUB300) driver Resubmission
  2011-01-25  9:13                         ` Wolfram Sang
@ 2011-01-25  9:35                           ` Tony Olech
  2011-01-25 20:40                             ` Nicolas Pitre
  0 siblings, 1 reply; 41+ messages in thread
From: Tony Olech @ 2011-01-25  9:35 UTC (permalink / raw)
  To: Wolfram Sang; +Cc: Nicolas Pitre, Chris Ball, linux-mmc, David Vrabel

On Tue, 2011-01-25 at 10:13 +0100, Wolfram Sang wrote:
> > The intended use of the read-only sysfs file is to enable first-line
> > support staff to determine quickly what actual firmware file is being
> > used.
> You have that in the driver (and similar ones):
> +	dev_info(&vub300->udev->dev, "requesting offload firmware %s\n",
> +		 vub300->vub_name);
> Won't checking the logs do?
>    Wolfram

Nearly, but there is one crucial difference, the logs
are historic and the sysfs is current.

Tony Olech



^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH] mmc: USB SDIO/SD/MMC Host Controller (VUB300) driver Resubmission
  2011-01-25  9:35                           ` Tony Olech
@ 2011-01-25 20:40                             ` Nicolas Pitre
  0 siblings, 0 replies; 41+ messages in thread
From: Nicolas Pitre @ 2011-01-25 20:40 UTC (permalink / raw)
  To: Tony Olech; +Cc: Wolfram Sang, Chris Ball, linux-mmc, David Vrabel

On Tue, 25 Jan 2011, Tony Olech wrote:

> On Tue, 2011-01-25 at 10:13 +0100, Wolfram Sang wrote:
> > > The intended use of the read-only sysfs file is to enable first-line
> > > support staff to determine quickly what actual firmware file is being
> > > used.
> > You have that in the driver (and similar ones):
> > +	dev_info(&vub300->udev->dev, "requesting offload firmware %s\n",
> > +		 vub300->vub_name);
> > Won't checking the logs do?
> >    Wolfram
> 
> Nearly, but there is one crucial difference, the logs
> are historic and the sysfs is current.

I personally have no problem with your exporting the firmware name 
through sysfs for human consumption only and that no user space tools 
rely on this.


Nicolas

^ permalink raw reply	[flat|nested] 41+ messages in thread

* [PATCH] mmc: USB SDIO/SD/MMC Host Controller (VUB300) driver Re-Resubmission
  2011-01-21 10:50             ` [PATCH] mmc: USB SDIO/SD/MMC Host Controller (VUB300) driver Resubmission Tony Olech
  2011-01-21 21:14               ` Nicolas Pitre
@ 2011-03-10 16:13               ` Tony Olech
  2011-03-15  3:01                 ` Chris Ball
  2011-03-15 16:23                 ` Arnd Bergmann
  1 sibling, 2 replies; 41+ messages in thread
From: Tony Olech @ 2011-03-10 16:13 UTC (permalink / raw)
  To: Chris Ball; +Cc: linux-mmc

[-- Attachment #1: Type: text/plain, Size: 2 bytes --]




[-- Attachment #2: vub300-driver-5.patch --]
[-- Type: text/x-patch, Size: 85706 bytes --]

Add a driver for Elan Digital System's VUB300 chip
which is a USB connected SDIO/SDmem/MMC host controller.
A VUB300 chip enables a USB 2.0 or USB 1.1 connected host
computer to use SDIO/SD/MMC cards without the need for
a directly connected, for example via PCI, SDIO host
controller.

Signed-off-by: Anthony F Olech <tony.olech@elandigitalsystems.com>
---
This is the third submission attempt.
There are no errors reported by scripts/checkpatch.pl
This driver has been tested on
a) 32bit x86
b) 64bit x86
c) dual processor
d) PowerPC
---
--- linux-2.6.38-rc8-vanilla/MAINTAINERS	2011-03-08 05:09:37.000000000 +0000
+++ linux-2.6.38-rc8-vub300/MAINTAINERS	2011-03-10 14:17:52.000000000 +0000
@@ -6735,6 +6735,13 @@ L:	lm-sensors@lm-sensors.org
 S:	Maintained
 F:	drivers/hwmon/vt8231.c
 
+VUB300 USB to SDIO/SD/MMC bridge chip
+M:	Tony Olech <tony.olech@elandigitalsystems.com>
+L:	linux-mmc@vger.kernel.org
+L:	linux-usb@vger.kernel.org
+S:	Supported
+F:	drivers/mmc/host/vub300.c
+
 W1 DALLAS'S 1-WIRE BUS
 M:	Evgeniy Polyakov <johnpol@2ka.mipt.ru>
 S:	Maintained
--- linux-2.6.38-rc8-vanilla/drivers/mmc/host/Makefile	2011-03-08 05:09:37.000000000 +0000
+++ linux-2.6.38-rc8-vub300/drivers/mmc/host/Makefile	2011-03-10 14:22:33.000000000 +0000
@@ -34,6 +34,7 @@ obj-$(CONFIG_SDH_BFIN)		+= bfin_sdh.o
 obj-$(CONFIG_MMC_DW)		+= dw_mmc.o
 obj-$(CONFIG_MMC_SH_MMCIF)	+= sh_mmcif.o
 obj-$(CONFIG_MMC_JZ4740)	+= jz4740_mmc.o
+obj-$(CONFIG_MMC_VUB300)	+= vub300.o
 obj-$(CONFIG_MMC_USHC)		+= ushc.o
 
 obj-$(CONFIG_MMC_SDHCI_PLTFM)			+= sdhci-platform.o
--- linux-2.6.38-rc8-vanilla/drivers/mmc/host/Kconfig	2011-03-08 05:09:37.000000000 +0000
+++ linux-2.6.38-rc8-vub300/drivers/mmc/host/Kconfig	2011-03-10 14:24:24.000000000 +0000
@@ -514,6 +514,37 @@ config MMC_JZ4740
 	  If you have a board based on such a SoC and with a SD/MMC slot,
 	  say Y or M here.
 
+config MMC_VUB300
+	tristate "VUB300 USB to SDIO/SD/MMC Host Controller support"
+	depends on USB
+	help
+	  This selects support for Elan Digital Systems' VUB300 chip.
+
+	  The VUB300 is a USB-SDIO Host Controller Interface chip
+	  that enables the host computer to use SDIO/SD/MMC cards
+	  via a USB 2.0 or USB 1.1 host.
+
+	  The VUB300 chip will be found in both physically separate
+	  USB to SDIO/SD/MMC adapters and embedded on some motherboards.
+
+	  The VUB300 chip supports SD and MMC memory cards in addition
+	  to single and multifunction SDIO cards.
+
+	  Some SDIO cards will need a firmware file to be loaded and
+	  sent to VUB300 chip in order to achieve better data throughput.
+	  Download these "Offload Pseudocode" from Elan Digital Systems'
+	  web-site http://www.elandigitalsystems.com/support/downloads.php
+	  and put them in /lib/firmware. Note that without these additional
+	  firmware files the VUB300 chip will still function, but not at
+	  the best obtainable data rate.
+
+	  To compile this mmc host controller driver as a module,
+	  choose M here: the module will be called vub300.
+
+	  If you have a computer with an embedded VUB300 chip
+	  or if you intend connecting a USB adapter based on a
+	  VUB300 chip say Y or M here.
+
 config MMC_USHC
 	tristate "USB SD Host Controller (USHC) support"
 	depends on USB
--- /dev/null	2011-03-10 15:59:32.096000002 +0000
+++ linux-2.6.38-rc8-vub300/Documentation/ABI/stable/vub300	2011-03-10 14:21:00.000000000 +0000
@@ -0,0 +1,11 @@
+
+What:		/sys/class/mmc_host/mmc<N>/operating_mode
+Date:		January 2011
+KernelVersion:	2.6.39
+Contact:	Tony Olech <tony.olech@elandigitalsystems.com>
+Description:	A read only diagnostic showing the state of the
+		vub300 sdio/mmc host controller driver, in particular
+		it shows which offload processing firmware file,
+		if any, is being used.
+Users:		-
+
--- /dev/null	2011-03-10 15:59:32.096000002 +0000
+++ linux-2.6.38-rc8-vub300/drivers/mmc/host/vub300.c	2011-03-10 16:04:23.000000000 +0000
@@ -0,0 +1,2605 @@
+/*
+ * Remote VUB300 SDIO/SDmem Host Controller Driver
+ *
+ * Copyright (C) 2010 Elan Digital Systems Limited
+ *
+ * based on USB Skeleton driver - 2.2
+ *
+ * Copyright (C) 2001-2004 Greg Kroah-Hartman (greg@kroah.com)
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2
+ *
+ * VUB300: is a USB 2.0 client device with a single SDIO/SDmem/MMC slot
+ *         Any SDIO/SDmem/MMC device plugged into the VUB300 will appear,
+ *         by virtue of this driver, to have been plugged into a local
+ *         SDIO host controller, similar to, say, a PCI Ricoh controller
+ *         This is because this kernel device driver is both a USB 2.0
+ *         client device driver AND an MMC host controller driver. Thus
+ *         if there is an existing driver for the inserted SDIO/SDmem/MMC
+ *         device then that driver will be used by the kernel to manage
+ *         the device in exactly the same fashion as if it had been
+ *         directly plugged into, say, a local pci bus Ricoh controller
+ *
+ * RANT: this driver was written using a display 128x48 - converting it
+ *       to a line width of 80 makes it very difficult to support. In
+ *       particular functions have been broken down into sub functions
+ *       and the original meaningful names have been shortened into
+ *       cryptic ones.
+ *       The problem is that executing a fragment of code subject to
+ *       two conditions means an indentation of 24, thus leaving only
+ *       56 characters for a C statement. And that is quite ridiculous!
+ *
+ * Data types: data passed to/from the VUB300 is fixed to a number of
+ *             bits and driver data fields reflect that limit by using
+ *             u8, u16, u32
+ */
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/kref.h>
+#include <linux/uaccess.h>
+#include <linux/usb.h>
+#include <linux/mutex.h>
+#include <linux/mmc/host.h>
+#include <linux/mmc/card.h>
+#include <linux/mmc/sdio_func.h>
+#include <linux/mmc/sdio_ids.h>
+#include <linux/workqueue.h>
+#include <linux/ctype.h>
+#include <linux/firmware.h>
+#include <linux/scatterlist.h>
+struct HostController_Info {
+	u8 Size;
+	u16 FirmwareVer;
+	u8 NumberOfPorts;
+} __packed;
+struct SD_Command_Header {
+	u8 HeaderSize;
+	u8 HeaderType;
+	u8 PortNumber;
+	u8 Command_Type; /* Bit7 - Rd/Wr */
+	u8 CommandIndex;
+	u8 TransferSize[4]; /* ReadSize + ReadSize */
+	u8 ResponseType;
+	u8 Arguments[4];
+	u8 BlockCount[2];
+	u8 BlockSize[2];
+	u8 BlockBoundary[2];
+	u8 Reserved[44]; /* to pad out to 64 bytes */
+} __packed;
+struct SD_IRQpoll_Header {
+	u8 HeaderSize;
+	u8 HeaderType;
+	u8 PortNumber;
+	u8 Command_Type; /* Bit7 - Rd/Wr */
+	u8 Padding[16]; /* don't ask why !! */
+	u8 Poll_Timeout_MSB;
+	u8 Poll_Timeout_LSB;
+	u8 Reserved[42]; /* to pad out to 64 bytes */
+} __packed;
+struct SD_Common_Header {
+	u8 HeaderSize;
+	u8 HeaderType;
+	u8 PortNumber;
+} __packed;
+struct SD_Response_Header {
+	u8 HeaderSize;
+	u8 HeaderType;
+	u8 PortNumber;
+	u8 Command_Type;
+	u8 CommandIndex;
+	u8 CommandResponse[0];
+} __packed;
+struct SD_Status_Header {
+	u8 HeaderSize;
+	u8 HeaderType;
+	u8 PortNumber;
+	u16 PortFlags;
+	u32 SDIOclock;
+	u16 HostHeaderSize;
+	u16 FuncHeaderSize;
+	u16 CtrlHeaderSize;
+} __packed;
+struct SD_Error_Header {
+	u8 HeaderSize;
+	u8 HeaderType;
+	u8 PortNumber;
+	u8 ErrorCode;
+} __packed;
+struct SD_Interrupt_Header {
+	u8 HeaderSize;
+	u8 HeaderType;
+	u8 PortNumber;
+} __packed;
+struct Offload_Registers_Access {
+	u8 Command_Byte[4];
+	u8 Respond_Byte[4];
+} __packed;
+#define INTERRUPT_REGISTER_ACCESSES 15
+struct SD_Offloaded_Interrupt {
+	u8 HeaderSize;
+	u8 HeaderType;
+	u8 PortNumber;
+	struct Offload_Registers_Access reg[INTERRUPT_REGISTER_ACCESSES];
+} __packed;
+struct SD_Register_Header {
+	u8 HeaderSize;
+	u8 HeaderType;
+	u8 PortNumber;
+	u8 Command_Type;
+	u8 CommandIndex;
+	u8 CommandResponse[6];
+} __packed;
+#define PIGGYBACK_REGISTER_ACCESSES 14
+struct SD_Offloaded_Piggyback {
+	struct SD_Register_Header sdio;
+	struct Offload_Registers_Access reg[PIGGYBACK_REGISTER_ACCESSES];
+} __packed;
+union SD_Response {
+	struct SD_Common_Header common;
+	struct SD_Status_Header status;
+	struct SD_Error_Header error;
+	struct SD_Interrupt_Header interrupt;
+	struct SD_Response_Header response;
+	struct SD_Offloaded_Interrupt irq;
+	struct SD_Offloaded_Piggyback pig;
+} __packed;
+union SD_Command {
+	struct SD_Command_Header head;
+	struct SD_IRQpoll_Header poll;
+} __packed;
+enum SD_RESPONSE_TYPE {
+	SDRT_UNSPECIFIED = 0,
+	SDRT_NONE,
+	SDRT_1,
+	SDRT_1B,
+	SDRT_2,
+	SDRT_3,
+	SDRT_4,
+	SDRT_5,
+	SDRT_5B,
+	SDRT_6,
+	SDRT_7,
+} __packed;
+#define RESPONSE_INTERRUPT 0x01
+#define RESPONSE_ERROR 0x02
+#define RESPONSE_STATUS 0x03
+#define RESPONSE_IRQ_DISABLED 0x05
+#define RESPONSE_IRQ_ENABLED 0x06
+#define RESPONSE_PIGGYBACKED 0x07
+#define RESPONSE_NO_INTERRUPT 0x08
+#define RESPONSE_PIG_DISABLED 0x09
+#define RESPONSE_PIG_ENABLED 0x0A
+#define SD_ERROR_1BIT_TIMEOUT   0x01
+#define SD_ERROR_4BIT_TIMEOUT   0x02
+#define SD_ERROR_1BIT_CRC_WRONG 0x03
+#define SD_ERROR_4BIT_CRC_WRONG 0x04
+#define SD_ERROR_1BIT_CRC_ERROR 0x05
+#define SD_ERROR_4BIT_CRC_ERROR 0x06
+#define SD_ERROR_NO_CMD_ENDBIT  0x07
+#define SD_ERROR_NO_1BIT_DATEND 0x08
+#define SD_ERROR_NO_4BIT_DATEND 0x09
+#define SD_ERROR_1BIT_UNEXPECTED_TIMEOUT    0x0A
+#define SD_ERROR_4BIT_UNEXPECTED_TIMEOUT    0x0B
+#define SD_ERROR_ILLEGAL_COMMAND    0x0C
+#define SD_ERROR_NO_DEVICE 0x0D
+#define SD_ERROR_TRANSFER_LENGTH    0x0E
+#define SD_ERROR_1BIT_DATA_TIMEOUT  0x0F
+#define SD_ERROR_4BIT_DATA_TIMEOUT  0x10
+#define SD_ERROR_ILLEGAL_STATE  0x11
+#define SD_ERROR_UNKNOWN_ERROR  0x12
+#define SD_ERROR_RESERVED_ERROR 0x13
+#define SD_ERROR_INVALID_FUNCTION   0x14
+#define SD_ERROR_OUT_OF_RANGE   0x15
+#define SD_ERROR_STAT_CMD 0x16
+#define SD_ERROR_STAT_DATA 0x17
+#define SD_ERROR_STAT_CMD_TIMEOUT 0x18
+#define SD_ERROR_SDCRDY_STUCK 0x19
+#define SD_ERROR_UNHANDLED 0x1A
+#define SD_ERROR_OVERRUN 0x1B
+#define SD_ERROR_PIO_TIMEOUT 0x1C
+#define INITIALIZE_VALUE_TO_ZERO 0
+MODULE_AUTHOR("Tony Olech <tony.olech@elandigitalsystems.com>");
+MODULE_DESCRIPTION("VUB300 USB to SD/MMC/SDIO adapter driver");
+MODULE_LICENSE("GPL");
+#define FUN(c) (0x000007 & (c->arg>>28))
+#define REG(c) (0x01FFFF & (c->arg>>9))
+static int limit_speed_to_24_MHz = INITIALIZE_VALUE_TO_ZERO;
+module_param(limit_speed_to_24_MHz, bool, 0644);
+MODULE_PARM_DESC(limit_speed_to_24_MHz, "Limit Max SDIO Clock Speed to 24 MHz");
+static int pad_input_to_usb_pkt = INITIALIZE_VALUE_TO_ZERO;
+module_param(pad_input_to_usb_pkt, bool, 0644);
+MODULE_PARM_DESC(pad_input_to_usb_pkt,
+		 "Pad USB data input transfers to whole USB Packet");
+static int disable_offload_processing = INITIALIZE_VALUE_TO_ZERO;
+module_param(disable_offload_processing, bool, 0644);
+MODULE_PARM_DESC(disable_offload_processing, "Disable Offload Processing");
+static int force_1_bit_data_xfers = INITIALIZE_VALUE_TO_ZERO;
+module_param(force_1_bit_data_xfers, bool, 0644);
+MODULE_PARM_DESC(force_1_bit_data_xfers,
+		 "Force SDIO Data Transfers to 1-bit Mode");
+static int force_polling_for_irqs = INITIALIZE_VALUE_TO_ZERO;
+module_param(force_polling_for_irqs, bool, 0644);
+MODULE_PARM_DESC(force_polling_for_irqs, "Force Polling for SDIO interrupts");
+static int firmware_irqpoll_timeout = 1024;
+module_param(firmware_irqpoll_timeout, int, 0644);
+MODULE_PARM_DESC(firmware_irqpoll_timeout, "VUB300 firmware irqpoll timeout");
+static int force_max_req_size = 128;
+module_param(force_max_req_size, int, 0644);
+MODULE_PARM_DESC(force_max_req_size, "set max request size in kBytes");
+#ifdef SMSC_DEVELOPMENT_BOARD
+static int firmware_rom_wait_states = 0x04;
+#else
+static int firmware_rom_wait_states = 0x1C;
+#endif
+module_param(firmware_rom_wait_states, bool, 0644);
+MODULE_PARM_DESC(firmware_rom_wait_states,
+		 "ROM wait states byte=RRRIIEEE (Reserved Internal External)");
+/* Define these values to match your devices */
+#define ELAN_VENDOR_ID 0x2201
+#define VUB300_VENDOR_ID 0x0424
+#define VUB300_PRODUCT_ID 0x012C
+#define FIRMWARE_BLOCK_BOUNDARY 1024
+/* table of devices that work with this driver */
+static struct usb_device_id vub300_table[] = {
+	{USB_DEVICE(ELAN_VENDOR_ID, VUB300_PRODUCT_ID)},
+	{USB_DEVICE(VUB300_VENDOR_ID, VUB300_PRODUCT_ID)},
+	{} /* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE(usb, vub300_table);
+static struct workqueue_struct *cmndworkqueue;
+static struct workqueue_struct *pollworkqueue;
+static struct workqueue_struct *deadworkqueue;
+static inline int interface_to_InterfaceNumber(struct usb_interface *interface)
+{
+	if (!interface)
+		return -1;
+	if (!interface->cur_altsetting)
+		return -1;
+	return interface->cur_altsetting->desc.bInterfaceNumber;
+}
+
+struct sdio_register {
+	unsigned func_num:3;
+	unsigned sdio_reg:17;
+	unsigned activate:1;
+	unsigned prepared:1;
+	unsigned regvalue:8;
+	unsigned response:8;
+	unsigned sparebit:26;
+};
+struct vub300_mmc_host {
+	struct usb_device *udev;
+	struct usb_interface *interface;
+	struct kref kref;
+	struct mutex cmd_mutex;
+	struct mutex irq_mutex;
+	char vub_name[3 + (9 * 8) + 4 + 1]; /* max of 7 sdio fn's */
+	u8 cmnd_out_ep; /* EndPoint for commands */
+	u8 cmnd_res_ep; /* EndPoint for responses */
+	u8 data_out_ep; /* EndPoint for out data */
+	u8 data_inp_ep; /* EndPoint for inp data */
+	unsigned card_powered:1;
+	unsigned card_present:1;
+	unsigned read_only:1;
+	unsigned large_usb_packets:1;
+	unsigned app_spec:1; /* ApplicationSpecific */
+	unsigned irq_enabled:1; /* by the MMC CORE */
+	unsigned irq_disabled:1; /* in the firmware */
+	unsigned bus_width:4;
+	u8 total_offload_count;
+	u8 dynamic_register_count;
+	u8 resp_len;
+	u32 datasize;
+	int errors;
+	int usb_transport_fail;
+	int usb_timed_out;
+	int irqs_queued;
+	struct sdio_register sdio_register[16];
+	struct offload_interrupt_function_register {
+#define MAXREGBITS 4
+#define MAXREGS (1<<MAXREGBITS)
+#define MAXREGMASK (MAXREGS-1)
+		u8 offload_count;
+		u32 offload_point;
+		struct Offload_Registers_Access reg[MAXREGS];
+	} fn[8];
+	u16 fbs[8]; /* Function Block Size */
+	struct mmc_command *cmd;
+	struct mmc_request *req;
+	struct mmc_data *data;
+	struct mmc_host *mmc;
+	struct urb *urb;
+	struct urb *command_out_urb;
+	struct urb *command_res_urb;
+	struct completion command_complete;
+	struct completion irqpoll_complete;
+	union SD_Command cmnd;
+	union SD_Response resp;
+	struct timer_list sg_transfer_timer;
+	struct usb_sg_request sg_request;
+	struct timer_list inactivity_timer;
+	struct work_struct deadwork;
+	struct work_struct cmndwork;
+	struct delayed_work pollwork;
+	struct HostController_Info hc_info;
+	struct SD_Status_Header system_port_status;
+	u8 padded_buffer[64];
+};
+#define kref_to_vub300_mmc_host(d) container_of(d, struct vub300_mmc_host, kref)
+#define SET_TRANSFER_PSEUDOCODE 21
+#define SET_INTERRUPT_PSEUDOCODE 20
+#define SET_FAILURE_MODE 18
+#define SET_ROM_WAIT_STATES 16
+#define SET_IRQ_ENABLE 13
+#define SET_CLOCK_SPEED 11
+#define SET_FUNCTION_BLOCK_SIZE 9
+#define SET_SD_DATA_MODE 6
+#define SET_SD_POWER 4
+#define ENTER_DFU_MODE 3
+#define GET_HC_INF0 1
+#define GET_SYSTEM_PORT_STATUS 0
+static void vub300_delete(struct kref *kref)
+{				/* kref callback - softirq */
+	struct vub300_mmc_host *vub300 = kref_to_vub300_mmc_host(kref);
+	struct mmc_host *mmc = vub300->mmc;
+	usb_free_urb(vub300->command_out_urb);
+	vub300->command_out_urb = NULL;
+	usb_free_urb(vub300->command_res_urb);
+	vub300->command_res_urb = NULL;
+	usb_put_dev(vub300->udev);
+	mmc_free_host(mmc);
+	/*
+	 * and hence also frees vub300
+	 * which is contained at the end of struct mmc
+	 */
+}
+
+static ssize_t __show_operating_mode(struct vub300_mmc_host *vub300,
+				    struct mmc_host *mmc, char *buf)
+{
+	int usb_packet_size = vub300->large_usb_packets ? 512 : 64;
+	if (vub300->vub_name[0])
+		return sprintf(buf, "VUB %s %s %d MHz %s %d byte USB packets"
+				" using %s\n",
+		       (mmc->caps & MMC_CAP_SDIO_IRQ) ? "IRQs" : "POLL",
+		       (mmc->caps & MMC_CAP_4_BIT_DATA) ? "4-bit" : "1-bit",
+		       mmc->f_max / 1000000,
+		       pad_input_to_usb_pkt ? "padding input data to" : "with",
+		       usb_packet_size, vub300->vub_name);
+	else
+		return sprintf(buf, "VUB %s %s %d MHz %s %d byte USB packets"
+				" and no offload processing\n",
+		       (mmc->caps & MMC_CAP_SDIO_IRQ) ? "IRQs" : "POLL",
+		       (mmc->caps & MMC_CAP_4_BIT_DATA) ? "4-bit" : "1-bit",
+		       mmc->f_max / 1000000,
+		       pad_input_to_usb_pkt ? "padding input data to" : "with",
+		       usb_packet_size);
+}
+
+static ssize_t show_operating_mode(struct device *dev,
+				  struct device_attribute *attr, char *buf)
+{
+	struct mmc_host *mmc = container_of(dev, struct mmc_host, class_dev);
+	if (mmc) {
+		struct vub300_mmc_host *vub300 = mmc_priv(mmc);
+		return __show_operating_mode(vub300, mmc, buf);
+	} else {
+		return sprintf(buf, "VUB driver has no attached device");
+	}
+}
+
+static DEVICE_ATTR(operating_mode, S_IRUGO, show_operating_mode, NULL);
+static struct attribute *vub300_attrs[] = {
+	&dev_attr_operating_mode.attr,
+	NULL,
+};
+
+static struct attribute_group vub300_attr_grp = {
+	.attrs = vub300_attrs,
+};
+
+static void vub300_queue_cmnd_work(struct vub300_mmc_host *vub300)
+{
+	kref_get(&vub300->kref);
+	if (queue_work(cmndworkqueue, &vub300->cmndwork)) {
+		/*
+		 * then the cmndworkqueue was not previously
+		 * running and the above get ref is obvious
+		 * required and will be put when the thread
+		 * terminates by a specific call
+		 */
+	} else {
+		/*
+		 * the cmndworkqueue was already running from
+		 * a previous invocation and thus to keep the
+		 * kref counts correct we must undo the get
+		 */
+		kref_put(&vub300->kref, vub300_delete);
+	}
+}
+
+static void vub300_queue_poll_work(struct vub300_mmc_host *vub300, int delay)
+{
+	kref_get(&vub300->kref);
+	if (queue_delayed_work(pollworkqueue, &vub300->pollwork, delay)) {
+		/*
+		 * then the pollworkqueue was not previously
+		 * running and the above get ref is obvious
+		 * required and will be put when the thread
+		 * terminates by a specific call
+		 */
+	} else {
+		/*
+		 * the pollworkqueue was already running from
+		 * a previous invocation and thus to keep the
+		 * kref counts correct we must undo the get
+		 */
+		kref_put(&vub300->kref, vub300_delete);
+	}
+}
+
+static void vub300_queue_dead_work(struct vub300_mmc_host *vub300)
+{
+	kref_get(&vub300->kref);
+	if (queue_work(deadworkqueue, &vub300->deadwork)) {
+		/*
+		 * then the deadworkqueue was not previously
+		 * running and the above get ref is obvious
+		 * required and will be put when the thread
+		 * terminates by a specific call
+		 */
+	} else {
+		/*
+		 * the deadworkqueue was already running from
+		 * a previous invocation and thus to keep the
+		 * kref counts correct we must undo the get
+		 */
+		kref_put(&vub300->kref, vub300_delete);
+	}
+}
+
+static void irqpoll_res_completed(struct urb *urb)
+{				/* urb completion handler - hardirq */
+	/*
+	 * cmd_mutex is held by vub300_pollwork_thread
+	 */
+	struct vub300_mmc_host *vub300 = (struct vub300_mmc_host *)urb->context;
+	if (urb->status)
+		vub300->usb_transport_fail = urb->status;
+	complete(&vub300->irqpoll_complete);
+}
+
+static void irqpoll_out_completed(struct urb *urb)
+{				/* urb completion handler - hardirq */
+	/*
+	 * cmd_mutex is held by vub300_pollwork_thread
+	 */
+	struct vub300_mmc_host *vub300 = (struct vub300_mmc_host *)urb->context;
+	if (urb->status) {
+		vub300->usb_transport_fail = urb->status;
+		complete(&vub300->irqpoll_complete);
+		return;
+	} else {
+		int ret;
+		unsigned int pipe =
+		    usb_rcvbulkpipe(vub300->udev, vub300->cmnd_res_ep);
+		usb_fill_bulk_urb(vub300->command_res_urb, vub300->udev, pipe,
+				  &vub300->resp, sizeof(vub300->resp),
+				  irqpoll_res_completed, vub300);
+		vub300->command_res_urb->actual_length = 0;
+		ret = usb_submit_urb(vub300->command_res_urb, GFP_NOIO);
+		if (ret) {
+			vub300->usb_transport_fail = ret;
+			complete(&vub300->irqpoll_complete);
+		}
+		return;
+	}
+}
+
+static void send_irqpoll(struct vub300_mmc_host *vub300)
+{
+	/*
+	 * cmd_mutex is held by vub300_pollwork_thread
+	 */
+	int retval;
+	int timeout = 0xFFFF & (0x0001FFFF - firmware_irqpoll_timeout);
+	vub300->cmnd.poll.HeaderSize = 22;
+	vub300->cmnd.poll.HeaderType = 1;
+	vub300->cmnd.poll.PortNumber = 0;
+	vub300->cmnd.poll.Command_Type = 2;
+	vub300->cmnd.poll.Poll_Timeout_LSB = 0xFF & (unsigned)timeout;
+	vub300->cmnd.poll.Poll_Timeout_MSB = 0xFF & (unsigned)(timeout >> 8);
+	usb_fill_bulk_urb(vub300->command_out_urb, vub300->udev,
+			  usb_sndbulkpipe(vub300->udev, vub300->cmnd_out_ep)
+			  , &vub300->cmnd, sizeof(vub300->cmnd)
+			  , irqpoll_out_completed, vub300);
+	retval = usb_submit_urb(vub300->command_out_urb, GFP_ATOMIC);
+	if (0 > retval) {
+		vub300->usb_transport_fail = retval;
+		vub300_queue_poll_work(vub300, 1);
+		complete(&vub300->irqpoll_complete);
+		return;
+	} else {
+		return;
+	}
+}
+
+static void new_system_port_status(struct vub300_mmc_host *vub300)
+{
+	int old_card_present = vub300->card_present;
+	int new_card_present =
+	    (0x0001 & vub300->system_port_status.PortFlags) ? 1 : 0;
+	vub300->read_only =
+	    (0x0010 & vub300->system_port_status.PortFlags) ? 1 : 0;
+	if (new_card_present && !old_card_present) {
+		dev_info(&vub300->udev->dev, "card just inserted\n");
+		vub300->card_present = 1;
+		vub300->bus_width = 0;
+		if (disable_offload_processing)
+			strncpy(vub300->vub_name, "EMPTY Processing Disabled",
+				sizeof(vub300->vub_name));
+		else
+			vub300->vub_name[0] = 0;
+		mmc_detect_change(vub300->mmc, 1);
+		return;
+	} else if (!new_card_present && old_card_present) {
+		dev_info(&vub300->udev->dev, "card just ejected\n");
+		vub300->card_present = 0;
+		mmc_detect_change(vub300->mmc, 0);
+		return;
+	} else {
+		return;
+	}
+}
+
+static void __add_offloaded_reg_to_fifo(struct vub300_mmc_host *vub300,
+					struct Offload_Registers_Access
+					*register_access, u8 Function)
+{
+	u8 r =
+	    vub300->fn[Function].offload_point +
+	    vub300->fn[Function].offload_count;
+	memcpy(&vub300->fn[Function].reg[MAXREGMASK & r]
+	       , register_access, sizeof(struct Offload_Registers_Access));
+	vub300->fn[Function].offload_count += 1;
+	vub300->total_offload_count += 1;
+}
+
+static void add_offloaded_reg(struct vub300_mmc_host *vub300,
+			      struct Offload_Registers_Access *register_access)
+{
+	u32 Register = ((0x03 & register_access->Command_Byte[0]) << 15)
+	    | ((0xFF & register_access->Command_Byte[1]) << 7)
+	    | ((0xFE & register_access->Command_Byte[2]) >> 1);
+	u8 Function = ((0x70 & register_access->Command_Byte[0]) >> 4);
+	u8 regs = vub300->dynamic_register_count;
+	u8 i = 0;
+	while (0 < regs-- && 1 == vub300->sdio_register[i].activate) {
+		if ((vub300->sdio_register[i].func_num == Function)
+		    && (vub300->sdio_register[i].sdio_reg == Register)
+		    ) {
+			if (0 == vub300->sdio_register[i].prepared)
+				vub300->sdio_register[i].prepared = 1;
+			vub300->sdio_register[i].response =
+			    register_access->Respond_Byte[2];
+			vub300->sdio_register[i].regvalue =
+			    register_access->Respond_Byte[3];
+			return;
+		} else {
+			i += 1;
+			continue;
+		}
+	};
+	__add_offloaded_reg_to_fifo(vub300, register_access, Function);
+}
+
+static void check_vub300_port_status(struct vub300_mmc_host *vub300)
+{
+	/*
+	 * cmd_mutex is held by vub300_pollwork_thread,
+	 * vub300_deadwork_thread or vub300_cmndwork_thread
+	 */
+	int retval;
+	retval =
+	    usb_control_msg(vub300->udev, usb_rcvctrlpipe(vub300->udev, 0),
+			    GET_SYSTEM_PORT_STATUS,
+			    USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			    0x0000, 0x0000, &vub300->system_port_status,
+			    sizeof(vub300->system_port_status), HZ);
+	if (sizeof(vub300->system_port_status) == retval)
+		new_system_port_status(vub300);
+}
+
+static void __vub300_irqpoll_response(struct vub300_mmc_host *vub300)
+{
+	/*
+	 * cmd_mutex is held by vub300_pollwork_thread
+	 */
+	if (0 == vub300->command_res_urb->actual_length) {
+		mutex_unlock(&vub300->cmd_mutex);
+		kref_put(&vub300->kref, vub300_delete);
+		return;
+	} else if (RESPONSE_INTERRUPT == vub300->resp.common.HeaderType) {
+		mutex_lock(&vub300->irq_mutex);
+		if (vub300->irq_enabled)
+			mmc_signal_sdio_irq(vub300->mmc);
+		else
+			vub300->irqs_queued += 1;
+		vub300->irq_disabled = 1;
+		mutex_unlock(&vub300->irq_mutex);
+		mutex_unlock(&vub300->cmd_mutex);
+		kref_put(&vub300->kref, vub300_delete);
+		return;
+	} else if (RESPONSE_ERROR == vub300->resp.common.HeaderType) {
+		if (SD_ERROR_NO_DEVICE == vub300->resp.error.ErrorCode)
+			check_vub300_port_status(vub300);
+		mutex_unlock(&vub300->cmd_mutex);
+		vub300_queue_poll_work(vub300, HZ);
+		kref_put(&vub300->kref, vub300_delete);
+		return;
+	} else if (RESPONSE_STATUS == vub300->resp.common.HeaderType) {
+		vub300->system_port_status = vub300->resp.status;
+		new_system_port_status(vub300);
+		mutex_unlock(&vub300->cmd_mutex);
+		if (!vub300->card_present)
+			vub300_queue_poll_work(vub300, HZ / 5);
+		kref_put(&vub300->kref, vub300_delete);
+		return;
+	} else if (RESPONSE_IRQ_DISABLED == vub300->resp.common.HeaderType) {
+		int offloaded_data_length = vub300->resp.common.HeaderSize - 3;
+		int register_count = offloaded_data_length >> 3;
+		int ri = 0;
+		while (register_count--) {
+			add_offloaded_reg(vub300, &vub300->resp.irq.reg[ri]);
+			ri += 1;
+		}
+		mutex_lock(&vub300->irq_mutex);
+		if (vub300->irq_enabled)
+			mmc_signal_sdio_irq(vub300->mmc);
+		else
+			vub300->irqs_queued += 1;
+		vub300->irq_disabled = 1;
+		mutex_unlock(&vub300->irq_mutex);
+		mutex_unlock(&vub300->cmd_mutex);
+		kref_put(&vub300->kref, vub300_delete);
+		return;
+	} else if (RESPONSE_IRQ_ENABLED == vub300->resp.common.HeaderType) {
+		int offloaded_data_length = vub300->resp.common.HeaderSize - 3;
+		int register_count = offloaded_data_length >> 3;
+		int ri = 0;
+		while (register_count--) {
+			add_offloaded_reg(vub300, &vub300->resp.irq.reg[ri]);
+			ri += 1;
+		}
+		mutex_lock(&vub300->irq_mutex);
+		if (vub300->irq_enabled)
+			mmc_signal_sdio_irq(vub300->mmc);
+		else if (vub300->irqs_queued)
+			vub300->irqs_queued += 1;
+		else
+			vub300->irqs_queued += 1;
+		vub300->irq_disabled = 0;
+		mutex_unlock(&vub300->irq_mutex);
+		mutex_unlock(&vub300->cmd_mutex);
+		kref_put(&vub300->kref, vub300_delete);
+		return;
+	} else if (RESPONSE_NO_INTERRUPT == vub300->resp.common.HeaderType) {
+		vub300_queue_poll_work(vub300, 1);
+		mutex_unlock(&vub300->cmd_mutex);
+		kref_put(&vub300->kref, vub300_delete);
+		return;
+	} else {
+		mutex_unlock(&vub300->cmd_mutex);
+		kref_put(&vub300->kref, vub300_delete);
+		return;
+	}
+}
+
+static void __do_poll(struct vub300_mmc_host *vub300)
+{
+	/*
+	 * cmd_mutex is held by vub300_pollwork_thread
+	 */
+	long commretval;
+	mod_timer(&vub300->inactivity_timer, jiffies + HZ);
+	init_completion(&vub300->irqpoll_complete);
+	send_irqpoll(vub300);
+	commretval =
+	    wait_for_completion_timeout(&vub300->irqpoll_complete,
+					msecs_to_jiffies(500));
+	if (vub300->usb_transport_fail) {
+		mutex_unlock(&vub300->cmd_mutex);
+		kref_put(&vub300->kref, vub300_delete);
+		return;
+	} else if (0 == commretval) {
+		vub300->usb_timed_out = 1;
+		usb_kill_urb(vub300->command_out_urb);
+		usb_kill_urb(vub300->command_res_urb);
+		mutex_unlock(&vub300->cmd_mutex);
+		kref_put(&vub300->kref, vub300_delete);
+		return;
+	} else if (0 > commretval) {
+		vub300_queue_poll_work(vub300, 1);
+		mutex_unlock(&vub300->cmd_mutex);
+		kref_put(&vub300->kref, vub300_delete);
+		return;
+	} else { /*(0 < commretval) */
+
+		__vub300_irqpoll_response(vub300);
+		return;
+	}
+}
+
+/* this thread runs only when the driver
+ * is trying to poll the device for an IRQ
+ */
+static void vub300_pollwork_thread(struct work_struct *work)
+{				/* NOT irq */
+	struct vub300_mmc_host *vub300 =
+	    container_of(work, struct vub300_mmc_host, pollwork.work);
+	if (!vub300->interface) {
+		kref_put(&vub300->kref, vub300_delete);
+		return;
+	}
+	mutex_lock(&vub300->cmd_mutex);
+	if (vub300->cmd) {
+		mutex_unlock(&vub300->cmd_mutex);
+		vub300_queue_poll_work(vub300, 1);
+		kref_put(&vub300->kref, vub300_delete);
+	} else if (!vub300->card_present) {
+		mutex_unlock(&vub300->cmd_mutex);
+		kref_put(&vub300->kref, vub300_delete);
+	} else { /* vub300->card_present */
+		mutex_lock(&vub300->irq_mutex);
+		if (!vub300->irq_enabled) {
+			mutex_unlock(&vub300->irq_mutex);
+			mutex_unlock(&vub300->cmd_mutex);
+			kref_put(&vub300->kref, vub300_delete);
+		} else if (vub300->irqs_queued) {
+			vub300->irqs_queued -= 1;
+			mmc_signal_sdio_irq(vub300->mmc);
+			mod_timer(&vub300->inactivity_timer, jiffies + HZ);
+			mutex_unlock(&vub300->irq_mutex);
+			mutex_unlock(&vub300->cmd_mutex);
+			kref_put(&vub300->kref, vub300_delete);
+		} else { /* NOT vub300->irqs_queued */
+			mutex_unlock(&vub300->irq_mutex);
+			__do_poll(vub300);
+		}
+	}
+}
+
+static void vub300_deadwork_thread(struct work_struct *work)
+{				/* NOT irq */
+	struct vub300_mmc_host *vub300 =
+	    container_of(work, struct vub300_mmc_host, deadwork);
+	if (!vub300->interface) {
+		kref_put(&vub300->kref, vub300_delete);
+		return;
+	}
+	mutex_lock(&vub300->cmd_mutex);
+	if (vub300->cmd) {
+		/*
+		 * a command got in as the inactivity
+		 * timer expired - so we just let the
+		 * processing of the command show if
+		 * the device is dead
+		 */
+	} else if (vub300->card_present) {
+		check_vub300_port_status(vub300);
+	} else if (vub300->mmc && vub300->mmc->card
+		   && mmc_card_present(vub300->mmc->card)) {
+		/*
+		 * the MMC core must not have responded
+		 * to the previous indication - lets
+		 * hope that it eventually does so we
+		 * will just ignore this for now
+		 */
+	} else {
+		check_vub300_port_status(vub300);
+	}
+	mod_timer(&vub300->inactivity_timer, jiffies + HZ);
+	mutex_unlock(&vub300->cmd_mutex);
+	kref_put(&vub300->kref, vub300_delete);
+}
+
+static void vub300_inactivity_timer_expired(unsigned long data)
+{				/* softirq */
+	struct vub300_mmc_host *vub300 = (struct vub300_mmc_host *)data;
+	if (!vub300->interface) {
+		kref_put(&vub300->kref, vub300_delete);
+	} else if (vub300->cmd) {
+		mod_timer(&vub300->inactivity_timer, jiffies + HZ);
+	} else {
+		vub300_queue_dead_work(vub300);
+		mod_timer(&vub300->inactivity_timer, jiffies + HZ);
+	}
+}
+
+static int vub300_response_error(u8 ErrorCode)
+{
+	switch (ErrorCode) {
+	case SD_ERROR_PIO_TIMEOUT:
+	case SD_ERROR_1BIT_TIMEOUT:
+	case SD_ERROR_4BIT_TIMEOUT:
+		return -ETIMEDOUT;
+	case SD_ERROR_STAT_DATA:
+	case SD_ERROR_OVERRUN:
+	case SD_ERROR_STAT_CMD:
+	case SD_ERROR_STAT_CMD_TIMEOUT:
+	case SD_ERROR_SDCRDY_STUCK:
+	case SD_ERROR_UNHANDLED:
+	case SD_ERROR_1BIT_CRC_WRONG:
+	case SD_ERROR_4BIT_CRC_WRONG:
+	case SD_ERROR_1BIT_CRC_ERROR:
+	case SD_ERROR_4BIT_CRC_ERROR:
+	case SD_ERROR_NO_CMD_ENDBIT:
+	case SD_ERROR_NO_1BIT_DATEND:
+	case SD_ERROR_NO_4BIT_DATEND:
+	case SD_ERROR_1BIT_DATA_TIMEOUT:
+	case SD_ERROR_4BIT_DATA_TIMEOUT:
+	case SD_ERROR_1BIT_UNEXPECTED_TIMEOUT:
+	case SD_ERROR_4BIT_UNEXPECTED_TIMEOUT:
+		return -EILSEQ;
+	case 33:
+		return -EILSEQ;
+	case SD_ERROR_ILLEGAL_COMMAND:
+		return -EINVAL;
+	case SD_ERROR_NO_DEVICE:
+		return -ENOMEDIUM;
+	default:
+		return -ENODEV;
+	}
+}
+
+static void command_res_completed(struct urb *urb)
+{				/* urb completion handler - hardirq */
+	/*
+	 * cmd_mutex is held by vub300_cmndwork_thread
+	 */
+	struct vub300_mmc_host *vub300 = (struct vub300_mmc_host *)urb->context;
+	if (urb->status) {
+		/*
+		 * we have to let the initiator handle the error
+		 */
+	} else if (0 == vub300->command_res_urb->actual_length) {
+		/*
+		 * we have seen this happen once or twice and
+		 * we suspect a buggy USB host controller
+		 */
+	} else if (!vub300->data) {
+		/*
+		 * this means that the command (typically CMD52) suceeded
+		 */
+	} else if (0x02 != vub300->resp.common.HeaderType) {
+		/*
+		 * this is an error response from the VUB300 chip
+		 * and we let the initiator handle it
+		 */
+	} else if (vub300->urb) {
+		vub300->cmd->error =
+		    vub300_response_error(vub300->resp.error.ErrorCode);
+		usb_unlink_urb(vub300->urb);
+	} else {
+		vub300->cmd->error =
+		    vub300_response_error(vub300->resp.error.ErrorCode);
+		usb_sg_cancel(&vub300->sg_request);
+	}
+	complete(&vub300->command_complete);	/* got_response_in */
+}
+
+static void command_out_completed(struct urb *urb)
+{				/* urb completion handler - hardirq */
+	/*
+	 * cmd_mutex is held by vub300_cmndwork_thread
+	 */
+	struct vub300_mmc_host *vub300 = (struct vub300_mmc_host *)urb->context;
+	if (urb->status) {
+		complete(&vub300->command_complete);
+	} else {
+		int ret;
+		unsigned int pipe =
+		    usb_rcvbulkpipe(vub300->udev, vub300->cmnd_res_ep);
+		usb_fill_bulk_urb(vub300->command_res_urb, vub300->udev, pipe,
+				  &vub300->resp, sizeof(vub300->resp),
+				  command_res_completed, vub300);
+		vub300->command_res_urb->actual_length = 0;
+		ret = usb_submit_urb(vub300->command_res_urb, GFP_NOIO);
+		if (0 == ret) {
+			/*
+			 * the urb completion handler will call
+			 * our completion handler
+			 */
+		} else {
+			/*
+			 * and thus we only call it directly
+			 * when it will not be called
+			 */
+			complete(&vub300->command_complete);
+		}
+	}
+}
+
+/*
+ * the STUFF bits are masked out for the comparisons
+ */
+static void snoop_block_size_and_bus_width(struct vub300_mmc_host *vub300,
+					   u32 cmd_arg)
+{
+	if (0x80022200 == (0xFBFFFE00 & cmd_arg))
+		vub300->fbs[1] = (cmd_arg << 8) | (0x00FF & vub300->fbs[1]);
+	else if (0x80022000 == (0xFBFFFE00 & cmd_arg))
+		vub300->fbs[1] = (0xFF & cmd_arg) | (0xFF00 & vub300->fbs[1]);
+	else if (0x80042200 == (0xFBFFFE00 & cmd_arg))
+		vub300->fbs[2] = (cmd_arg << 8) | (0x00FF & vub300->fbs[2]);
+	else if (0x80042000 == (0xFBFFFE00 & cmd_arg))
+		vub300->fbs[2] = (0xFF & cmd_arg) | (0xFF00 & vub300->fbs[2]);
+	else if (0x80062200 == (0xFBFFFE00 & cmd_arg))
+		vub300->fbs[3] = (cmd_arg << 8) | (0x00FF & vub300->fbs[3]);
+	else if (0x80062000 == (0xFBFFFE00 & cmd_arg))
+		vub300->fbs[3] = (0xFF & cmd_arg) | (0xFF00 & vub300->fbs[3]);
+	else if (0x80082200 == (0xFBFFFE00 & cmd_arg))
+		vub300->fbs[4] = (cmd_arg << 8) | (0x00FF & vub300->fbs[4]);
+	else if (0x80082000 == (0xFBFFFE00 & cmd_arg))
+		vub300->fbs[4] = (0xFF & cmd_arg) | (0xFF00 & vub300->fbs[4]);
+	else if (0x800A2200 == (0xFBFFFE00 & cmd_arg))
+		vub300->fbs[5] = (cmd_arg << 8) | (0x00FF & vub300->fbs[5]);
+	else if (0x800A2000 == (0xFBFFFE00 & cmd_arg))
+		vub300->fbs[5] = (0xFF & cmd_arg) | (0xFF00 & vub300->fbs[5]);
+	else if (0x800C2200 == (0xFBFFFE00 & cmd_arg))
+		vub300->fbs[6] = (cmd_arg << 8) | (0x00FF & vub300->fbs[6]);
+	else if (0x800C2000 == (0xFBFFFE00 & cmd_arg))
+		vub300->fbs[6] = (0xFF & cmd_arg) | (0xFF00 & vub300->fbs[6]);
+	else if (0x800E2200 == (0xFBFFFE00 & cmd_arg))
+		vub300->fbs[7] = (cmd_arg << 8) | (0x00FF & vub300->fbs[7]);
+	else if (0x800E2000 == (0xFBFFFE00 & cmd_arg))
+		vub300->fbs[7] = (0xFF & cmd_arg) | (0xFF00 & vub300->fbs[7]);
+	else if (0x80000E00 == (0xFBFFFE03 & cmd_arg))
+		vub300->bus_width = 1;
+	else if (0x80000E02 == (0xFBFFFE03 & cmd_arg))
+		vub300->bus_width = 4;
+}
+
+static void send_command(struct vub300_mmc_host *vub300)
+{
+	/*
+	 * cmd_mutex is held by vub300_cmndwork_thread
+	 */
+	struct mmc_command *cmd = vub300->cmd;
+	struct mmc_data *data = vub300->data;
+	int retval;
+	u8 ResponseType;
+	if (vub300->app_spec) {
+		if (6 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+			if (0x00000000 == (0x00000003 & cmd->arg))
+				vub300->bus_width = 1;
+			else if (0x00000002 == (0x00000003 & cmd->arg))
+				vub300->bus_width = 4;
+			else
+				dev_err(&vub300->udev->dev,
+					"unexpected ACMD6 bus_width=%d\n",
+					0x00000003 & cmd->arg);
+		} else if (13 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (22 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (23 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (41 == cmd->opcode) {
+			ResponseType = SDRT_3;
+			vub300->resp_len = 6;
+		} else if (42 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (51 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (55 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else {
+			vub300->resp_len = 0;
+			cmd->error = -EINVAL;
+			complete(&vub300->command_complete);
+			return;
+		}
+		vub300->app_spec = 0;
+	} else {
+		if (0 == cmd->opcode) {
+			ResponseType = SDRT_NONE;
+			vub300->resp_len = 0;
+		} else if (1 == cmd->opcode) {
+			ResponseType = SDRT_3;
+			vub300->resp_len = 6;
+		} else if (2 == cmd->opcode) {
+			ResponseType = SDRT_2;
+			vub300->resp_len = 17;
+		} else if (3 == cmd->opcode) {
+			ResponseType = SDRT_6;
+			vub300->resp_len = 6;
+		} else if (4 == cmd->opcode) {
+			ResponseType = SDRT_NONE;
+			vub300->resp_len = 0;
+		} else if (5 == cmd->opcode) {
+			ResponseType = SDRT_4;
+			vub300->resp_len = 6;
+		} else if (6 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (7 == cmd->opcode) {
+			ResponseType = SDRT_1B;
+			vub300->resp_len = 6;
+		} else if (8 == cmd->opcode) {
+			ResponseType = SDRT_7;
+			vub300->resp_len = 6;
+		} else if (9 == cmd->opcode) {
+			ResponseType = SDRT_2;
+			vub300->resp_len = 17;
+		} else if (10 == cmd->opcode) {
+			ResponseType = SDRT_2;
+			vub300->resp_len = 17;
+		} else if (12 == cmd->opcode) {
+			ResponseType = SDRT_1B;
+			vub300->resp_len = 6;
+		} else if (13 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (15 == cmd->opcode) {
+			ResponseType = SDRT_NONE;
+			vub300->resp_len = 0;
+		} else if (16 == cmd->opcode) {
+			int i;
+			for (i = 0; i < ARRAY_SIZE(vub300->fbs); i++)
+				vub300->fbs[i] = 0xFFFF & cmd->arg;
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (17 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (18 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (24 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (25 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (27 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (28 == cmd->opcode) {
+			ResponseType = SDRT_1B;
+			vub300->resp_len = 6;
+		} else if (29 == cmd->opcode) {
+			ResponseType = SDRT_1B;
+			vub300->resp_len = 6;
+		} else if (30 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (32 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (33 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (38 == cmd->opcode) {
+			ResponseType = SDRT_1B;
+			vub300->resp_len = 6;
+		} else if (42 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else if (52 == cmd->opcode) {
+			ResponseType = SDRT_5;
+			vub300->resp_len = 6;
+			snoop_block_size_and_bus_width(vub300, cmd->arg);
+		} else if (53 == cmd->opcode) {
+			ResponseType = SDRT_5;
+			vub300->resp_len = 6;
+		} else if (55 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+			vub300->app_spec = 1;
+		} else if (56 == cmd->opcode) {
+			ResponseType = SDRT_1;
+			vub300->resp_len = 6;
+		} else {
+			vub300->resp_len = 0;
+			cmd->error = -EINVAL;
+			complete(&vub300->command_complete);
+			return;
+		}
+	}
+	/*
+	 * it is a shame that we can not use "sizeof(struct SD_Command_Header)"
+	 * this is because the packet _must_ be padded to 64 bytes
+	 */
+	vub300->cmnd.head.HeaderSize = 20;
+	vub300->cmnd.head.HeaderType = 0x00;
+	vub300->cmnd.head.PortNumber = 0; /* "0" means port 1 */
+	vub300->cmnd.head.Command_Type = 0x00; /* standard read command */
+	vub300->cmnd.head.ResponseType = ResponseType;
+	vub300->cmnd.head.CommandIndex = cmd->opcode;
+	vub300->cmnd.head.Arguments[0] = cmd->arg >> 24;
+	vub300->cmnd.head.Arguments[1] = cmd->arg >> 16;
+	vub300->cmnd.head.Arguments[2] = cmd->arg >> 8;
+	vub300->cmnd.head.Arguments[3] = cmd->arg >> 0;
+	if (52 == cmd->opcode) {
+		int fn = 0x7 & (cmd->arg >> 28);
+		vub300->cmnd.head.BlockCount[0] = 0;
+		vub300->cmnd.head.BlockCount[1] = 0;
+		vub300->cmnd.head.BlockSize[0] = (vub300->fbs[fn] >> 8) & 0xFF;
+		vub300->cmnd.head.BlockSize[1] = (vub300->fbs[fn] >> 0) & 0xFF;
+		vub300->cmnd.head.Command_Type = 0x00;
+		vub300->cmnd.head.TransferSize[0] = 0;
+		vub300->cmnd.head.TransferSize[1] = 0;
+		vub300->cmnd.head.TransferSize[2] = 0;
+		vub300->cmnd.head.TransferSize[3] = 0;
+	} else if (!data) {
+		vub300->cmnd.head.BlockCount[0] = 0;
+		vub300->cmnd.head.BlockCount[1] = 0;
+		vub300->cmnd.head.BlockSize[0] = (vub300->fbs[0] >> 8) & 0xFF;
+		vub300->cmnd.head.BlockSize[1] = (vub300->fbs[0] >> 0) & 0xFF;
+		vub300->cmnd.head.Command_Type = 0x00;
+		vub300->cmnd.head.TransferSize[0] = 0;
+		vub300->cmnd.head.TransferSize[1] = 0;
+		vub300->cmnd.head.TransferSize[2] = 0;
+		vub300->cmnd.head.TransferSize[3] = 0;
+	} else if (53 == cmd->opcode) {
+		int fn = 0x7 & (cmd->arg >> 28);
+		if (0x08 & vub300->cmnd.head.Arguments[0]) { /* BLOCK MODE */
+			vub300->cmnd.head.BlockCount[0] =
+			    (data->blocks >> 8) & 0xFF;
+			vub300->cmnd.head.BlockCount[1] =
+			    (data->blocks >> 0) & 0xFF;
+			vub300->cmnd.head.BlockSize[0] =
+			    (data->blksz >> 8) & 0xFF;
+			vub300->cmnd.head.BlockSize[1] =
+			    (data->blksz >> 0) & 0xFF;
+		} else {	/* BYTE MODE */
+			vub300->cmnd.head.BlockCount[0] = 0;
+			vub300->cmnd.head.BlockCount[1] = 0;
+			vub300->cmnd.head.BlockSize[0] =
+			    (vub300->datasize >> 8) & 0xFF;
+			vub300->cmnd.head.BlockSize[1] =
+			    (vub300->datasize >> 0) & 0xFF;
+		}
+		vub300->cmnd.head.Command_Type =
+		    (MMC_DATA_READ & data->flags) ? 0x00 : 0x80;
+		vub300->cmnd.head.TransferSize[0] =
+		    (vub300->datasize >> 24) & 0xFF;
+		vub300->cmnd.head.TransferSize[1] =
+		    (vub300->datasize >> 16) & 0xFF;
+		vub300->cmnd.head.TransferSize[2] =
+		    (vub300->datasize >> 8) & 0xFF;
+		vub300->cmnd.head.TransferSize[3] =
+		    (vub300->datasize >> 0) & 0xFF;
+		if (vub300->datasize < vub300->fbs[fn]) {
+			vub300->cmnd.head.BlockCount[0] = 0;
+			vub300->cmnd.head.BlockCount[1] = 0;
+		}
+	} else {
+		vub300->cmnd.head.BlockCount[0] = (data->blocks >> 8) & 0xFF;
+		vub300->cmnd.head.BlockCount[1] = (data->blocks >> 0) & 0xFF;
+		vub300->cmnd.head.BlockSize[0] = (data->blksz >> 8) & 0xFF;
+		vub300->cmnd.head.BlockSize[1] = (data->blksz >> 0) & 0xFF;
+		vub300->cmnd.head.Command_Type =
+		    (MMC_DATA_READ & data->flags) ? 0x00 : 0x80;
+		vub300->cmnd.head.TransferSize[0] =
+		    (vub300->datasize >> 24) & 0xFF;
+		vub300->cmnd.head.TransferSize[1] =
+		    (vub300->datasize >> 16) & 0xFF;
+		vub300->cmnd.head.TransferSize[2] =
+		    (vub300->datasize >> 8) & 0xFF;
+		vub300->cmnd.head.TransferSize[3] =
+		    (vub300->datasize >> 0) & 0xFF;
+		if (vub300->datasize < vub300->fbs[0]) {
+			vub300->cmnd.head.BlockCount[0] = 0;
+			vub300->cmnd.head.BlockCount[1] = 0;
+		}
+	}
+	if (vub300->cmnd.head.BlockSize[0] || vub300->cmnd.head.BlockSize[1]) {
+		u16 BlockSize = vub300->cmnd.head.BlockSize[1]
+		    | (vub300->cmnd.head.BlockSize[0] << 8);
+		u16 BlockBoundary =
+		    FIRMWARE_BLOCK_BOUNDARY -
+		    (FIRMWARE_BLOCK_BOUNDARY % BlockSize);
+		vub300->cmnd.head.BlockBoundary[0] =
+		    (BlockBoundary >> 8) & 0xFF;
+		vub300->cmnd.head.BlockBoundary[1] =
+		    (BlockBoundary >> 0) & 0xFF;
+	} else {
+		vub300->cmnd.head.BlockBoundary[0] = 0;
+		vub300->cmnd.head.BlockBoundary[1] = 0;
+	}
+	usb_fill_bulk_urb(vub300->command_out_urb, vub300->udev,
+			  usb_sndbulkpipe(vub300->udev, vub300->cmnd_out_ep),
+			  &vub300->cmnd, sizeof(vub300->cmnd),
+			  command_out_completed, vub300);
+	retval = usb_submit_urb(vub300->command_out_urb, GFP_ATOMIC);
+	if (0 > retval) {
+		cmd->error = retval;
+		complete(&vub300->command_complete);
+		return;
+	} else {
+		return;
+	}
+}
+
+/*
+ * timer callback runs in atomic mode
+ *       so it cannot call usb_kill_urb()
+ */
+static void vub300_sg_timed_out(unsigned long data)
+{
+	struct vub300_mmc_host *vub300 = (struct vub300_mmc_host *)data;
+	vub300->usb_timed_out = 1;
+	usb_sg_cancel(&vub300->sg_request);
+	usb_unlink_urb(vub300->command_out_urb);
+	usb_unlink_urb(vub300->command_res_urb);
+}
+
+static u16 roundup_to_multiple_of_64(u16 number)
+{
+	return 0xFFC0 & (0x3F + number);
+}
+
+/*
+ * this is a separate function to solve the 80 column width restriction
+ */
+static void __download_offload_pseudocode(struct vub300_mmc_host *vub300,
+					  const struct firmware *fw)
+{
+	u8 register_count = 0;
+	u16 transfer_size = 0;
+	u16 interrupt_size = 0;
+	const u8 *data = fw->data;
+	int size = fw->size;
+	u8 c;
+	dev_info(&vub300->udev->dev, "using %s for SDIO offload processing\n",
+		 vub300->vub_name);
+	do {
+		c = *data++;
+	} while (size-- && c); /* skip comment */
+	dev_info(&vub300->udev->dev, "using offload firmware %s %s\n", fw->data,
+		 vub300->vub_name);
+	if (size < 4) {
+		dev_err(&vub300->udev->dev,
+			"corrupt offload pseudocode in firmware %s\n",
+			vub300->vub_name);
+		strncpy(vub300->vub_name, "corrupt offload pseudocode",
+			sizeof(vub300->vub_name));
+		return;
+	}
+	interrupt_size += *data++;
+	size -= 1;
+	interrupt_size <<= 8;
+	interrupt_size += *data++;
+	size -= 1;
+	if (interrupt_size < size) {
+		u16 xfer_length = roundup_to_multiple_of_64(interrupt_size);
+		u8 *xfer_buffer = kmalloc(xfer_length, GFP_KERNEL);
+		if (xfer_buffer) {
+			int retval;
+			memcpy(xfer_buffer, data, interrupt_size);
+			memset(xfer_buffer + interrupt_size, 0,
+			       xfer_length - interrupt_size);
+			size -= interrupt_size;
+			data += interrupt_size;
+			retval =
+			    usb_control_msg(vub300->udev,
+					    usb_sndctrlpipe(vub300->udev, 0),
+					    SET_INTERRUPT_PSEUDOCODE,
+					    USB_DIR_OUT | USB_TYPE_VENDOR |
+					    USB_RECIP_DEVICE, 0x0000, 0x0000,
+					    xfer_buffer, xfer_length, HZ);
+			kfree(xfer_buffer);
+			if (retval < 0) {
+				strncpy(vub300->vub_name,
+					"SDIO pseudocode download failed",
+					sizeof(vub300->vub_name));
+				return;
+			}
+		} else {
+			dev_err(&vub300->udev->dev,
+				"not enough memory for xfer buffer to send"
+				" INTERRUPT_PSEUDOCODE for %s %s\n", fw->data,
+				vub300->vub_name);
+			strncpy(vub300->vub_name,
+				"SDIO interrupt pseudocode download failed",
+				sizeof(vub300->vub_name));
+			return;
+		}
+	} else {
+		dev_err(&vub300->udev->dev,
+			"corrupt interrupt pseudocode in firmware %s %s\n",
+			fw->data, vub300->vub_name);
+		strncpy(vub300->vub_name, "corrupt interrupt pseudocode",
+			sizeof(vub300->vub_name));
+		return;
+	}
+	transfer_size += *data++;
+	size -= 1;
+	transfer_size <<= 8;
+	transfer_size += *data++;
+	size -= 1;
+	if (transfer_size < size) {
+		u16 xfer_length = roundup_to_multiple_of_64(transfer_size);
+		u8 *xfer_buffer = kmalloc(xfer_length, GFP_KERNEL);
+		if (xfer_buffer) {
+			int retval;
+			memcpy(xfer_buffer, data, transfer_size);
+			memset(xfer_buffer + transfer_size, 0,
+			       xfer_length - transfer_size);
+			size -= transfer_size;
+			data += transfer_size;
+			retval =
+			    usb_control_msg(vub300->udev,
+					    usb_sndctrlpipe(vub300->udev, 0),
+					    SET_TRANSFER_PSEUDOCODE,
+					    USB_DIR_OUT | USB_TYPE_VENDOR |
+					    USB_RECIP_DEVICE, 0x0000, 0x0000,
+					    xfer_buffer, xfer_length, HZ);
+			kfree(xfer_buffer);
+			if (retval < 0) {
+				strncpy(vub300->vub_name,
+					"SDIO pseudocode download failed",
+					sizeof(vub300->vub_name));
+				return;
+			}
+		} else {
+			dev_err(&vub300->udev->dev,
+				"not enough memory for xfer buffer to send"
+				" TRANSFER_PSEUDOCODE for %s %s\n", fw->data,
+				vub300->vub_name);
+			strncpy(vub300->vub_name,
+				"SDIO transfer pseudocode download failed",
+				sizeof(vub300->vub_name));
+			return;
+		}
+	} else {
+		dev_err(&vub300->udev->dev,
+			"corrupt transfer pseudocode in firmware %s %s\n",
+			fw->data, vub300->vub_name);
+		strncpy(vub300->vub_name, "corrupt transfer pseudocode",
+			sizeof(vub300->vub_name));
+		return;
+	}
+	register_count += *data++;
+	size -= 1;
+	if (register_count * 4 == size) {
+		int I = vub300->dynamic_register_count = register_count;
+		int i = 0;
+		while (I--) {
+			unsigned int func_num = 0;
+			vub300->sdio_register[i].func_num = *data++;
+			size -= 1;
+			func_num += *data++;
+			size -= 1;
+			func_num <<= 8;
+			func_num += *data++;
+			size -= 1;
+			func_num <<= 8;
+			func_num += *data++;
+			size -= 1;
+			vub300->sdio_register[i].sdio_reg = func_num;
+			vub300->sdio_register[i].activate = 1;
+			vub300->sdio_register[i].prepared = 0;
+			i += 1;
+		}
+		dev_info(&vub300->udev->dev,
+			 "initialized %d dynamic pseudocode registers\n",
+			 vub300->dynamic_register_count);
+		return;
+	} else {
+		dev_err(&vub300->udev->dev,
+			"corrupt dynamic registers in firmware %s\n",
+			vub300->vub_name);
+		strncpy(vub300->vub_name, "corrupt dynamic registers",
+			sizeof(vub300->vub_name));
+		return;
+	}
+}
+
+/*
+ * if the binary containing the EMPTY PseudoCode can not be found
+ * vub300->vub_name is set anyway in order to prevent an automatic retry
+ */
+static void download_offload_pseudocode(struct vub300_mmc_host *vub300)
+{
+	struct mmc_card *card = vub300->mmc->card;
+	int sdio_funcs = card->sdio_funcs;
+	const struct firmware *fw = NULL;
+	int l = snprintf(vub300->vub_name, sizeof(vub300->vub_name),
+			 "vub_%04X%04X", card->cis.vendor, card->cis.device);
+	int N = sdio_funcs;
+	int n = 0;
+	int retval;
+	while (N--) {
+		struct sdio_func *sf = card->sdio_func[n++];
+		l += snprintf(vub300->vub_name + l,
+			      sizeof(vub300->vub_name) - l, "_%04X%04X",
+			      sf->vendor, sf->device);
+	};
+	snprintf(vub300->vub_name + l, sizeof(vub300->vub_name) - l, ".bin");
+	dev_info(&vub300->udev->dev, "requesting offload firmware %s\n",
+		 vub300->vub_name);
+	retval = request_firmware(&fw, vub300->vub_name, &card->dev);
+	if (0 > retval) {
+		strncpy(vub300->vub_name, "vub_default.bin",
+			sizeof(vub300->vub_name));
+		retval = request_firmware(&fw, vub300->vub_name, &card->dev);
+		if (0 > retval) {
+			strncpy(vub300->vub_name,
+				"no SDIO offload firmware found",
+				sizeof(vub300->vub_name));
+		} else {
+			__download_offload_pseudocode(vub300, fw);
+			release_firmware(fw);
+		}
+	} else {
+		__download_offload_pseudocode(vub300, fw);
+		release_firmware(fw);
+	}
+}
+
+static void vub300_usb_bulk_msg_completion(struct urb *urb)
+{				/* urb completion handler - hardirq */
+	complete((struct completion *)urb->context);
+}
+
+static int vub300_usb_bulk_msg(struct vub300_mmc_host *vub300,
+			       unsigned int pipe, void *data, int len,
+			       int *actual_length, int timeout_msecs)
+{
+	/*
+	 * cmd_mutex is held by vub300_cmndwork_thread
+	 */
+	struct usb_device *usb_dev = vub300->udev;
+	struct completion done;
+	int retval;
+	vub300->urb = usb_alloc_urb(0, GFP_KERNEL);
+	if (!vub300->urb)
+		return -ENOMEM;
+	usb_fill_bulk_urb(vub300->urb, usb_dev, pipe, data, len,
+			  vub300_usb_bulk_msg_completion, NULL);
+	init_completion(&done);
+	vub300->urb->context = &done;
+	vub300->urb->actual_length = 0;
+	retval = usb_submit_urb(vub300->urb, GFP_NOIO);
+	if (unlikely(retval))
+		goto out;
+	if (!wait_for_completion_timeout
+	    (&done, msecs_to_jiffies(timeout_msecs))) {
+		retval = -ETIMEDOUT;
+		usb_kill_urb(vub300->urb);
+	} else {
+		retval = vub300->urb->status;
+	}
+out:
+	*actual_length = vub300->urb->actual_length;
+	usb_free_urb(vub300->urb);
+	vub300->urb = NULL;
+	return retval;
+}
+
+static int __command_read_data(struct vub300_mmc_host *vub300,
+			       struct mmc_command *cmd, struct mmc_data *data)
+{
+	/*
+	 * cmd_mutex is held by vub300_cmndwork_thread
+	 */
+	int linear_length = vub300->datasize;
+	int padded_length = vub300->large_usb_packets ?
+	    ((511 + linear_length) >> 9) << 9 :
+	    ((63 + linear_length) >> 6) << 6;
+	if ((padded_length == linear_length) || !pad_input_to_usb_pkt) {
+		int result;
+		unsigned pipe;
+		pipe = usb_rcvbulkpipe(vub300->udev, vub300->data_inp_ep);
+		result = usb_sg_init(&vub300->sg_request, vub300->udev,
+				     pipe, 0, data->sg,
+				     data->sg_len, 0, GFP_NOIO);
+		if (0 > result) {
+			usb_unlink_urb(vub300->command_out_urb);
+			usb_unlink_urb(vub300->command_res_urb);
+			cmd->error = result;
+			data->bytes_xfered = 0;
+			return 0;
+		} else {
+			vub300->sg_transfer_timer.expires =
+			    jiffies + msecs_to_jiffies(2000 +
+						       (linear_length / 16384));
+			add_timer(&vub300->sg_transfer_timer);
+			usb_sg_wait(&vub300->sg_request);
+			del_timer(&vub300->sg_transfer_timer);
+			if (0 > vub300->sg_request.status) {
+				cmd->error = vub300->sg_request.status;
+				data->bytes_xfered = 0;
+				return 0;
+			} else {
+				data->bytes_xfered = vub300->datasize;
+				return linear_length;
+			}
+		}
+	} else {
+		u8 *buf = kmalloc(padded_length, GFP_KERNEL);
+		if (buf) {
+			int result;
+			unsigned pipe =
+			    usb_rcvbulkpipe(vub300->udev, vub300->data_inp_ep);
+			int actual_length = 0;
+			result =
+			    vub300_usb_bulk_msg(vub300, pipe, buf,
+						padded_length, &actual_length,
+						2000 + (padded_length / 16384));
+			if (0 > result) {
+				cmd->error = result;
+				data->bytes_xfered = 0;
+				kfree(buf);
+				return 0;
+			} else if (actual_length < linear_length) {
+				cmd->error = -EREMOTEIO;
+				data->bytes_xfered = 0;
+				kfree(buf);
+				return 0;
+			} else {
+				sg_copy_from_buffer(data->sg, data->sg_len, buf,
+						    linear_length);
+				kfree(buf);
+				data->bytes_xfered = vub300->datasize;
+				return linear_length;
+			}
+		} else {
+			cmd->error = -ENOMEM;
+			data->bytes_xfered = 0;
+			return 0;
+		}
+	}
+}
+
+static int __command_write_data(struct vub300_mmc_host *vub300,
+				struct mmc_command *cmd, struct mmc_data *data)
+{
+	/*
+	 * cmd_mutex is held by vub300_cmndwork_thread
+	 */
+	unsigned pipe = usb_sndbulkpipe(vub300->udev, vub300->data_out_ep);
+	int linear_length = vub300->datasize;
+	int modulo_64_length = linear_length & 0x003F;
+	int modulo_512_length = linear_length & 0x01FF;
+	if (64 > linear_length) {
+		int result;
+		int actual_length;
+		sg_copy_to_buffer(data->sg, data->sg_len,
+				  vub300->padded_buffer,
+				  sizeof(vub300->padded_buffer));
+		memset(vub300->padded_buffer + linear_length, 0,
+		       sizeof(vub300->padded_buffer) - linear_length);
+		result =
+		    vub300_usb_bulk_msg(vub300, pipe, vub300->padded_buffer,
+					sizeof(vub300->padded_buffer),
+					&actual_length,
+					2000 +
+					(sizeof(vub300->padded_buffer) /
+					 16384));
+		if (0 > result) {
+			cmd->error = result;
+			data->bytes_xfered = 0;
+		} else {
+			data->bytes_xfered = vub300->datasize;
+		}
+	} else if ((!vub300->large_usb_packets && (0 < modulo_64_length))
+		   || (vub300->large_usb_packets && (64 > modulo_512_length))
+	    ) {			/* don't you just love these work-rounds */
+		int padded_length = ((63 + linear_length) >> 6) << 6;
+		u8 *buf = kmalloc(padded_length, GFP_KERNEL);
+		if (buf) {
+			int result;
+			int actual_length;
+			sg_copy_to_buffer(data->sg, data->sg_len, buf,
+					  padded_length);
+			memset(buf + linear_length, 0,
+			       padded_length - linear_length);
+			result =
+			    vub300_usb_bulk_msg(vub300, pipe, buf,
+						padded_length, &actual_length,
+						2000 + padded_length / 16384);
+			kfree(buf);
+			if (0 > result) {
+				cmd->error = result;
+				data->bytes_xfered = 0;
+			} else {
+				data->bytes_xfered = vub300->datasize;
+			}
+		} else {
+			cmd->error = -ENOMEM;
+			data->bytes_xfered = 0;
+		}
+	} else {		/* no data padding required */
+		int result;
+		unsigned char buf[64 * 4];
+		sg_copy_to_buffer(data->sg, data->sg_len, buf, sizeof(buf));
+		result = usb_sg_init(&vub300->sg_request, vub300->udev,
+				     pipe, 0, data->sg,
+				     data->sg_len, 0, GFP_NOIO);
+		if (0 > result) {
+			usb_unlink_urb(vub300->command_out_urb);
+			usb_unlink_urb(vub300->command_res_urb);
+			cmd->error = result;
+			data->bytes_xfered = 0;
+		} else {
+			vub300->sg_transfer_timer.expires =
+			    jiffies + msecs_to_jiffies(2000 +
+						       linear_length / 16384);
+			add_timer(&vub300->sg_transfer_timer);
+			usb_sg_wait(&vub300->sg_request);
+			if (cmd->error) {
+				data->bytes_xfered = 0;
+			} else {
+				del_timer(&vub300->sg_transfer_timer);
+				if (0 > vub300->sg_request.status) {
+					cmd->error = vub300->sg_request.status;
+					data->bytes_xfered = 0;
+				} else {
+					data->bytes_xfered = vub300->datasize;
+				}
+			}
+		}
+	}
+	return linear_length;
+}
+
+static void __vub300_command_response(struct vub300_mmc_host *vub300,
+				      struct mmc_command *cmd,
+				      struct mmc_data *data, int data_length)
+{
+	/*
+	 * cmd_mutex is held by vub300_cmndwork_thread
+	 */
+	long respretval;
+	int msec_timeout = 1000 + data_length / 4;
+	respretval =
+	    wait_for_completion_timeout(&vub300->command_complete,
+					msecs_to_jiffies(msec_timeout));
+	if (0 == respretval) { /* TIMED OUT */
+		/*
+		 * we don't know which of "out" and "res" if any failed
+		 */
+		int result;
+		vub300->usb_timed_out = 1;
+		usb_kill_urb(vub300->command_out_urb);
+		usb_kill_urb(vub300->command_res_urb);
+		cmd->error = -ETIMEDOUT;
+		result =
+		    usb_lock_device_for_reset(vub300->udev, vub300->interface);
+		if (result == 0) {
+			result = usb_reset_device(vub300->udev);
+			usb_unlock_device(vub300->udev);
+		}
+	} else if (0 > respretval) {
+		/*
+		 * we don't know which of "out" and "res" if any failed
+		 */
+		usb_kill_urb(vub300->command_out_urb);
+		usb_kill_urb(vub300->command_res_urb);
+		cmd->error = respretval;
+	} else if (cmd->error) {
+		/*
+		 * the error occured sending the command
+		 * or recieving the response
+		 */
+	} else if (vub300->command_out_urb->status) {
+		vub300->usb_transport_fail = vub300->command_out_urb->status;
+		cmd->error = -EPROTO == vub300->command_out_urb->status ?
+		    -ESHUTDOWN : vub300->command_out_urb->status;
+	} else if (vub300->command_res_urb->status) {
+		vub300->usb_transport_fail = vub300->command_res_urb->status;
+		cmd->error = -EPROTO == vub300->command_res_urb->status ?
+		    -ESHUTDOWN : vub300->command_res_urb->status;
+	} else if (0x00 == vub300->resp.common.HeaderType) {
+		/*
+		 * the command completed successfully
+		 * and there was no piggybacked data
+		 */
+	} else if (RESPONSE_ERROR == vub300->resp.common.HeaderType) {
+		cmd->error =
+		    vub300_response_error(vub300->resp.error.ErrorCode);
+		if (vub300->data)
+			usb_sg_cancel(&vub300->sg_request);
+	} else if (RESPONSE_PIGGYBACKED == vub300->resp.common.HeaderType) {
+		int offloaded_data_length =
+		    vub300->resp.common.HeaderSize -
+		    sizeof(struct SD_Register_Header);
+		int register_count = offloaded_data_length >> 3;
+		int ri = 0;
+		while (register_count--) {
+			add_offloaded_reg(vub300, &vub300->resp.pig.reg[ri]);
+			ri += 1;
+		}
+		vub300->resp.common.HeaderSize =
+		    sizeof(struct SD_Register_Header);
+		vub300->resp.common.HeaderType = 0x00;
+		cmd->error = 0;
+	} else if (RESPONSE_PIG_DISABLED == vub300->resp.common.HeaderType) {
+		int offloaded_data_length =
+		    vub300->resp.common.HeaderSize -
+		    sizeof(struct SD_Register_Header);
+		int register_count = offloaded_data_length >> 3;
+		int ri = 0;
+		while (register_count--) {
+			add_offloaded_reg(vub300, &vub300->resp.pig.reg[ri]);
+			ri += 1;
+		}
+		mutex_lock(&vub300->irq_mutex);
+		if (vub300->irqs_queued) {
+			vub300->irqs_queued += 1;
+		} else if (vub300->irq_enabled) {
+			vub300->irqs_queued += 1;
+			vub300_queue_poll_work(vub300, 0);
+		} else {
+			vub300->irqs_queued += 1;
+		}
+		vub300->irq_disabled = 1;
+		mutex_unlock(&vub300->irq_mutex);
+		vub300->resp.common.HeaderSize =
+		    sizeof(struct SD_Register_Header);
+		vub300->resp.common.HeaderType = 0x00;
+		cmd->error = 0;
+	} else if (RESPONSE_PIG_ENABLED == vub300->resp.common.HeaderType) {
+		int offloaded_data_length =
+		    vub300->resp.common.HeaderSize -
+		    sizeof(struct SD_Register_Header);
+		int register_count = offloaded_data_length >> 3;
+		int ri = 0;
+		while (register_count--) {
+			add_offloaded_reg(vub300, &vub300->resp.pig.reg[ri]);
+			ri += 1;
+		}
+		mutex_lock(&vub300->irq_mutex);
+		if (vub300->irqs_queued) {
+			vub300->irqs_queued += 1;
+		} else if (vub300->irq_enabled) {
+			vub300->irqs_queued += 1;
+			vub300_queue_poll_work(vub300, 0);
+		} else {
+			vub300->irqs_queued += 1;
+		}
+		vub300->irq_disabled = 0;
+		mutex_unlock(&vub300->irq_mutex);
+		vub300->resp.common.HeaderSize =
+		    sizeof(struct SD_Register_Header);
+		vub300->resp.common.HeaderType = 0x00;
+		cmd->error = 0;
+	} else {
+		cmd->error = -EINVAL;
+	}
+}
+
+static void construct_request_response(struct vub300_mmc_host *vub300,
+				       struct mmc_command *cmd)
+{
+	int resp_len = vub300->resp_len;
+	int less_cmd = (17 == resp_len) ? resp_len : resp_len - 1;
+	int bytes = 3 & less_cmd;
+	int words = less_cmd >> 2;
+	u8 *r = vub300->resp.response.CommandResponse;
+	if (3 == bytes) {
+		cmd->resp[words] = (r[1 + (words << 2)] << 24)
+		    | (r[2 + (words << 2)] << 16)
+		    | (r[3 + (words << 2)] << 8);
+	} else if (2 == bytes) {
+		cmd->resp[words] = (r[1 + (words << 2)] << 24)
+		    | (r[2 + (words << 2)] << 16);
+	} else if (1 == bytes) {
+		cmd->resp[words] = (r[1 + (words << 2)] << 24);
+	}
+	while (words-- > 0) {
+		cmd->resp[words] = (r[1 + (words << 2)] << 24)
+		    | (r[2 + (words << 2)] << 16)
+		    | (r[3 + (words << 2)] << 8)
+		    | (r[4 + (words << 2)] << 0);
+	}
+	if ((53 == cmd->opcode) && (0x000000FF & cmd->resp[0]))
+		cmd->resp[0] &= 0xFFFFFF00;
+}
+
+/*
+ * this thread runs only when there
+ * is an upper level command req outstanding
+ */
+static void vub300_cmndwork_thread(struct work_struct *work)
+{
+	struct vub300_mmc_host *vub300 =
+	    container_of(work, struct vub300_mmc_host, cmndwork);
+	if (!vub300->interface) {
+		kref_put(&vub300->kref, vub300_delete);
+		return;
+	} else {
+		struct mmc_request *req = vub300->req;
+		struct mmc_command *cmd = vub300->cmd;
+		struct mmc_data *data = vub300->data;
+		int data_length;
+		mutex_lock(&vub300->cmd_mutex);
+		init_completion(&vub300->command_complete);
+		if (likely(vub300->vub_name[0])
+		    || !vub300->mmc->card
+		    || !mmc_card_present(vub300->mmc->card)) {
+			/*
+			 * the name of the EMPTY Pseudo firmware file
+			 * is used as a flag to indicate that the file
+			 * has been already downloaded to the VUB300 chip
+			 */
+		} else if (0 == vub300->mmc->card->sdio_funcs) {
+			strncpy(vub300->vub_name, "SD memory device",
+				sizeof(vub300->vub_name));
+		} else {
+			download_offload_pseudocode(vub300);
+		}
+		send_command(vub300);
+		if (!data)
+			data_length = 0;
+		else if (MMC_DATA_READ & data->flags)
+			data_length = __command_read_data(vub300, cmd, data);
+		else
+			data_length = __command_write_data(vub300, cmd, data);
+		__vub300_command_response(vub300, cmd, data, data_length);
+		vub300->req = NULL;
+		vub300->cmd = NULL;
+		vub300->data = NULL;
+		if (cmd->error) {
+			if (-ENOMEDIUM == cmd->error)
+				check_vub300_port_status(vub300);
+			mutex_unlock(&vub300->cmd_mutex);
+			mmc_request_done(vub300->mmc, req);
+			kref_put(&vub300->kref, vub300_delete);
+			return;
+		} else {
+			construct_request_response(vub300, cmd);
+			vub300->resp_len = 0;
+			mutex_unlock(&vub300->cmd_mutex);
+			kref_put(&vub300->kref, vub300_delete);
+			mmc_request_done(vub300->mmc, req);
+			return;
+		}
+	}
+}
+
+static int examine_cyclic_buffer(struct vub300_mmc_host *vub300,
+				 struct mmc_command *cmd, u8 Function)
+{
+	/*
+	 * cmd_mutex is held by vub300_mmc_request
+	 */
+	u8 cmd0 = 0xFF & (cmd->arg >> 24);
+	u8 cmd1 = 0xFF & (cmd->arg >> 16);
+	u8 cmd2 = 0xFF & (cmd->arg >> 8);
+	u8 cmd3 = 0xFF & (cmd->arg >> 0);
+	int first = MAXREGMASK & vub300->fn[Function].offload_point;
+	struct Offload_Registers_Access *rf = &vub300->fn[Function].reg[first];
+	if (cmd0 == rf->Command_Byte[0]
+	    && cmd1 == rf->Command_Byte[1]
+	    && cmd2 == rf->Command_Byte[2]
+	    && cmd3 == rf->Command_Byte[3]
+	    ) {
+		u8 checksum = 0x00;
+		cmd->resp[1] = checksum << 24;
+		cmd->resp[0] = (rf->Respond_Byte[0] << 24)
+		    | (rf->Respond_Byte[1] << 16)
+		    | (rf->Respond_Byte[2] << 8)
+		    | (rf->Respond_Byte[3] << 0);
+		vub300->fn[Function].offload_point += 1;
+		vub300->fn[Function].offload_count -= 1;
+		vub300->total_offload_count -= 1;
+		return 1;
+	} else {
+		int delta = 1;	/* because it does not match the first one */
+		u8 register_count = vub300->fn[Function].offload_count - 1;
+		u32 register_point = vub300->fn[Function].offload_point + 1;
+		while (0 < register_count) {
+			int point = MAXREGMASK & register_point;
+			struct Offload_Registers_Access *r =
+			    &vub300->fn[Function].reg[point];
+			if (cmd0 == r->Command_Byte[0]
+			    && cmd1 == r->Command_Byte[1]
+			    && cmd2 == r->Command_Byte[2]
+			    && cmd3 == r->Command_Byte[3]
+			    ) {
+				u8 checksum = 0x00;
+				cmd->resp[1] = checksum << 24;
+				cmd->resp[0] = (r->Respond_Byte[0] << 24)
+				    | (r->Respond_Byte[1] << 16)
+				    | (r->Respond_Byte[2] << 8)
+				    | (r->Respond_Byte[3] << 0);
+				vub300->fn[Function].offload_point += delta;
+				vub300->fn[Function].offload_count -= delta;
+				vub300->total_offload_count -= delta;
+				return 1;
+			} else {
+				register_point += 1;
+				register_count -= 1;
+				delta += 1;
+				continue;
+			}
+		}
+		return 0;
+	}
+}
+
+static int satisfy_request_from_offloaded_data(struct vub300_mmc_host *vub300,
+					       struct mmc_command *cmd)
+{
+	/*
+	 * cmd_mutex is held by vub300_mmc_request
+	 */
+	u8 regs = vub300->dynamic_register_count;
+	u8 i = 0;
+	u8 Function = FUN(cmd);
+	u32 Register = REG(cmd);
+	while (0 < regs--) {
+		if ((vub300->sdio_register[i].func_num == Function)
+		    && (vub300->sdio_register[i].sdio_reg == Register)
+		    ) {
+			if (!vub300->sdio_register[i].prepared) {
+				return 0;
+			} else if (0x80000000 == (0x80000000 & cmd->arg)) {
+				/*
+				 * a write to a dynamic register
+				 * nullifies our offloaded value
+				 */
+				vub300->sdio_register[i].prepared = 0;
+				return 0;
+			} else {
+				u8 checksum = 0x00;
+				u8 rsp0 = 0x00;
+				u8 rsp1 = 0x00;
+				u8 rsp2 = vub300->sdio_register[i].response;
+				u8 rsp3 = vub300->sdio_register[i].regvalue;
+				vub300->sdio_register[i].prepared = 0;
+				cmd->resp[1] = checksum << 24;
+				cmd->resp[0] = (rsp0 << 24)
+				    | (rsp1 << 16)
+				    | (rsp2 << 8)
+				    | (rsp3 << 0);
+				return 1;
+			}
+		} else {
+			i += 1;
+			continue;
+		}
+	};
+	if (0 == vub300->total_offload_count)
+		return 0;
+	else if (0 == vub300->fn[Function].offload_count)
+		return 0;
+	else
+		return examine_cyclic_buffer(vub300, cmd, Function);
+}
+
+static void vub300_mmc_request(struct mmc_host *mmc, struct mmc_request *req)
+{				/* NOT irq */
+	struct mmc_command *cmd = req->cmd;
+	struct vub300_mmc_host *vub300 = mmc_priv(mmc);
+	if (!vub300->interface) {
+		cmd->error = -ESHUTDOWN;
+		mmc_request_done(mmc, req);
+		return;
+	} else {
+		struct mmc_data *data = req->data;
+		if (!vub300->card_powered) {
+			cmd->error = -ENOMEDIUM;
+			mmc_request_done(mmc, req);
+			return;
+		}
+		if (!vub300->card_present) {
+			cmd->error = -ENOMEDIUM;
+			mmc_request_done(mmc, req);
+			return;
+		}
+		if (vub300->usb_transport_fail) {
+			cmd->error = vub300->usb_transport_fail;
+			mmc_request_done(mmc, req);
+			return;
+		}
+		if (!vub300->interface) {
+			cmd->error = -ENODEV;
+			mmc_request_done(mmc, req);
+			return;
+		}
+		kref_get(&vub300->kref);
+		mutex_lock(&vub300->cmd_mutex);
+		mod_timer(&vub300->inactivity_timer, jiffies + HZ);
+		/*
+		 * for performance we have to return immediately
+		 * if the requested data has been offloaded
+		 */
+		if ((52 == cmd->opcode)
+		    && satisfy_request_from_offloaded_data(vub300, cmd)) {
+			cmd->error = 0;
+			mutex_unlock(&vub300->cmd_mutex);
+			kref_put(&vub300->kref, vub300_delete);
+			mmc_request_done(mmc, req);
+			return;
+		} else {
+			vub300->cmd = cmd;
+			vub300->req = req;
+			vub300->data = data;
+			if (data)
+				vub300->datasize = data->blksz * data->blocks;
+			else
+				vub300->datasize = 0;
+			vub300_queue_cmnd_work(vub300);
+			mutex_unlock(&vub300->cmd_mutex);
+			kref_put(&vub300->kref, vub300_delete);
+			/*
+			 * the kernel lock diagnostics complain
+			 * if the cmd_mutex * is "passed on"
+			 * to the cmndwork thread,
+			 * so we must release it now
+			 * and re-acquire it in the cmndwork thread
+			 */
+		}
+	}
+}
+
+static void __set_clock_speed(struct vub300_mmc_host *vub300, u8 buf[8],
+			      struct mmc_ios *ios)
+{
+	int buf_array_size = 8; /* ARRAY_SIZE(buf) does not work !!! */
+	int retval;
+	u32 kHzClock;
+	if (ios->clock >= 48000000)
+		kHzClock = 48000;
+	else if (ios->clock >= 24000000)
+		kHzClock = 24000;
+	else if (ios->clock >= 20000000)
+		kHzClock = 20000;
+	else if (ios->clock >= 15000000)
+		kHzClock = 15000;
+	else if (ios->clock >= 200000)
+		kHzClock = 200;
+	else
+		kHzClock = 0;
+	{
+		int i;
+		u64 c = kHzClock;
+		for (i = 0; i < buf_array_size; i++) {
+			buf[i] = c;
+			c >>= 8;
+		}
+	}
+	retval =
+	    usb_control_msg(vub300->udev, usb_sndctrlpipe(vub300->udev, 0),
+			    SET_CLOCK_SPEED,
+			    USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			    0x00, 0x00, buf, buf_array_size, HZ);
+	if (8 != retval) {
+		dev_err(&vub300->udev->dev, "SET_CLOCK_SPEED"
+			" %dkHz failed with retval=%d\n", kHzClock, retval);
+	} else {
+		dev_info(&vub300->udev->dev, "SET_CLOCK_SPEED"
+			 " %dkHz\n", kHzClock);
+	}
+}
+
+static void vub300_mmc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
+{				/* NOT irq */
+	struct vub300_mmc_host *vub300 = mmc_priv(mmc);
+	if (!vub300->interface)
+		return;
+	kref_get(&vub300->kref);
+	mutex_lock(&vub300->cmd_mutex);
+	if ((MMC_POWER_OFF == ios->power_mode) && vub300->card_powered) {
+		vub300->card_powered = 0;
+		usb_control_msg(vub300->udev,
+				    usb_sndctrlpipe(vub300->udev, 0),
+				    SET_SD_POWER,
+				    USB_DIR_OUT | USB_TYPE_VENDOR |
+				    USB_RECIP_DEVICE, 0x0000, 0x0000, NULL, 0,
+				    HZ);
+		/*
+		 * must wait for the VUB300 u-proc to boot up
+		 */
+		msleep(600);
+	} else if ((MMC_POWER_UP == ios->power_mode) && !vub300->card_powered) {
+		usb_control_msg(vub300->udev,
+				    usb_sndctrlpipe(vub300->udev, 0),
+				    SET_SD_POWER,
+				    USB_DIR_OUT | USB_TYPE_VENDOR |
+				    USB_RECIP_DEVICE, 0x0001, 0x0000, NULL, 0,
+				    HZ);
+		msleep(600);
+		vub300->card_powered = 1;
+	} else if (MMC_POWER_ON == ios->power_mode) {
+		u8 *buf = kmalloc(8, GFP_KERNEL);
+		if (buf) {
+			__set_clock_speed(vub300, buf, ios);
+			kfree(buf);
+		}
+	} else {
+		/*
+		 * this should mean no change of state
+		 */
+	}
+	mutex_unlock(&vub300->cmd_mutex);
+	kref_put(&vub300->kref, vub300_delete);
+}
+
+static int vub300_mmc_get_ro(struct mmc_host *mmc)
+{
+	struct vub300_mmc_host *vub300 = mmc_priv(mmc);
+	return vub300->read_only;
+}
+
+static void vub300_enable_sdio_irq(struct mmc_host *mmc, int enable)
+{				/* NOT irq */
+	struct vub300_mmc_host *vub300 = mmc_priv(mmc);
+	if (!vub300->interface)
+		return;
+	kref_get(&vub300->kref);
+	if (enable) {
+		mutex_lock(&vub300->irq_mutex);
+		if (vub300->irqs_queued) {
+			vub300->irqs_queued -= 1;
+			mmc_signal_sdio_irq(vub300->mmc);
+		} else if (vub300->irq_disabled) {
+			vub300->irq_disabled = 0;
+			vub300->irq_enabled = 1;
+			vub300_queue_poll_work(vub300, 0);
+		} else if (vub300->irq_enabled) {
+			/*
+			 * this should not happen
+			 * so we will just ignore it
+			 */
+		} else {
+			vub300->irq_enabled = 1;
+			vub300_queue_poll_work(vub300, 0);
+		}
+		mutex_unlock(&vub300->irq_mutex);
+	} else {
+		vub300->irq_enabled = 0;
+	}
+	kref_put(&vub300->kref, vub300_delete);
+}
+
+void vub300_init_card(struct mmc_host *mmc, struct mmc_card *card)
+{				/* NOT irq */
+	struct vub300_mmc_host *vub300 = mmc_priv(mmc);
+	dev_info(&vub300->udev->dev, "NO host QUIRKS for this card\n");
+}
+
+static struct mmc_host_ops vub300_mmc_ops = {
+	.request = vub300_mmc_request,
+	.set_ios = vub300_mmc_set_ios,
+	.get_ro = vub300_mmc_get_ro,
+	.enable_sdio_irq = vub300_enable_sdio_irq,
+	.init_card = vub300_init_card,
+};
+
+static int vub300_probe(struct usb_interface *interface,
+			const struct usb_device_id *id)
+{				/* NOT irq */
+	struct vub300_mmc_host *vub300 = NULL;
+	struct usb_host_interface *iface_desc;
+	struct usb_device *udev = usb_get_dev(interface_to_usbdev(interface));
+	int i;
+	int retval = -ENOMEM;
+	struct urb *command_out_urb;
+	struct urb *command_res_urb;
+	struct mmc_host *mmc;
+	char Manufacturer[48];
+	char Product[32];
+	char SerialNumber[32];
+	usb_string(udev, udev->descriptor.iManufacturer, Manufacturer,
+		   sizeof(Manufacturer));
+	usb_string(udev, udev->descriptor.iProduct, Product, sizeof(Product));
+	usb_string(udev, udev->descriptor.iSerialNumber, SerialNumber,
+		   sizeof(SerialNumber));
+	dev_info(&udev->dev, "probing VID:PID(%04X:%04X) %s %s %s\n",
+		 udev->descriptor.idVendor, udev->descriptor.idProduct,
+		 Manufacturer, Product, SerialNumber);
+	command_out_urb = usb_alloc_urb(0, GFP_KERNEL);
+	if (!command_out_urb) {
+		retval = -ENOMEM;
+		dev_err(&vub300->udev->dev,
+			"not enough memory for the command_out_urb\n");
+		goto error0;
+	}
+	command_res_urb = usb_alloc_urb(0, GFP_KERNEL);
+	if (!command_res_urb) {
+		retval = -ENOMEM;
+		dev_err(&vub300->udev->dev,
+			"not enough memory for the command_res_urb\n");
+		goto error1;
+	}
+	/* this also allocates memory for our VUB300 mmc host device */
+	mmc = mmc_alloc_host(sizeof(struct vub300_mmc_host), &udev->dev);
+	if (!mmc) {
+		retval = -ENOMEM;
+		dev_err(&vub300->udev->dev,
+			"not enough memory for the mmc_host\n");
+		goto error4;
+	}
+	/* MMC core transfer sizes tunable parameters */
+	mmc->caps = 0;
+	if (!force_1_bit_data_xfers)
+		mmc->caps |= MMC_CAP_4_BIT_DATA;
+	if (!force_polling_for_irqs)
+		mmc->caps |= MMC_CAP_SDIO_IRQ;
+	mmc->caps &= ~MMC_CAP_NEEDS_POLL;
+	/*
+	 * MMC_CAP_NEEDS_POLL causes core.c:mmc_rescan() to poll
+	 * for devices which results in spurious CMD7's being
+	 * issued which stops some SDIO cards from working
+	 */
+	if (limit_speed_to_24_MHz) {
+		mmc->caps |= MMC_CAP_MMC_HIGHSPEED;
+		mmc->caps |= MMC_CAP_SD_HIGHSPEED;
+		mmc->f_max = 24000000;
+		dev_info(&udev->dev, "limiting SDIO speed to 24_MHz\n");
+	} else {
+		mmc->caps |= MMC_CAP_MMC_HIGHSPEED;
+		mmc->caps |= MMC_CAP_SD_HIGHSPEED;
+		mmc->f_max = 48000000;
+	}
+	mmc->f_min = 200000;
+	mmc->max_blk_count = 511;
+	mmc->max_blk_size = 512;
+	mmc->max_segs = 128;
+	if (force_max_req_size)
+		mmc->max_req_size = force_max_req_size * 1024;
+	else
+		mmc->max_req_size = 64 * 1024;
+	mmc->max_seg_size = mmc->max_req_size;
+	mmc->ocr_avail = 0;
+	mmc->ocr_avail |= MMC_VDD_165_195;
+	mmc->ocr_avail |= MMC_VDD_20_21;
+	mmc->ocr_avail |= MMC_VDD_21_22;
+	mmc->ocr_avail |= MMC_VDD_22_23;
+	mmc->ocr_avail |= MMC_VDD_23_24;
+	mmc->ocr_avail |= MMC_VDD_24_25;
+	mmc->ocr_avail |= MMC_VDD_25_26;
+	mmc->ocr_avail |= MMC_VDD_26_27;
+	mmc->ocr_avail |= MMC_VDD_27_28;
+	mmc->ocr_avail |= MMC_VDD_28_29;
+	mmc->ocr_avail |= MMC_VDD_29_30;
+	mmc->ocr_avail |= MMC_VDD_30_31;
+	mmc->ocr_avail |= MMC_VDD_31_32;
+	mmc->ocr_avail |= MMC_VDD_32_33;
+	mmc->ocr_avail |= MMC_VDD_33_34;
+	mmc->ocr_avail |= MMC_VDD_34_35;
+	mmc->ocr_avail |= MMC_VDD_35_36;
+	mmc->ops = &vub300_mmc_ops;
+	vub300 = mmc_priv(mmc);
+	vub300->mmc = mmc;
+	vub300->card_powered = 0;
+	vub300->bus_width = 0;
+	vub300->cmnd.head.BlockSize[0] = 0x00;
+	vub300->cmnd.head.BlockSize[1] = 0x00;
+	vub300->app_spec = 0;
+	mutex_init(&vub300->cmd_mutex);
+	mutex_init(&vub300->irq_mutex);
+	vub300->command_out_urb = command_out_urb;
+	vub300->command_res_urb = command_res_urb;
+	vub300->usb_timed_out = 0;
+	vub300->dynamic_register_count = 0;
+	{
+		int i;
+		for (i = 0; i < ARRAY_SIZE(vub300->fn); i++) {
+			vub300->fn[i].offload_point = 0;
+			vub300->fn[i].offload_count = 0;
+		}
+	}
+	vub300->total_offload_count = 0;
+	vub300->irq_enabled = 0;
+	vub300->irq_disabled = 0;
+	vub300->irqs_queued = 0;
+	{
+		int i;
+		for (i = 0; i < ARRAY_SIZE(vub300->sdio_register); i++)
+			vub300->sdio_register[i++].activate = 0;
+	}
+	vub300->udev = udev;
+	vub300->interface = interface;
+	vub300->cmnd_res_ep = 0;
+	vub300->cmnd_out_ep = 0;
+	vub300->data_inp_ep = 0;
+	vub300->data_out_ep = 0;
+	{
+		int i;
+		for (i = 0; i < ARRAY_SIZE(vub300->fbs); i++)
+			vub300->fbs[i] = 512;
+	}
+	/*
+	 *      set up the endpoint information
+	 *
+	 * use the first pair of bulk-in and bulk-out
+	 *     endpoints for Command/Response+Interrupt
+	 *
+	 * use the second pair of bulk-in and bulk-out
+	 *     endpoints for Data In/Out
+	 */
+	vub300->large_usb_packets = 0;
+	iface_desc = interface->cur_altsetting;
+	for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) {
+		struct usb_endpoint_descriptor *endpoint =
+		    &iface_desc->endpoint[i].desc;
+		dev_info(&vub300->udev->dev,
+			 "vub300 testing %s EndPoint(%d) %02X\n",
+			 usb_endpoint_is_bulk_in(endpoint) ? "BULK IN" :
+			 usb_endpoint_is_bulk_out(endpoint) ? "BULK OUT" :
+			 "UNKNOWN", i, endpoint->bEndpointAddress);
+		if (64 < endpoint->wMaxPacketSize)
+			vub300->large_usb_packets = 1;
+		if (usb_endpoint_is_bulk_in(endpoint)) {
+			if (!vub300->cmnd_res_ep) {
+				vub300->cmnd_res_ep =
+				    endpoint->bEndpointAddress;
+			} else if (!vub300->data_inp_ep) {
+				vub300->data_inp_ep =
+				    endpoint->bEndpointAddress;
+			} else {
+				dev_warn(&vub300->udev->dev,
+					 "ignoring"
+					 " unexpected bulk_in endpoint");
+			}
+		} else if (usb_endpoint_is_bulk_out(endpoint)) {
+			if (!vub300->cmnd_out_ep) {
+				vub300->cmnd_out_ep =
+				    endpoint->bEndpointAddress;
+			} else if (!vub300->data_out_ep) {
+				vub300->data_out_ep =
+				    endpoint->bEndpointAddress;
+			} else {
+				dev_warn(&vub300->udev->dev,
+					 "ignoring"
+					 " unexpected bulk_out endpoint");
+			}
+		} else {
+			dev_warn(&vub300->udev->dev,
+				 "vub300 ignoring EndPoint(%d) %02X", i,
+				 endpoint->bEndpointAddress);
+		}
+	}
+	if (vub300->cmnd_res_ep
+	    && vub300->cmnd_out_ep
+	    && vub300->data_inp_ep && vub300->data_out_ep) {
+		dev_info(&vub300->udev->dev,
+			 "vub300 %s packets"
+			 " using EndPoints %02X %02X %02X %02X\n",
+			 vub300->large_usb_packets ? "LARGE" : "SMALL",
+			 vub300->cmnd_out_ep, vub300->cmnd_res_ep,
+			 vub300->data_out_ep, vub300->data_inp_ep);
+		/*
+		 * we have the expected EndPoints
+		 */
+	} else {
+		dev_err(&vub300->udev->dev,
+			"Could not find two sets of bulk-in/out endpoint pairs\n");
+		retval = -EINVAL;
+		goto error5;
+	}
+	retval =
+	    usb_control_msg(vub300->udev, usb_rcvctrlpipe(vub300->udev, 0),
+			    GET_HC_INF0,
+			    USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			    0x0000, 0x0000, &vub300->hc_info,
+			    sizeof(vub300->hc_info), HZ);
+	if (retval < 0)
+		goto error5;
+	retval =
+	    usb_control_msg(vub300->udev, usb_rcvctrlpipe(vub300->udev, 0),
+			    SET_ROM_WAIT_STATES,
+			    USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			    firmware_rom_wait_states, 0x0000, NULL, 0, HZ);
+	if (retval < 0)
+		goto error5;
+	dev_info(&vub300->udev->dev,
+		 "operating_mode = %s %s %d MHz %s %d byte USB packets\n",
+		 (mmc->caps & MMC_CAP_SDIO_IRQ) ? "IRQs" : "POLL",
+		 (mmc->caps & MMC_CAP_4_BIT_DATA) ? "4-bit" : "1-bit",
+		 mmc->f_max / 1000000,
+		 pad_input_to_usb_pkt ? "padding input data to" : "with",
+		 vub300->large_usb_packets ? 512 : 64);
+	retval =
+	    usb_control_msg(vub300->udev, usb_rcvctrlpipe(vub300->udev, 0),
+			    GET_SYSTEM_PORT_STATUS,
+			    USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+			    0x0000, 0x0000, &vub300->system_port_status,
+			    sizeof(vub300->system_port_status), HZ);
+	if (retval < 0) {
+		goto error4;
+	} else if (sizeof(vub300->system_port_status) == retval) {
+		vub300->card_present =
+		    (0x0001 & vub300->system_port_status.PortFlags) ? 1 : 0;
+		vub300->read_only =
+		    (0x0010 & vub300->system_port_status.PortFlags) ? 1 : 0;
+	} else {
+		goto error4;
+	}
+	usb_set_intfdata(interface, vub300);
+	INIT_DELAYED_WORK(&vub300->pollwork, vub300_pollwork_thread);
+	INIT_WORK(&vub300->cmndwork, vub300_cmndwork_thread);
+	INIT_WORK(&vub300->deadwork, vub300_deadwork_thread);
+	kref_init(&vub300->kref);
+	init_timer(&vub300->sg_transfer_timer);
+	vub300->sg_transfer_timer.data = (unsigned long)vub300;
+	vub300->sg_transfer_timer.function = vub300_sg_timed_out;
+	kref_get(&vub300->kref);
+	init_timer(&vub300->inactivity_timer);
+	vub300->inactivity_timer.data = (unsigned long)vub300;
+	vub300->inactivity_timer.function = vub300_inactivity_timer_expired;
+	vub300->inactivity_timer.expires = jiffies + HZ;
+	add_timer(&vub300->inactivity_timer);
+	if (vub300->card_present)
+		dev_info(&vub300->udev->dev,
+			 "USB vub300 remote SDIO host controller[%d]"
+			 "connected with SD/SDIO card inserted\n",
+			 interface_to_InterfaceNumber(interface));
+	else
+		dev_info(&vub300->udev->dev,
+			 "USB vub300 remote SDIO host controller[%d]"
+			 "connected with no SD/SDIO card inserted\n",
+			 interface_to_InterfaceNumber(interface));
+	mmc_add_host(mmc);
+	retval = sysfs_create_group(&mmc->class_dev.kobj, &vub300_attr_grp);
+	return 0;
+error5:
+	mmc_free_host(mmc);
+	/*
+	 * and hence also frees vub300
+	 * which is contained at the end of struct mmc
+	 */
+error4:
+	usb_free_urb(command_out_urb);
+error1:
+	usb_free_urb(command_res_urb);
+error0:
+	return retval;
+}
+
+static void vub300_disconnect(struct usb_interface *interface)
+{				/* NOT irq */
+	struct vub300_mmc_host *vub300 = usb_get_intfdata(interface);
+	if (!vub300 || !vub300->mmc) {
+		return;
+	} else {
+		struct mmc_host *mmc = vub300->mmc;
+		if (!vub300->mmc) {
+			return;
+		} else {
+			int ifnum = interface_to_InterfaceNumber(interface);
+			sysfs_remove_group(&mmc->class_dev.kobj,
+					   &vub300_attr_grp);
+			usb_set_intfdata(interface, NULL);
+			/* prevent more I/O from starting */
+			vub300->interface = NULL;
+			kref_put(&vub300->kref, vub300_delete);
+			mmc_remove_host(mmc);
+			pr_info("USB vub300 remote SDIO host controller[%d]"
+				" now disconnected", ifnum);
+			return;
+		}
+	}
+}
+
+#ifdef CONFIG_PM
+static int vub300_suspend(struct usb_interface *intf, pm_message_t message)
+{
+	struct vub300_mmc_host *vub300 = usb_get_intfdata(intf);
+	if (!vub300 || !vub300->mmc) {
+		return 0;
+	} else {
+		struct mmc_host *mmc = vub300->mmc;
+		mmc_suspend_host(mmc);
+		return 0;
+	}
+}
+
+static int vub300_resume(struct usb_interface *intf)
+{
+	struct vub300_mmc_host *vub300 = usb_get_intfdata(intf);
+	if (!vub300 || !vub300->mmc) {
+		return 0;
+	} else {
+		struct mmc_host *mmc = vub300->mmc;
+		mmc_resume_host(mmc);
+		return 0;
+	}
+}
+#else
+#define vub300_suspend NULL
+#define vub300_resume NULL
+#endif
+static int vub300_pre_reset(struct usb_interface *intf)
+{				/* NOT irq */
+	struct vub300_mmc_host *vub300 = usb_get_intfdata(intf);
+	mutex_lock(&vub300->cmd_mutex);
+	return 0;
+}
+
+static int vub300_post_reset(struct usb_interface *intf)
+{				/* NOT irq */
+	struct vub300_mmc_host *vub300 = usb_get_intfdata(intf);
+	/* we are sure no URBs are active - no locking needed */
+	vub300->errors = -EPIPE;
+	mutex_unlock(&vub300->cmd_mutex);
+	return 0;
+}
+
+static struct usb_driver vub300_driver = {
+	.name = "vub300",
+	.probe = vub300_probe,
+	.disconnect = vub300_disconnect,
+	.suspend = vub300_suspend,
+	.resume = vub300_resume,
+	.pre_reset = vub300_pre_reset,
+	.post_reset = vub300_post_reset,
+	.id_table = vub300_table,
+	.supports_autosuspend = 1,
+};
+
+static int __init vub300_init(void)
+{				/* NOT irq */
+	int result;
+	pr_info("VUB300 Driver rom wait states = %02X irqpoll timeout = %04X",
+		firmware_rom_wait_states, 0x0FFFF & firmware_irqpoll_timeout);
+	cmndworkqueue = create_singlethread_workqueue("kvub300c");
+	if (!cmndworkqueue) {
+		pr_err("not enough memory for the REQUEST workqueue");
+		result = -ENOMEM;
+		goto out1;
+	}
+	pollworkqueue = create_singlethread_workqueue("kvub300p");
+	if (!pollworkqueue) {
+		pr_err("not enough memory for the IRQPOLL workqueue");
+		result = -ENOMEM;
+		goto out2;
+	}
+	deadworkqueue = create_singlethread_workqueue("kvub300d");
+	if (!deadworkqueue) {
+		pr_err("not enough memory for the EXPIRED workqueue");
+		result = -ENOMEM;
+		goto out3;
+	}
+	result = usb_register(&vub300_driver);
+	if (result) {
+		pr_err("usb_register failed. Error number %d", result);
+		goto out4;
+	}
+	return 0;
+out4:
+	destroy_workqueue(deadworkqueue);
+out3:
+	destroy_workqueue(pollworkqueue);
+out2:
+	destroy_workqueue(cmndworkqueue);
+out1:
+	return result;
+}
+
+static void __exit vub300_exit(void)
+{
+	usb_deregister(&vub300_driver);
+	flush_workqueue(cmndworkqueue);
+	flush_workqueue(pollworkqueue);
+	flush_workqueue(deadworkqueue);
+	destroy_workqueue(cmndworkqueue);
+	destroy_workqueue(pollworkqueue);
+	destroy_workqueue(deadworkqueue);
+}
+
+module_init(vub300_init);
+module_exit(vub300_exit);
+MODULE_LICENSE("GPL");

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH] mmc: USB SDIO/SD/MMC Host Controller (VUB300) driver Re-Resubmission
  2011-03-10 16:13               ` [PATCH] mmc: USB SDIO/SD/MMC Host Controller (VUB300) driver Re-Resubmission Tony Olech
@ 2011-03-15  3:01                 ` Chris Ball
  2011-03-15  9:40                   ` Wolfram Sang
  2011-03-15 16:23                 ` Arnd Bergmann
  1 sibling, 1 reply; 41+ messages in thread
From: Chris Ball @ 2011-03-15  3:01 UTC (permalink / raw)
  To: Tony Olech; +Cc: linux-mmc

Hi,

On Thu, Mar 10 2011, Tony Olech wrote:
> Add a driver for Elan Digital System's VUB300 chip
> which is a USB connected SDIO/SDmem/MMC host controller.
> A VUB300 chip enables a USB 2.0 or USB 1.1 connected host
> computer to use SDIO/SD/MMC cards without the need for
> a directly connected, for example via PCI, SDIO host
> controller.
>
> Signed-off-by: Anthony F Olech <tony.olech@elandigitalsystems.com>

linux-mmc@ folks, any strong opinions on this driver?  My impression is
that it looks reasonable, but has some areas of being pretty far from
standard CodingStyle.  I'm leaning towards merging it as-is and then
working on incremental fixes; please yell if that sounds like a bad idea.

Thanks,

- Chris.
-- 
Chris Ball   <cjb@laptop.org>   <http://printf.net/>
One Laptop Per Child

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH] mmc: USB SDIO/SD/MMC Host Controller (VUB300) driver Re-Resubmission
  2011-03-15  3:01                 ` Chris Ball
@ 2011-03-15  9:40                   ` Wolfram Sang
  2011-03-15 15:06                     ` Chris Ball
  0 siblings, 1 reply; 41+ messages in thread
From: Wolfram Sang @ 2011-03-15  9:40 UTC (permalink / raw)
  To: Chris Ball; +Cc: Tony Olech, linux-mmc

[-- Attachment #1: Type: text/plain, Size: 805 bytes --]

Hi,

> linux-mmc@ folks, any strong opinions on this driver?  My impression is
> that it looks reasonable, but has some areas of being pretty far from
> standard CodingStyle.

I totally agree (sadly it is hard to review because of its size) and I am still
not convinced of the sysfs-entry. Then again, it is a working driver.

> I'm leaning towards merging it as-is and then
> working on incremental fixes; please yell if that sounds like a bad idea.

What about staging? From my all overall impression, the driver would fit there.
Also, there are more people lurking around fixing coding-style than here.

Regards,

   Wolfram

-- 
Pengutronix e.K.                           | Wolfram Sang                |
Industrial Linux Solutions                 | http://www.pengutronix.de/  |

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 197 bytes --]

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH] mmc: USB SDIO/SD/MMC Host Controller (VUB300) driver Re-Resubmission
  2011-03-15  9:40                   ` Wolfram Sang
@ 2011-03-15 15:06                     ` Chris Ball
  2011-03-15 15:41                       ` Arnd Bergmann
  0 siblings, 1 reply; 41+ messages in thread
From: Chris Ball @ 2011-03-15 15:06 UTC (permalink / raw)
  To: Wolfram Sang; +Cc: Tony Olech, linux-mmc

Hi Wolfram,

On Tue, Mar 15 2011, Wolfram Sang wrote:
>> I'm leaning towards merging it as-is and then
>> working on incremental fixes; please yell if that sounds like a bad idea.
>
> What about staging? From my all overall impression, the driver would fit there.
> Also, there are more people lurking around fixing coding-style than here.

I thought about it, but I think staging drivers generally have a much
larger TODO list than this one would.

Perhaps a good plan is:  I'll push it to mmc-next after the 2.6.39 merge,
then I'll try to work on style changes a bit (I'll send each patch to the
list and Tony can ACK/NACK them), and then we can include it in 2.6.40
either as a normal driver if we're comfortable with how it's ended up,
or as a staging driver if we'd still like to get some more eyes on it.

Does that sound okay to everyone?

Thanks,

- Chris.
-- 
Chris Ball   <cjb@laptop.org>   <http://printf.net/>
One Laptop Per Child

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH] mmc: USB SDIO/SD/MMC Host Controller (VUB300) driver Re-Resubmission
  2011-03-15 15:06                     ` Chris Ball
@ 2011-03-15 15:41                       ` Arnd Bergmann
  0 siblings, 0 replies; 41+ messages in thread
From: Arnd Bergmann @ 2011-03-15 15:41 UTC (permalink / raw)
  To: Chris Ball; +Cc: Wolfram Sang, Tony Olech, linux-mmc

On Tuesday 15 March 2011, Chris Ball wrote:
> I thought about it, but I think staging drivers generally have a much
> larger TODO list than this one would.
> 
> Perhaps a good plan is:  I'll push it to mmc-next after the 2.6.39 merge,
> then I'll try to work on style changes a bit (I'll send each patch to the
> list and Tony can ACK/NACK them), and then we can include it in 2.6.40
> either as a normal driver if we're comfortable with how it's ended up,
> or as a staging driver if we'd still like to get some more eyes on it.
> 
> Does that sound okay to everyone?

Sounds good to me. I certainly wouldn't rush it now after the
merge window is open. I'll also do a quick review for a TODO
list.

	Arnd

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH] mmc: USB SDIO/SD/MMC Host Controller (VUB300) driver Re-Resubmission
  2011-03-10 16:13               ` [PATCH] mmc: USB SDIO/SD/MMC Host Controller (VUB300) driver Re-Resubmission Tony Olech
  2011-03-15  3:01                 ` Chris Ball
@ 2011-03-15 16:23                 ` Arnd Bergmann
  2011-03-15 16:55                   ` Tony Olech
  2011-04-19  9:05                   ` Tony Olech
  1 sibling, 2 replies; 41+ messages in thread
From: Arnd Bergmann @ 2011-03-15 16:23 UTC (permalink / raw)
  To: Tony Olech; +Cc: Chris Ball, linux-mmc

On Thursday 10 March 2011, Tony Olech wrote:
>   Add a driver for Elan Digital System's VUB300 chip
> which is a USB connected SDIO/SDmem/MMC host controller.
> A VUB300 chip enables a USB 2.0 or USB 1.1 connected host
> computer to use SDIO/SD/MMC cards without the need for
> a directly connected, for example via PCI, SDIO host
> controller.

First a small question: I've read that USB mmc controllers
only work for SDIO but not for storage cards. Is that true
for this driver?

If not, I'd probably get one for card testing, since the
spring loading mechanism in my laptop is starting to wear
out.

What are typical products using this?

My comments are mostly for coding style, I didn't see any
major problems with the operation of the driver.

> +#include <linux/workqueue.h>
> +#include <linux/ctype.h>
> +#include <linux/firmware.h>
> +#include <linux/scatterlist.h>
> +struct HostController_Info {
> +       u8 Size;
> +       u16 FirmwareVer;
> +       u8 NumberOfPorts;
> +} __packed;
> +struct SD_Command_Header {
> +       u8 HeaderSize;
> +       u8 HeaderType;

Please insert empty lines between the definitions,
and use proper identifiers. In particular, don't
use CamelCase but lowercase with underscores like

struct host_controller_info;

> +       u8 PortNumber;
> +       u8 Command_Type; /* Bit7 - Rd/Wr */
> +       u8 CommandIndex;
> +       u8 TransferSize[4]; /* ReadSize + ReadSize */
> +       u8 ResponseType;
> +       u8 Arguments[4];
> +       u8 BlockCount[2];
> +       u8 BlockSize[2];
> +       u8 BlockBoundary[2];
> +       u8 Reserved[44]; /* to pad out to 64 bytes */
> +} __packed;

Don't use __packed for structures that are implicitly packed
already, or for structures that are not defined in hardware.
In this case, all members are u8, so __packed has no significance.

In other cases, it's better to mark only those members as packed
that are unaligned in hardware, instead of marking all of them.

Access to structures marked as __packed can be signficantly
slower on some architectures because gcc has to replace
them with bytewise access, even when they are in fact
aligned.

> +#define RESPONSE_INTERRUPT 0x01
> +#define RESPONSE_ERROR 0x02
> +#define RESPONSE_STATUS 0x03
> +#define RESPONSE_IRQ_DISABLED 0x05
> +#define RESPONSE_IRQ_ENABLED 0x06
> +#define RESPONSE_PIGGYBACKED 0x07

It would be more readable if you insert some tabs to
align the values, like

#define RESPONSE_INTERRUPT	0x01
#define RESPONSE_ERROR		0x02
#define RESPONSE_STATUS		0x03

> +static int limit_speed_to_24_MHz = INITIALIZE_VALUE_TO_ZERO;
> +module_param(limit_speed_to_24_MHz, bool, 0644);
> +MODULE_PARM_DESC(limit_speed_to_24_MHz, "Limit Max SDIO Clock Speed to 24 MHz");
> +static int pad_input_to_usb_pkt = INITIALIZE_VALUE_TO_ZERO;
> +module_param(pad_input_to_usb_pkt, bool, 0644);
> +MODULE_PARM_DESC(pad_input_to_usb_pkt,
> +                "Pad USB data input transfers to whole USB Packet");
> +static int disable_offload_processing = INITIALIZE_VALUE_TO_ZERO;
> +module_param(disable_offload_processing, bool, 0644);
> +MODULE_PARM_DESC(disable_offload_processing, "Disable Offload Processing");
> +static int force_1_bit_data_xfers = INITIALIZE_VALUE_TO_ZERO;
> +module_param(force_1_bit_data_xfers, bool, 0644);
> +MODULE_PARM_DESC(force_1_bit_data_xfers,
> +                "Force SDIO Data Transfers to 1-bit Mode");
> +static int force_polling_for_irqs = INITIALIZE_VALUE_TO_ZERO;
> +module_param(force_polling_for_irqs, bool, 0644);
> +MODULE_PARM_DESC(force_polling_for_irqs, "Force Polling for SDIO interrupts");
> +static int firmware_irqpoll_timeout = 1024;
> +module_param(firmware_irqpoll_timeout, int, 0644);
> +MODULE_PARM_DESC(firmware_irqpoll_timeout, "VUB300 firmware irqpoll timeout");
> +static int force_max_req_size = 128;
> +module_param(force_max_req_size, int, 0644);
> +MODULE_PARM_DESC(force_max_req_size, "set max request size in kBytes");
> +#ifdef SMSC_DEVELOPMENT_BOARD
> +static int firmware_rom_wait_states = 0x04;
> +#else
> +static int firmware_rom_wait_states = 0x1C;
> +#endif
> +module_param(firmware_rom_wait_states, bool, 0644);
> +MODULE_PARM_DESC(firmware_rom_wait_states,
> +                "ROM wait states byte=RRRIIEEE (Reserved Internal External)");

A lot of module parameters for a simple driver. I assume that some
of these were useful in debugging but could be removed now.

> +static struct workqueue_struct *cmndworkqueue;
> +static struct workqueue_struct *pollworkqueue;
> +static struct workqueue_struct *deadworkqueue;

Do you really need three separate workqueues? Note that you can
have different work_struct in the same queue.

> +       unsigned card_powered:1;
> +       unsigned card_present:1;
> +       unsigned read_only:1;
> +       unsigned large_usb_packets:1;
> +       unsigned app_spec:1; /* ApplicationSpecific */
> +       unsigned irq_enabled:1; /* by the MMC CORE */
> +       unsigned irq_disabled:1; /* in the firmware */
> +       unsigned bus_width:4;

Bit fields are normally discouraged, just use bool values
for flags, or a u8 for the bus_width.

> +static ssize_t __show_operating_mode(struct vub300_mmc_host *vub300,
> +                                   struct mmc_host *mmc, char *buf)
> +{
> +       int usb_packet_size = vub300->large_usb_packets ? 512 : 64;
> +       if (vub300->vub_name[0])
> +               return sprintf(buf, "VUB %s %s %d MHz %s %d byte USB packets"
> +                               " using %s\n",
> +                      (mmc->caps & MMC_CAP_SDIO_IRQ) ? "IRQs" : "POLL",
> +                      (mmc->caps & MMC_CAP_4_BIT_DATA) ? "4-bit" : "1-bit",
> +                      mmc->f_max / 1000000,
> +                      pad_input_to_usb_pkt ? "padding input data to" : "with",
> +                      usb_packet_size, vub300->vub_name);
> +       else
> +               return sprintf(buf, "VUB %s %s %d MHz %s %d byte USB packets"
> +                               " and no offload processing\n",
> +                      (mmc->caps & MMC_CAP_SDIO_IRQ) ? "IRQs" : "POLL",
> +                      (mmc->caps & MMC_CAP_4_BIT_DATA) ? "4-bit" : "1-bit",
> +                      mmc->f_max / 1000000,
> +                      pad_input_to_usb_pkt ? "padding input data to" : "with",
> +                      usb_packet_size);
> +}
> +
> +static ssize_t show_operating_mode(struct device *dev,
> +                                 struct device_attribute *attr, char *buf)
> +{
> +       struct mmc_host *mmc = container_of(dev, struct mmc_host, class_dev);
> +       if (mmc) {
> +               struct vub300_mmc_host *vub300 = mmc_priv(mmc);
> +               return __show_operating_mode(vub300, mmc, buf);
> +       } else {
> +               return sprintf(buf, "VUB driver has no attached device");
> +       }
> +}

This sysfs attribute is rather hard to parse from user space, it looks
like it's designed only to be read by humans. I think it would be better
to use multiple attributes, each of which has only a single piece
of information in it.

Some of these attributes however don't really belong into this driver
but into the core, like the 1-bit / 4-bit mode. Please leave this out
of your driver, and submit a separate patch to the mmc core if you
think it's reasonable.

> +static void vub300_queue_cmnd_work(struct vub300_mmc_host *vub300)
> +{
> +       kref_get(&vub300->kref);
> +       if (queue_work(cmndworkqueue, &vub300->cmndwork)) {
> +               /*
> +                * then the cmndworkqueue was not previously
> +                * running and the above get ref is obvious
> +                * required and will be put when the thread
> +                * terminates by a specific call
> +                */
> +       } else {
> +               /*
> +                * the cmndworkqueue was already running from
> +                * a previous invocation and thus to keep the
> +                * kref counts correct we must undo the get
> +                */
> +               kref_put(&vub300->kref, vub300_delete);
> +       }
> +}

You use the same kref_get/kref_put statement in a lot of
places, better encapsulate it in an inline function and make the
callers do

	vub300_get(vub300);
	...
	vub300_put(vub300);


> +       } else if (!new_card_present && old_card_present) {
> +               dev_info(&vub300->udev->dev, "card just ejected\n");
> +               vub300->card_present = 0;
> +               mmc_detect_change(vub300->mmc, 0);
> +               return;
> +       } else {
> +               return;
> +       }
> +}

The last else branch is redundant here.

> +static void __add_offloaded_reg_to_fifo(struct vub300_mmc_host *vub300,
> +                                       struct Offload_Registers_Access
> +                                       *register_access, u8 Function)
> +{
> +       u8 r =
> +           vub300->fn[Function].offload_point +
> +           vub300->fn[Function].offload_count;
> +       memcpy(&vub300->fn[Function].reg[MAXREGMASK & r]
> +              , register_access, sizeof(struct Offload_Registers_Access));
> +       vub300->fn[Function].offload_count += 1;
> +       vub300->total_offload_count += 1;
> +}

Just use a temporary variable:

	struct offload_interrupt_function_register *fn = &vub300->fn[Function];
	u8 r = fn->offload_point + fn.offload_count;
	...

> +static void __vub300_irqpoll_response(struct vub300_mmc_host *vub300)
> +{
> +       /*
> +        * cmd_mutex is held by vub300_pollwork_thread
> +        */
> +       if (0 == vub300->command_res_urb->actual_length) {

The common coding style is to write the variable name first and then
the value you are comparing with:

	if (vub300->command_res_urb->actual_length == 0)


> +       } else if (RESPONSE_INTERRUPT == vub300->resp.common.HeaderType) {
> +               mutex_lock(&vub300->irq_mutex);
> +               if (vub300->irq_enabled)
> +                       mmc_signal_sdio_irq(vub300->mmc);
> +               else
> +                       vub300->irqs_queued += 1;
> +               vub300->irq_disabled = 1;
> +               mutex_unlock(&vub300->irq_mutex);
> +               mutex_unlock(&vub300->cmd_mutex);
> +               kref_put(&vub300->kref, vub300_delete);
> +               return;
> +       } else if (RESPONSE_ERROR == vub300->resp.common.HeaderType) {
> +               if (SD_ERROR_NO_DEVICE == vub300->resp.error.ErrorCode)
> +                       check_vub300_port_status(vub300);
> +               mutex_unlock(&vub300->cmd_mutex);
> +               vub300_queue_poll_work(vub300, HZ);
> +               kref_put(&vub300->kref, vub300_delete);
> +               return;
> +       } else if (RESPONSE_STATUS == vub300->resp.common.HeaderType) {
> +               vub300->system_port_status = vub300->resp.status;
> +               new_system_port_status(vub300);
> +               mutex_unlock(&vub300->cmd_mutex);
> +               if (!vub300->card_present)
> +                       vub300_queue_poll_work(vub300, HZ / 5);
> +               kref_put(&vub300->kref, vub300_delete);
> +               return;

Better use a switch/case statement instead of lots of if/else. Also, don't
duplicate the cleanup path, instead do:

	switch (vub300->resp.common.HeaderType) {
	case RESPONSE_INTERRUPT:
		...
		break;
	case RESPONSE_ERROR:
		...
		break;
	case ...
		break;
	}
	mutex_unlock(&vub300->cmd_mutex);
	kref_put(&vub300->kref, vub300_delete);
	return;

Also, make sure that all functions have a matching number of
mutex_lock/mutex_unlock etc calls, to avoid confusing the readers.

> +/*
> + * the STUFF bits are masked out for the comparisons
> + */
> +static void snoop_block_size_and_bus_width(struct vub300_mmc_host *vub300,
> +                                          u32 cmd_arg)
> +{
> +       if (0x80022200 == (0xFBFFFE00 & cmd_arg))
> +               vub300->fbs[1] = (cmd_arg << 8) | (0x00FF & vub300->fbs[1]);
> +       else if (0x80022000 == (0xFBFFFE00 & cmd_arg))
> +               vub300->fbs[1] = (0xFF & cmd_arg) | (0xFF00 & vub300->fbs[1]);
> +       else if (0x80042200 == (0xFBFFFE00 & cmd_arg))
> +               vub300->fbs[2] = (cmd_arg << 8) | (0x00FF & vub300->fbs[2]);

switch/case again.

> +static void send_command(struct vub300_mmc_host *vub300)
> +{
> +       /*
> +        * cmd_mutex is held by vub300_cmndwork_thread
> +        */
> +       struct mmc_command *cmd = vub300->cmd;
> +       struct mmc_data *data = vub300->data;
> +       int retval;
> +       u8 ResponseType;
> +       if (vub300->app_spec) {
> +               if (6 == cmd->opcode) {
> +                       ResponseType = SDRT_1;
> +                       vub300->resp_len = 6;
> +                       if (0x00000000 == (0x00000003 & cmd->arg))
> +                               vub300->bus_width = 1;
> +                       else if (0x00000002 == (0x00000003 & cmd->arg))
> +                               vub300->bus_width = 4;
> +                       else
> +                               dev_err(&vub300->udev->dev,
> +                                       "unexpected ACMD6 bus_width=%d\n",
> +                                       0x00000003 & cmd->arg);
> +               } else if (13 == cmd->opcode) {
> +                       ResponseType = SDRT_1;
> +                       vub300->resp_len = 6;
> +               } else if (22 == cmd->opcode) {
> +                       ResponseType = SDRT_1;
> +                       vub300->resp_len = 6;
> +               } else if (23 == cmd->opcode) {
> +                       ResponseType = SDRT_1;
> +                       vub300->resp_len = 6;

and here. Also, this function is too long to be readable.

	Arnd

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH] mmc: USB SDIO/SD/MMC Host Controller (VUB300) driver Re-Resubmission
  2011-03-15 16:23                 ` Arnd Bergmann
@ 2011-03-15 16:55                   ` Tony Olech
  2011-04-19  9:05                   ` Tony Olech
  1 sibling, 0 replies; 41+ messages in thread
From: Tony Olech @ 2011-03-15 16:55 UTC (permalink / raw)
  To: Arnd Bergmann; +Cc: Chris Ball, linux-mmc

see embedded answers:-

On Tue, 2011-03-15 at 17:23 +0100, Arnd Bergmann wrote:
> On Thursday 10 March 2011, Tony Olech wrote:
> >   Add a driver for Elan Digital System's VUB300 chip
> > which is a USB connected SDIO/SDmem/MMC host controller.
> > A VUB300 chip enables a USB 2.0 or USB 1.1 connected host
> > computer to use SDIO/SD/MMC cards without the need for
> > a directly connected, for example via PCI, SDIO host
> > controller.
> First a small question: I've read that USB mmc controllers
> only work for SDIO but not for storage cards. Is that true
> for this driver?

The chip has been designed to handle memory cards as well
as SDIO, and we have test all we could get our hands on,
and tested for and thus 64M to 8G SD cards. It has also
been tested on a 64M MMC (1 bit) data card.

> If not, I'd probably get one for card testing, since the
> spring loading mechanism in my laptop is starting to wear
> out.
> What are typical products using this?

At the moment the dev kit, which is really just a bare board
USB to SD/SDIO/MMC converter, is available for evaluation.
the chips themselves are available for for purchase - see
our web site for details.

> My comments are mostly for coding style, I didn't see any
> major problems with the operation of the driver.

I am going to have to go through your coding style comments
separately.

Thanks for reading through the code,

Tony Olech


^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH] mmc: USB SDIO/SD/MMC Host Controller (VUB300) driver Re-Resubmission
  2011-03-15 16:23                 ` Arnd Bergmann
  2011-03-15 16:55                   ` Tony Olech
@ 2011-04-19  9:05                   ` Tony Olech
  2011-04-19 12:10                     ` Arnd Bergmann
  1 sibling, 1 reply; 41+ messages in thread
From: Tony Olech @ 2011-04-19  9:05 UTC (permalink / raw)
  To: Arnd Bergmann; +Cc: Chris Ball, linux-mmc

On Tue, 2011-03-15 at 17:23 +0100, Arnd Bergmann wrote:
> On Thursday 10 March 2011, Tony Olech wrote:
...
> > +static ssize_t __show_operating_mode(struct vub300_mmc_host *vub300,
> > +                                   struct mmc_host *mmc, char *buf)
> > +{
> > +       int usb_packet_size = vub300->large_usb_packets ? 512 : 64;
> > +       if (vub300->vub_name[0])
> > +               return sprintf(buf, "VUB %s %s %d MHz %s %d byte USB packets"
> > +                               " using %s\n",
> > +                      (mmc->caps & MMC_CAP_SDIO_IRQ) ? "IRQs" : "POLL",
> > +                      (mmc->caps & MMC_CAP_4_BIT_DATA) ? "4-bit" : "1-bit",
> > +                      mmc->f_max / 1000000,
> > +                      pad_input_to_usb_pkt ? "padding input data to" : "with",
> > +                      usb_packet_size, vub300->vub_name);
> > +       else
> > +               return sprintf(buf, "VUB %s %s %d MHz %s %d byte USB packets"
> > +                               " and no offload processing\n",
> > +                      (mmc->caps & MMC_CAP_SDIO_IRQ) ? "IRQs" : "POLL",
> > +                      (mmc->caps & MMC_CAP_4_BIT_DATA) ? "4-bit" : "1-bit",
> > +                      mmc->f_max / 1000000,
> > +                      pad_input_to_usb_pkt ? "padding input data to" : "with",
> > +                      usb_packet_size);
> > +}
> > +
> > +static ssize_t show_operating_mode(struct device *dev,
> > +                                 struct device_attribute *attr, char *buf)
> > +{
> > +       struct mmc_host *mmc = container_of(dev, struct mmc_host, class_dev);
> > +       if (mmc) {
> > +               struct vub300_mmc_host *vub300 = mmc_priv(mmc);
> > +               return __show_operating_mode(vub300, mmc, buf);
> > +       } else {
> > +               return sprintf(buf, "VUB driver has no attached device");
> > +       }
> > +}
> 
> This sysfs attribute is rather hard to parse from user space, it looks
> like it's designed only to be read by humans. I think it would be better
> to use multiple attributes, each of which has only a single piece
> of information in it.
> 
> Some of these attributes however don't really belong into this driver
> but into the core, like the 1-bit / 4-bit mode. Please leave this out
> of your driver, and submit a separate patch to the mmc core if you
> think it's reasonable.

The purpose of this read-only interface is for VUB300 support
staff to obtain information from our customers. Our customers
are not like the people on this list. If you have ever tried
to do telephone support you would appreciate the difficulty
of getting a non-technical person to first of all find the
log files and then to extract the correct lines. It therefore
follows that if the 1 or 4 bit mode is useful info for the 
VUB300 support staff, then this read-only interface is the
appropriate single place for it to be provided. The overhead
of providing such an interface is negligible and the existance
of such an interface demonstrates that linux is no longer
designed only for geeks.

Tony Olech





^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH] mmc: USB SDIO/SD/MMC Host Controller (VUB300) driver Re-Resubmission
  2011-04-19  9:05                   ` Tony Olech
@ 2011-04-19 12:10                     ` Arnd Bergmann
  2011-04-19 12:32                       ` Tony Olech
  0 siblings, 1 reply; 41+ messages in thread
From: Arnd Bergmann @ 2011-04-19 12:10 UTC (permalink / raw)
  To: Tony Olech; +Cc: Chris Ball, linux-mmc

On Tuesday 19 April 2011, Tony Olech wrote:
> The purpose of this read-only interface is for VUB300 support
> staff to obtain information from our customers. Our customers
> are not like the people on this list. If you have ever tried
> to do telephone support you would appreciate the difficulty
> of getting a non-technical person to first of all find the
> log files and then to extract the correct lines. It therefore
> follows that if the 1 or 4 bit mode is useful info for the 
> VUB300 support staff, then this read-only interface is the
> appropriate single place for it to be provided. The overhead
> of providing such an interface is negligible and the existance
> of such an interface demonstrates that linux is no longer
> designed only for geeks.

Wrong answer.

You are missing the point. Sysfs is not the place where you can
put random interfaces you want for you own needs. By adding
files there, you create a support burden for everyone who now
has to maintain backwards-compatibility.

You cannot seriously ask for inclusion of a kernel interface
just to fit the use of your support team at the expense of
everyone else.

Please remove the sysfs attributes from the driver before resubmitting.
We can have a separate discussion about core interfaces that will
help everyone and that are consistent with how Linux kernels are
supported.

	Arnd

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH] mmc: USB SDIO/SD/MMC Host Controller (VUB300) driver Re-Resubmission
  2011-04-19 12:10                     ` Arnd Bergmann
@ 2011-04-19 12:32                       ` Tony Olech
  2011-04-19 13:21                         ` Arnd Bergmann
  0 siblings, 1 reply; 41+ messages in thread
From: Tony Olech @ 2011-04-19 12:32 UTC (permalink / raw)
  To: Arnd Bergmann; +Cc: Chris Ball, linux-mmc

On Tue, 2011-04-19 at 14:10 +0200, Arnd Bergmann wrote:
> On Tuesday 19 April 2011, Tony Olech wrote:
> > The purpose of this read-only interface is for VUB300 support
> > staff to obtain information from our customers. Our customers
> > are not like the people on this list. If you have ever tried
> > to do telephone support you would appreciate the difficulty
> > of getting a non-technical person to first of all find the
> > log files and then to extract the correct lines. It therefore
> > follows that if the 1 or 4 bit mode is useful info for the 
> > VUB300 support staff, then this read-only interface is the
> > appropriate single place for it to be provided. The overhead
> > of providing such an interface is negligible and the existance
> > of such an interface demonstrates that linux is no longer
> > designed only for geeks.
> 
> Wrong answer.
> 
> You are missing the point. Sysfs is not the place where you can
> put random interfaces you want for you own needs. By adding
> files there, you create a support burden for everyone who now
> has to maintain backwards-compatibility.
> 
> You cannot seriously ask for inclusion of a kernel interface
> just to fit the use of your support team at the expense of
> everyone else.
> 
> Please remove the sysfs attributes from the driver before resubmitting.
> We can have a separate discussion about core interfaces that will
> help everyone and that are consistent with how Linux kernels are
> supported.
> 
> 	Arnd

You have not answered my points. If linux is to move from
the preserve of geeks, then support is an issue.

What is wrong in wanting to support non-technical users?
Especially since there are no backward compatible issues
involved at all.

Tony



^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH] mmc: USB SDIO/SD/MMC Host Controller (VUB300) driver Re-Resubmission
  2011-04-19 12:32                       ` Tony Olech
@ 2011-04-19 13:21                         ` Arnd Bergmann
  0 siblings, 0 replies; 41+ messages in thread
From: Arnd Bergmann @ 2011-04-19 13:21 UTC (permalink / raw)
  To: Tony Olech; +Cc: Chris Ball, linux-mmc

On Tuesday 19 April 2011, Tony Olech wrote:
> What is wrong in wanting to support non-technical users?
> Especially since there are no backward compatible issues
> involved at all.

It's good to support non-technical users. Things that are
bad about your patch are:

* Have interfaces that work only for your customers. If a
  specific interface is a good thing to have, it certainly
  is good to have it for everyone.

* Fragmentation between drivers. In addition, if someone
  else needs a similar interface and introduces it in another
  driver, we can end up with incompatible ways of getting
  the same information, which is an absolute support nightmare
  for people that want to support all users.

* Not following the interface standards. You don't get to
  make the decision which interfaces are helpful and which
  ones are not, they have to be agreed upon. The files you were
  suggesting violate the rules about how they should look
  like, in particular having one value per file. Doing something
  else is very confusing to users that have reasonable expectations
  about the contents.

So even though you had the intentions to make support easier
for some people, you end up with a situation that makes it harder
to support for everyone.

	Arnd

^ permalink raw reply	[flat|nested] 41+ messages in thread

end of thread, other threads:[~2011-04-19 13:22 UTC | newest]

Thread overview: 41+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
     [not found] <20B0EAA71DD7413A9A29D0493C6C1D87@AN00536>
     [not found] ` <20101116150022.GA27726@void.printf.net>
     [not found]   ` <27884BED0E3C489C8849EE12A803F536@AN00536>
     [not found]     ` <m3oc9pnt6g.fsf@pullcord.laptop.org>
     [not found]       ` <4CE41BE3.1060806@elandigitalsystems.com>
     [not found]         ` <m3oc9n241c.fsf@pullcord.laptop.org>
2010-11-22 15:05           ` [PATCH] mmc: USB SDIO/SD/MMC Host Controller (VUB300) driver Tony Olech
2010-11-30  6:15             ` Chris Ball
2010-11-30 12:23               ` David Vrabel
2010-12-17  0:43                 ` Chris Ball
2010-12-21 15:03                 ` Tony Olech
2011-01-06  4:56                   ` Chris Ball
2011-01-06 13:18                     ` David Vrabel
2011-01-06 13:17             ` David Vrabel
2011-01-20 16:09               ` Tony Olech
2011-01-20 16:11               ` Tony Olech
2011-01-21 10:50             ` [PATCH] mmc: USB SDIO/SD/MMC Host Controller (VUB300) driver Resubmission Tony Olech
2011-01-21 21:14               ` Nicolas Pitre
2011-01-22 14:21                 ` Wolfram Sang
2011-01-22 19:07                   ` Nicolas Pitre
2011-01-23 10:09                     ` Wolfram Sang
2011-01-23 14:01                       ` Nicolas Pitre
2011-01-24 15:35                         ` Wolfram Sang
2011-01-24 16:27                         ` Tony Olech
2011-01-24 16:21                       ` Tony Olech
2011-01-25  9:13                         ` Wolfram Sang
2011-01-25  9:35                           ` Tony Olech
2011-01-25 20:40                             ` Nicolas Pitre
2011-01-24 16:17                     ` Tony Olech
2011-01-24  8:49                 ` Tony Olech
2011-01-24 14:43                   ` Nicolas Pitre
2011-01-24 15:10                     ` Tony Olech
2011-01-24 15:55                       ` Nicolas Pitre
2011-01-24 16:08                         ` Tony Olech
2011-01-24 16:28                           ` Nicolas Pitre
2011-01-24 16:43                             ` Tony Olech
2011-03-10 16:13               ` [PATCH] mmc: USB SDIO/SD/MMC Host Controller (VUB300) driver Re-Resubmission Tony Olech
2011-03-15  3:01                 ` Chris Ball
2011-03-15  9:40                   ` Wolfram Sang
2011-03-15 15:06                     ` Chris Ball
2011-03-15 15:41                       ` Arnd Bergmann
2011-03-15 16:23                 ` Arnd Bergmann
2011-03-15 16:55                   ` Tony Olech
2011-04-19  9:05                   ` Tony Olech
2011-04-19 12:10                     ` Arnd Bergmann
2011-04-19 12:32                       ` Tony Olech
2011-04-19 13:21                         ` Arnd Bergmann

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.