aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/usb/Makefile9
-rw-r--r--drivers/usb/misc/Kconfig24
-rw-r--r--drivers/usb/misc/Makefile2
-rw-r--r--drivers/usb/misc/ftdi-elan.c2809
-rw-r--r--drivers/usb/misc/usb_u132.h97
5 files changed, 2936 insertions, 5 deletions
diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile
index 46946b2d308..e3364705709 100644
--- a/drivers/usb/Makefile
+++ b/drivers/usb/Makefile
@@ -48,23 +48,24 @@ obj-$(CONFIG_USB_MICROTEK) += image/
obj-$(CONFIG_USB_SERIAL) += serial/
+obj-$(CONFIG_USB_ADUTUX) += misc/
+obj-$(CONFIG_USB_APPLEDISPLAY) += misc/
obj-$(CONFIG_USB_AUERSWALD) += misc/
obj-$(CONFIG_USB_CYPRESS_CY7C63)+= misc/
obj-$(CONFIG_USB_CYTHERM) += misc/
obj-$(CONFIG_USB_EMI26) += misc/
obj-$(CONFIG_USB_EMI62) += misc/
+obj-$(CONFIG_USB_FTDI_ELAN) += misc/
obj-$(CONFIG_USB_IDMOUSE) += misc/
obj-$(CONFIG_USB_LCD) += misc/
obj-$(CONFIG_USB_LD) += misc/
obj-$(CONFIG_USB_LED) += misc/
obj-$(CONFIG_USB_LEGOTOWER) += misc/
+obj-$(CONFIG_USB_PHIDGETSERVO) += misc/
obj-$(CONFIG_USB_RIO500) += misc/
+obj-$(CONFIG_USB_SISUSBVGA) += misc/
obj-$(CONFIG_USB_TEST) += misc/
obj-$(CONFIG_USB_USS720) += misc/
-obj-$(CONFIG_USB_PHIDGETSERVO) += misc/
-obj-$(CONFIG_USB_SISUSBVGA) += misc/
-obj-$(CONFIG_USB_APPLEDISPLAY) += misc/
-obj-$(CONFIG_USB_ADUTUX) += misc/
obj-$(CONFIG_USB_ATM) += atm/
obj-$(CONFIG_USB_SPEEDTOUCH) += atm/
diff --git a/drivers/usb/misc/Kconfig b/drivers/usb/misc/Kconfig
index 288d301d2bf..c29658f69e2 100644
--- a/drivers/usb/misc/Kconfig
+++ b/drivers/usb/misc/Kconfig
@@ -178,6 +178,30 @@ config USB_IDMOUSE
See also <http://www.fs.tum.de/~echtler/idmouse/>.
+config USB_FTDI_ELAN
+ tristate "Elan PCMCIA CardBus Adapter USB Client"
+ depends on USB
+ default M
+ help
+ ELAN's Uxxx series of adapters are USB to PCMCIA CardBus adapters.
+ Currently only the U132 adapter is available.
+
+ The U132 is specifically designed for CardBus PC cards that contain
+ an OHCI host controller. Typical PC cards are the Orange Mobile 3G
+ Option GlobeTrotter Fusion card. The U132 adapter will *NOT* work
+ with PC cards that do not contain an OHCI controller. To use a U132
+ adapter you will need this "ftdi-elan" module as well as the "u132-hcd"
+ module which is a USB host controller driver that talks to the OHCI
+ controller within CardBus card that are inserted in the U132 adapter.
+
+ This driver has been tested with a CardBus OHCI USB adapter, and
+ worked with a USB PEN Drive inserted into the first USB port of
+ the PCCARD. A rather pointless thing to do, but useful for testing.
+
+ See also the USB_U132_HCD entry "Elan U132 Adapter Host Controller"
+
+ It is safe to say M here.
+
config USB_APPLEDISPLAY
tristate "Apple Cinema Display support"
depends on USB
diff --git a/drivers/usb/misc/Makefile b/drivers/usb/misc/Makefile
index 73fc8be0d8c..2be70fa259b 100644
--- a/drivers/usb/misc/Makefile
+++ b/drivers/usb/misc/Makefile
@@ -9,6 +9,7 @@ obj-$(CONFIG_USB_CYPRESS_CY7C63)+= cypress_cy7c63.o
obj-$(CONFIG_USB_CYTHERM) += cytherm.o
obj-$(CONFIG_USB_EMI26) += emi26.o
obj-$(CONFIG_USB_EMI62) += emi62.o
+obj-$(CONFIG_USB_FTDI_ELAN) += ftdi-elan.o
obj-$(CONFIG_USB_IDMOUSE) += idmouse.o
obj-$(CONFIG_USB_LCD) += usblcd.o
obj-$(CONFIG_USB_LD) += ldusb.o
@@ -21,7 +22,6 @@ obj-$(CONFIG_USB_PHIDGETSERVO) += phidgetservo.o
obj-$(CONFIG_USB_RIO500) += rio500.o
obj-$(CONFIG_USB_TEST) += usbtest.o
obj-$(CONFIG_USB_USS720) += uss720.o
-obj-$(CONFIG_USB_APPLEDISPLAY) += appledisplay.o
obj-$(CONFIG_USB_SISUSBVGA) += sisusbvga/
diff --git a/drivers/usb/misc/ftdi-elan.c b/drivers/usb/misc/ftdi-elan.c
new file mode 100644
index 00000000000..b88a09497c2
--- /dev/null
+++ b/drivers/usb/misc/ftdi-elan.c
@@ -0,0 +1,2809 @@
+/*
+* USB FTDI client driver for Elan Digital Systems's Uxxx adapters
+*
+* Copyright(C) 2006 Elan Digital Systems Limited
+* http://www.elandigitalsystems.com
+*
+* Author and Maintainer - Tony Olech - Elan Digital Systems
+* tony.olech@elandigitalsystems.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.
+*
+*
+* This driver was written by Tony Olech(tony.olech@elandigitalsystems.com)
+* based on various USB client drivers in the 2.6.15 linux kernel
+* with constant reference to the 3rd Edition of Linux Device Drivers
+* published by O'Reilly
+*
+* The U132 adapter is a USB to CardBus adapter specifically designed
+* for PC cards that contain an OHCI host controller. Typical PC cards
+* are the Orange Mobile 3G Option GlobeTrotter Fusion card.
+*
+* The U132 adapter will *NOT *work with PC cards that do not contain
+* an OHCI controller. A simple way to test whether a PC card has an
+* OHCI controller as an interface is to insert the PC card directly
+* into a laptop(or desktop) with a CardBus slot and if "lspci" shows
+* a new USB controller and "lsusb -v" shows a new OHCI Host Controller
+* then there is a good chance that the U132 adapter will support the
+* PC card.(you also need the specific client driver for the PC card)
+*
+* Please inform the Author and Maintainer about any PC cards that
+* contain OHCI Host Controller and work when directly connected to
+* an embedded CardBus slot but do not work when they are connected
+* via an ELAN U132 adapter.
+*
+*/
+#include <linux/config.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/ioctl.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/kref.h>
+#include <asm/uaccess.h>
+#include <linux/usb.h>
+#include <linux/workqueue.h>
+#include <linux/platform_device.h>
+MODULE_AUTHOR("Tony Olech");
+MODULE_DESCRIPTION("FTDI ELAN driver");
+MODULE_LICENSE("GPL");
+#define INT_MODULE_PARM(n, v) static int n = v;module_param(n, int, 0444)
+extern struct platform_driver u132_platform_driver;
+static struct workqueue_struct *status_queue;
+static struct workqueue_struct *command_queue;
+static struct workqueue_struct *respond_queue;
+/*
+* ftdi_module_lock exists to protect access to global variables
+*
+*/
+static struct semaphore ftdi_module_lock;
+static int ftdi_instances = 0;
+static struct list_head ftdi_static_list;
+/*
+* end of the global variables protected by ftdi_module_lock
+*/
+#include "usb_u132.h"
+#define TD_DEVNOTRESP 5
+/* Define these values to match your devices*/
+#define USB_FTDI_ELAN_VENDOR_ID 0x0403
+#define USB_FTDI_ELAN_PRODUCT_ID 0xd6ea
+/* table of devices that work with this driver*/
+static struct usb_device_id ftdi_elan_table[] = {
+ {USB_DEVICE(USB_FTDI_ELAN_VENDOR_ID, USB_FTDI_ELAN_PRODUCT_ID)},
+ { /* Terminating entry */ }
+};
+
+MODULE_DEVICE_TABLE(usb, ftdi_elan_table);
+/* only the jtag(firmware upgrade device) interface requires
+* a device file and corresponding minor number, but the
+* interface is created unconditionally - I suppose it could
+* be configured or not according to a module parameter.
+* But since we(now) require one interface per device,
+* and since it unlikely that a normal installation would
+* require more than a couple of elan-ftdi devices, 8 seems
+* like a reasonable limit to have here, and if someone
+* really requires more than 8 devices, then they can frig the
+* code and recompile
+*/
+#define USB_FTDI_ELAN_MINOR_BASE 192
+#define COMMAND_BITS 5
+#define COMMAND_SIZE (1<<COMMAND_BITS)
+#define COMMAND_MASK (COMMAND_SIZE-1)
+struct u132_command {
+ u8 header;
+ u16 length;
+ u8 address;
+ u8 width;
+ u32 value;
+ int follows;
+ void *buffer;
+};
+#define RESPOND_BITS 5
+#define RESPOND_SIZE (1<<RESPOND_BITS)
+#define RESPOND_MASK (RESPOND_SIZE-1)
+struct u132_respond {
+ u8 header;
+ u8 address;
+ u32 *value;
+ int *result;
+ struct completion wait_completion;
+};
+struct u132_target {
+ void *endp;
+ struct urb *urb;
+ int toggle_bits;
+ int error_count;
+ int condition_code;
+ int repeat_number;
+ int halted;
+ int skipped;
+ int actual;
+ int non_null;
+ int active;
+ int abandoning;
+ void (*callback) (void *endp, struct urb *urb, u8 *buf, int len,
+ int toggle_bits, int error_count, int condition_code,
+ int repeat_number, int halted, int skipped, int actual,
+ int non_null);
+};
+/* Structure to hold all of our device specific stuff*/
+struct usb_ftdi {
+ struct list_head ftdi_list;
+ struct semaphore u132_lock;
+ int command_next;
+ int command_head;
+ struct u132_command command[COMMAND_SIZE];
+ int respond_next;
+ int respond_head;
+ struct u132_respond respond[RESPOND_SIZE];
+ struct u132_target target[4];
+ char device_name[16];
+ unsigned synchronized:1;
+ unsigned enumerated:1;
+ unsigned registered:1;
+ unsigned initialized:1;
+ unsigned card_ejected:1;
+ int function;
+ int sequence_num;
+ int disconnected;
+ int gone_away;
+ int stuck_status;
+ int status_queue_delay;
+ struct semaphore sw_lock;
+ struct usb_device *udev;
+ struct usb_interface *interface;
+ struct usb_class_driver *class;
+ struct work_struct status_work;
+ struct work_struct command_work;
+ struct work_struct respond_work;
+ struct u132_platform_data platform_data;
+ struct resource resources[0];
+ struct platform_device platform_dev;
+ unsigned char *bulk_in_buffer;
+ size_t bulk_in_size;
+ size_t bulk_in_last;
+ size_t bulk_in_left;
+ __u8 bulk_in_endpointAddr;
+ __u8 bulk_out_endpointAddr;
+ struct kref kref;
+ u32 controlreg;
+ u8 response[4 + 1024];
+ int expected;
+ int recieved;
+ int ed_found;
+};
+#define kref_to_usb_ftdi(d) container_of(d, struct usb_ftdi, kref)
+#define platform_device_to_usb_ftdi(d) container_of(d, struct usb_ftdi, \
+ platform_dev)
+static struct usb_driver ftdi_elan_driver;
+static void ftdi_elan_delete(struct kref *kref)
+{
+ struct usb_ftdi *ftdi = kref_to_usb_ftdi(kref);
+ dev_warn(&ftdi->udev->dev, "FREEING ftdi=%p\n", ftdi);
+ usb_put_dev(ftdi->udev);
+ ftdi->disconnected += 1;
+ down(&ftdi_module_lock);
+ list_del_init(&ftdi->ftdi_list);
+ ftdi_instances -= 1;
+ up(&ftdi_module_lock);
+ kfree(ftdi->bulk_in_buffer);
+ ftdi->bulk_in_buffer = NULL;
+}
+
+static void ftdi_elan_put_kref(struct usb_ftdi *ftdi)
+{
+ kref_put(&ftdi->kref, ftdi_elan_delete);
+}
+
+static void ftdi_elan_get_kref(struct usb_ftdi *ftdi)
+{
+ kref_get(&ftdi->kref);
+}
+
+static void ftdi_elan_init_kref(struct usb_ftdi *ftdi)
+{
+ kref_init(&ftdi->kref);
+}
+
+static void ftdi_status_requeue_work(struct usb_ftdi *ftdi, unsigned int delta)
+{
+ if (delta > 0) {
+ if (queue_delayed_work(status_queue, &ftdi->status_work, delta))
+ return;
+ } else if (queue_work(status_queue, &ftdi->status_work))
+ return;
+ kref_put(&ftdi->kref, ftdi_elan_delete);
+ return;
+}
+
+static void ftdi_status_queue_work(struct usb_ftdi *ftdi, unsigned int delta)
+{
+ if (delta > 0) {
+ if (queue_delayed_work(status_queue, &ftdi->status_work, delta))
+ kref_get(&ftdi->kref);
+ } else if (queue_work(status_queue, &ftdi->status_work))
+ kref_get(&ftdi->kref);
+ return;
+}
+
+static void ftdi_status_cancel_work(struct usb_ftdi *ftdi)
+{
+ if (cancel_delayed_work(&ftdi->status_work))
+ kref_put(&ftdi->kref, ftdi_elan_delete);
+}
+
+static void ftdi_command_requeue_work(struct usb_ftdi *ftdi, unsigned int delta)
+{
+ if (delta > 0) {
+ if (queue_delayed_work(command_queue, &ftdi->command_work,
+ delta))
+ return;
+ } else if (queue_work(command_queue, &ftdi->command_work))
+ return;
+ kref_put(&ftdi->kref, ftdi_elan_delete);
+ return;
+}
+
+static void ftdi_command_queue_work(struct usb_ftdi *ftdi, unsigned int delta)
+{
+ if (delta > 0) {
+ if (queue_delayed_work(command_queue, &ftdi->command_work,
+ delta))
+ kref_get(&ftdi->kref);
+ } else if (queue_work(command_queue, &ftdi->command_work))
+ kref_get(&ftdi->kref);
+ return;
+}
+
+static void ftdi_command_cancel_work(struct usb_ftdi *ftdi)
+{
+ if (cancel_delayed_work(&ftdi->command_work))
+ kref_put(&ftdi->kref, ftdi_elan_delete);
+}
+
+static void ftdi_response_requeue_work(struct usb_ftdi *ftdi,
+ unsigned int delta)
+{
+ if (delta > 0) {
+ if (queue_delayed_work(respond_queue, &ftdi->respond_work,
+ delta))
+ return;
+ } else if (queue_work(respond_queue, &ftdi->respond_work))
+ return;
+ kref_put(&ftdi->kref, ftdi_elan_delete);
+ return;
+}
+
+static void ftdi_respond_queue_work(struct usb_ftdi *ftdi, unsigned int delta)
+{
+ if (delta > 0) {
+ if (queue_delayed_work(respond_queue, &ftdi->respond_work,
+ delta))
+ kref_get(&ftdi->kref);
+ } else if (queue_work(respond_queue, &ftdi->respond_work))
+ kref_get(&ftdi->kref);
+ return;
+}
+
+static void ftdi_response_cancel_work(struct usb_ftdi *ftdi)
+{
+ if (cancel_delayed_work(&ftdi->respond_work))
+ kref_put(&ftdi->kref, ftdi_elan_delete);
+}
+
+void ftdi_elan_gone_away(struct platform_device *pdev)
+{
+ struct usb_ftdi *ftdi = platform_device_to_usb_ftdi(pdev);
+ ftdi->gone_away += 1;
+ ftdi_elan_put_kref(ftdi);
+}
+
+
+EXPORT_SYMBOL_GPL(ftdi_elan_gone_away);
+void ftdi_release_platform_dev(struct device *dev)
+{
+ dev->parent = NULL;
+}
+
+static void ftdi_elan_do_callback(struct usb_ftdi *ftdi,
+ struct u132_target *target, u8 *buffer, int length);
+static void ftdi_elan_kick_command_queue(struct usb_ftdi *ftdi);
+static void ftdi_elan_kick_respond_queue(struct usb_ftdi *ftdi);
+static int ftdi_elan_setupOHCI(struct usb_ftdi *ftdi);
+static int ftdi_elan_checkingPCI(struct usb_ftdi *ftdi);
+static int ftdi_elan_enumeratePCI(struct usb_ftdi *ftdi);
+static int ftdi_elan_synchronize(struct usb_ftdi *ftdi);
+static int ftdi_elan_stuck_waiting(struct usb_ftdi *ftdi);
+static int ftdi_elan_command_engine(struct usb_ftdi *ftdi);
+static int ftdi_elan_respond_engine(struct usb_ftdi *ftdi);
+static int ftdi_elan_hcd_init(struct usb_ftdi *ftdi)
+{
+ int result;
+ if (ftdi->platform_dev.dev.parent)
+ return -EBUSY;
+ ftdi_elan_get_kref(ftdi);
+ ftdi->platform_data.potpg = 100;
+ ftdi->platform_data.reset = NULL;
+ ftdi->platform_dev.id = ftdi->sequence_num;
+ ftdi->platform_dev.resource = ftdi->resources;
+ ftdi->platform_dev.num_resources = ARRAY_SIZE(ftdi->resources);
+ ftdi->platform_dev.dev.platform_data = &ftdi->platform_data;
+ ftdi->platform_dev.dev.parent = NULL;
+ ftdi->platform_dev.dev.release = ftdi_release_platform_dev;
+ ftdi->platform_dev.dev.dma_mask = NULL;
+ snprintf(ftdi->device_name, sizeof(ftdi->device_name), "u132_hcd");
+ ftdi->platform_dev.name = ftdi->device_name;
+ dev_info(&ftdi->udev->dev, "requesting module '%s'\n", "u132_hcd");
+ request_module("u132_hcd");
+ dev_info(&ftdi->udev->dev, "registering '%s'\n",
+ ftdi->platform_dev.name);
+ result = platform_device_register(&ftdi->platform_dev);
+ return result;
+}
+
+static void ftdi_elan_abandon_completions(struct usb_ftdi *ftdi)
+{
+ down(&ftdi->u132_lock);
+ while (ftdi->respond_next > ftdi->respond_head) {
+ struct u132_respond *respond = &ftdi->respond[RESPOND_MASK &
+ ftdi->respond_head++];
+ *respond->result = -ESHUTDOWN;
+ *respond->value = 0;
+ complete(&respond->wait_completion);
+ } up(&ftdi->u132_lock);
+}
+
+static void ftdi_elan_abandon_targets(struct usb_ftdi *ftdi)
+{
+ int ed_number = 4;
+ down(&ftdi->u132_lock);
+ while (ed_number-- > 0) {
+ struct u132_target *target = &ftdi->target[ed_number];
+ if (target->active == 1) {
+ target->condition_code = TD_DEVNOTRESP;
+ up(&ftdi->u132_lock);
+ ftdi_elan_do_callback(ftdi, target, NULL, 0);
+ down(&ftdi->u132_lock);
+ }
+ }
+ ftdi->recieved = 0;
+ ftdi->expected = 4;
+ ftdi->ed_found = 0;
+ up(&ftdi->u132_lock);
+}
+
+static void ftdi_elan_flush_targets(struct usb_ftdi *ftdi)
+{
+ int ed_number = 4;
+ down(&ftdi->u132_lock);
+ while (ed_number-- > 0) {
+ struct u132_target *target = &ftdi->target[ed_number];
+ target->abandoning = 1;
+ wait_1:if (target->active == 1) {
+ int command_size = ftdi->command_next -
+ ftdi->command_head;
+ if (command_size < COMMAND_SIZE) {
+ struct u132_command *command = &ftdi->command[
+ COMMAND_MASK & ftdi->command_next];
+ command->header = 0x80 | (ed_number << 5) | 0x4;
+ command->length = 0x00;
+ command->address = 0x00;
+ command->width = 0x00;
+ command->follows = 0;
+ command->value = 0;
+ command->buffer = &command->value;
+ ftdi->command_next += 1;
+ ftdi_elan_kick_command_queue(ftdi);
+ } else {
+ up(&ftdi->u132_lock);
+ msleep(100);
+ down(&ftdi->u132_lock);
+ goto wait_1;
+ }
+ }
+ wait_2:if (target->active == 1) {
+ int command_size = ftdi->command_next -
+ ftdi->command_head;
+ if (command_size < COMMAND_SIZE) {
+ struct u132_command *command = &ftdi->command[
+ COMMAND_MASK & ftdi->command_next];
+ command->header = 0x90 | (ed_number << 5);
+ command->length = 0x00;
+ command->address = 0x00;
+ command->width = 0x00;
+ command->follows = 0;
+ command->value = 0;
+ command->buffer = &command->value;
+ ftdi->command_next += 1;
+ ftdi_elan_kick_command_queue(ftdi);
+ } else {
+ up(&ftdi->u132_lock);
+ msleep(100);
+ down(&ftdi->u132_lock);
+ goto wait_2;
+ }
+ }
+ }
+ ftdi->recieved = 0;
+ ftdi->expected = 4;
+ ftdi->ed_found = 0;
+ up(&ftdi->u132_lock);
+}
+
+static void ftdi_elan_cancel_targets(struct usb_ftdi *ftdi)
+{
+ int ed_number = 4;
+ down(&ftdi->u132_lock);
+ while (ed_number-- > 0) {
+ struct u132_target *target = &ftdi->target[ed_number];
+ target->abandoning = 1;
+ wait:if (target->active == 1) {
+ int command_size = ftdi->command_next -
+ ftdi->command_head;
+ if (command_size < COMMAND_SIZE) {
+ struct u132_command *command = &ftdi->command[
+ COMMAND_MASK & ftdi->command_next];
+ command->header = 0x80 | (ed_number << 5) | 0x4;
+ command->length = 0x00;
+ command->address = 0x00;
+ command->width = 0x00;
+ command->follows = 0;
+ command->value = 0;
+ command->buffer = &command->value;
+ ftdi->command_next += 1;
+ ftdi_elan_kick_command_queue(ftdi);
+ } else {
+ up(&ftdi->u132_lock);
+ msleep(100);
+ down(&ftdi->u132_lock);
+ goto wait;
+ }
+ }
+ }
+ ftdi->recieved = 0;
+ ftdi->expected = 4;
+ ftdi->ed_found = 0;
+ up(&ftdi->u132_lock);
+}
+
+static void ftdi_elan_kick_command_queue(struct usb_ftdi *ftdi)
+{
+ ftdi_command_queue_work(ftdi, 0);
+ return;
+}
+
+static void ftdi_elan_command_work(void *data)
+{
+ struct usb_ftdi *ftdi = data;
+ if (ftdi->disconnected > 0) {
+ ftdi_elan_put_kref(ftdi);
+ return;
+ } else {
+ int retval = ftdi_elan_command_engine(ftdi);
+ if (retval == -ESHUTDOWN) {
+ ftdi->disconnected += 1;
+ } else if (retval == -ENODEV) {
+ ftdi->disconnected += 1;
+ } else if (retval)
+ dev_err(&ftdi->udev->dev, "command error %d\n", retval);
+ ftdi_command_requeue_work(ftdi, msecs_to_jiffies(10));
+ return;
+ }
+}
+
+static void ftdi_elan_kick_respond_queue(struct usb_ftdi *ftdi)
+{
+ ftdi_respond_queue_work(ftdi, 0);
+ return;
+}
+
+static void ftdi_elan_respond_work(void *data)
+{
+ struct usb_ftdi *ftdi = data;
+ if (ftdi->disconnected > 0) {
+ ftdi_elan_put_kref(ftdi);
+ return;
+ } else {
+ int retval = ftdi_elan_respond_engine(ftdi);
+ if (retval == 0) {
+ } else if (retval == -ESHUTDOWN) {
+ ftdi->disconnected += 1;
+ } else if (retval == -ENODEV) {
+ ftdi->disconnected += 1;
+ } else if (retval == -ENODEV) {
+ ftdi->disconnected += 1;
+ } else if (retval == -EILSEQ) {
+ ftdi->disconnected += 1;
+ } else {
+ ftdi->disconnected += 1;
+ dev_err(&ftdi->udev->dev, "respond error %d\n", retval);
+ }
+ if (ftdi->disconnected > 0) {
+ ftdi_elan_abandon_completions(ftdi);
+ ftdi_elan_abandon_targets(ftdi);
+ }
+ ftdi_response_requeue_work(ftdi, msecs_to_jiffies(10));
+ return;
+ }
+}
+
+
+/*
+* the sw_lock is initially held and will be freed
+* after the FTDI has been synchronized
+*
+*/
+static void ftdi_elan_status_work(void *data)
+{
+ struct usb_ftdi *ftdi = data;
+ int work_delay_in_msec = 0;
+ if (ftdi->disconnected > 0) {
+ ftdi_elan_put_kref(ftdi);
+ return;
+ } else if (ftdi->synchronized == 0) {
+ down(&ftdi->sw_lock);
+ if (ftdi_elan_synchronize(ftdi) == 0) {
+ ftdi->synchronized = 1;
+ ftdi_command_queue_work(ftdi, 1);
+ ftdi_respond_queue_work(ftdi, 1);
+ up(&ftdi->sw_lock);
+ work_delay_in_msec = 100;
+ } else {
+ dev_err(&ftdi->udev->dev, "synchronize failed\n");
+ up(&ftdi->sw_lock);
+ work_delay_in_msec = 10 *1000;
+ }
+ } else if (ftdi->stuck_status > 0) {
+ if (ftdi_elan_stuck_waiting(ftdi) == 0) {
+ ftdi->stuck_status = 0;
+ ftdi->synchronized = 0;
+ } else if ((ftdi->stuck_status++ % 60) == 1) {
+ dev_err(&ftdi->udev->dev, "WRONG type of card inserted "
+ "- please remove\n");
+ } else
+ dev_err(&ftdi->udev->dev, "WRONG type of card inserted "
+ "- checked %d times\n", ftdi->stuck_status);
+ work_delay_in_msec = 100;
+ } else if (ftdi->enumerated == 0) {
+ if (ftdi_elan_enumeratePCI(ftdi) == 0) {
+ ftdi->enumerated = 1;
+ work_delay_in_msec = 250;
+ } else
+ work_delay_in_msec = 1000;
+ } else if (ftdi->initialized == 0) {
+ if (ftdi_elan_setupOHCI(ftdi) == 0) {
+ ftdi->initialized = 1;
+ work_delay_in_msec = 500;
+ } else {
+ dev_err(&ftdi->udev->dev, "initialized failed - trying "
+ "again in 10 seconds\n");
+ work_delay_in_msec = 10 *1000;
+ }
+ } else if (ftdi->registered == 0) {
+ work_delay_in_msec = 10;
+ if (ftdi_elan_hcd_init(ftdi) == 0) {
+ ftdi->registered = 1;
+ } else
+ dev_err(&ftdi->udev->dev, "register failed\n");
+ work_delay_in_msec = 250;
+ } else {
+ if (ftdi_elan_checkingPCI(ftdi) == 0) {
+ work_delay_in_msec = 250;
+ } else if (ftdi->controlreg & 0x00400000) {
+ if (ftdi->gone_away > 0) {
+ dev_err(&ftdi->udev->dev, "PCI device eject con"
+ "firmed platform_dev.dev.parent=%p plat"
+ "form_dev.dev=%p\n",
+ ftdi->platform_dev.dev.parent,
+ &ftdi->platform_dev.dev);
+ platform_device_unregister(&ftdi->platform_dev);
+ ftdi->platform_dev.dev.parent = NULL;
+ ftdi->registered = 0;
+ ftdi->enumerated = 0;
+ ftdi->card_ejected = 0;
+ ftdi->initialized = 0;
+ ftdi->gone_away = 0;
+ } else
+ ftdi_elan_flush_targets(ftdi);
+ work_delay_in_msec = 250;
+ } else {
+ dev_err(&ftdi->udev->dev, "PCI device has disappeared\n"
+ );
+ ftdi_elan_cancel_targets(ftdi);
+ work_delay_in_msec = 500;
+ ftdi->enumerated = 0;
+ ftdi->initialized = 0;
+ }
+ }
+ if (ftdi->disconnected > 0) {
+ ftdi_elan_put_kref(ftdi);
+ return;
+ } else {
+ ftdi_status_requeue_work(ftdi,
+ msecs_to_jiffies(work_delay_in_msec));
+ return;
+ }
+}
+
+
+/*
+* file_operations for the jtag interface
+*
+* the usage count for the device is incremented on open()
+* and decremented on release()
+*/
+static int ftdi_elan_open(struct inode *inode, struct file *file)
+{
+ int subminor = iminor(inode);
+ struct usb_interface *interface = usb_find_interface(&ftdi_elan_driver,
+ subminor);
+ if (!interface) {
+ printk(KERN_ERR "can't find device for minor %d\n", subminor);
+ return -ENODEV;
+ } else {
+ struct usb_ftdi *ftdi = usb_get_intfdata(interface);
+ if (!ftdi) {
+ return -ENODEV;
+ } else {
+ if (down_interruptible(&ftdi->sw_lock)) {
+ return -EINTR;
+ } else {
+ ftdi_elan_get_kref(ftdi);
+ file->private_data = ftdi;
+ return 0;
+ }
+ }
+ }
+}
+
+static int ftdi_elan_release(struct inode *inode, struct file *file)
+{
+ struct usb_ftdi *ftdi = (struct usb_ftdi *)file->private_data;
+ if (ftdi == NULL)
+ return -ENODEV;
+ up(&ftdi->sw_lock); /* decrement the count on our device */
+ ftdi_elan_put_kref(ftdi);
+ return 0;
+}
+
+
+#define FTDI_ELAN_IOC_MAGIC 0xA1
+#define FTDI_ELAN_IOCDEBUG _IOC(_IOC_WRITE, FTDI_ELAN_IOC_MAGIC, 1, 132)
+static int ftdi_elan_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ switch (cmd) {
+ case FTDI_ELAN_IOCDEBUG:{
+ char line[132];
+ int size = strncpy_from_user(line,
+ (const char __user *)arg, sizeof(line));
+ if (size < 0) {
+ return -EINVAL;
+ } else {
+ printk(KERN_ERR "TODO: ioctl %s\n", line);
+ return 0;
+ }
+ }
+ default:
+ return -EFAULT;
+ }
+}
+
+
+/*
+*
+* blocking bulk reads are used to get data from the device
+*
+*/
+static ssize_t ftdi_elan_read(struct file *file, char __user *buffer,
+ size_t count, loff_t *ppos)
+{
+ char data[30 *3 + 4];
+ char *d = data;
+ int m = (sizeof(data) - 1) / 3;
+ int bytes_read = 0;
+ int retry_on_empty = 10;
+ int retry_on_timeout = 5;
+ struct usb_ftdi *ftdi = (struct usb_ftdi *)file->private_data;
+ if (ftdi->disconnected > 0) {
+ return -ENODEV;
+ }
+ data[0] = 0;
+ have:if (ftdi->bulk_in_left > 0) {
+ if (count-- > 0) {
+ char *p = ++ftdi->bulk_in_last + ftdi->bulk_in_buffer;
+ ftdi->bulk_in_left -= 1;
+ if (bytes_read < m) {
+ d += sprintf(d, " %02X", 0x000000FF & *p);
+ } else if (bytes_read > m) {
+ } else
+ d += sprintf(d, " ..");
+ if (copy_to_user(buffer++, p, 1)) {
+ return -EFAULT;
+ } else {
+ bytes_read += 1;
+ goto have;
+ }
+ } else
+ return bytes_read;
+ }
+ more:if (count > 0) {
+ int packet_bytes = 0;
+ int retval = usb_bulk_msg(ftdi->udev,
+ usb_rcvbulkpipe(ftdi->udev, ftdi->bulk_in_endpointAddr),
+ ftdi->bulk_in_buffer, ftdi->bulk_in_size,
+ &packet_bytes, msecs_to_jiffies(50));
+ if (packet_bytes > 2) {
+ ftdi->bulk_in_left = packet_bytes - 2;
+ ftdi->bulk_in_last = 1;
+ goto have;
+ } else if (retval == -ETIMEDOUT) {
+ if (retry_on_timeout-- > 0) {
+ goto more;
+ } else if (bytes_read > 0) {
+ return bytes_read;
+ } else
+ return retval;
+ } else if (retval == 0) {
+ if (retry_on_empty-- > 0) {
+ goto more;
+ } else
+ return bytes_read;
+ } else
+ return retval;
+ } else
+ return bytes_read;
+}
+
+static void ftdi_elan_write_bulk_callback(struct urb *urb, struct pt_regs *regs)
+{
+ struct usb_ftdi *ftdi = (struct usb_ftdi *)urb->context;
+ if (urb->status && !(urb->status == -ENOENT || urb->status ==
+ -ECONNRESET || urb->status == -ESHUTDOWN)) {
+ dev_err(&ftdi->udev->dev, "urb=%p write bulk status received: %"
+ "d\n", urb, urb->status);
+ }
+ usb_buffer_free(urb->dev, urb->transfer_buffer_length,
+ urb->transfer_buffer, urb->transfer_dma);
+}
+
+static int fill_buffer_with_all_queued_commands(struct usb_ftdi *ftdi,
+ char *buf, int command_size, int total_size)
+{
+ int ed_commands = 0;
+ int b = 0;
+ int I = command_size;
+ int i = ftdi->command_head;
+ while (I-- > 0) {
+ struct u132_command *command = &ftdi->command[COMMAND_MASK &
+ i++];
+ int F = command->follows;
+ u8 *f = command->buffer;
+ if (command->header & 0x80) {
+ ed_commands |= 1 << (0x3 & (command->header >> 5));
+ }
+ buf[b++] = command->header;
+ buf[b++] = (command->length >> 0) & 0x00FF;
+ buf[b++] = (command->length >> 8) & 0x00FF;
+ buf[b++] = command->address;
+ buf[b++] = command->width;
+ while (F-- > 0) {
+ buf[b++] = *f++;
+ }
+ }
+ return ed_commands;
+}
+
+static int ftdi_elan_total_command_size(struct usb_ftdi *ftdi, int command_size)
+{
+ int total_size = 0;
+ int I = command_size;
+ int i = ftdi->command_head;
+ while (I-- > 0) {
+ struct u132_command *command = &ftdi->command[COMMAND_MASK &
+ i++];
+ total_size += 5 + command->follows;
+ } return total_size;
+}
+
+static int ftdi_elan_command_engine(struct usb_ftdi *ftdi)
+{
+ int retval;
+ char *buf;
+ int ed_commands;
+ int total_size;
+ struct urb *urb;
+ int command_size = ftdi->command_next - ftdi->command_head;
+ if (command_size == 0)
+ return 0;
+ total_size = ftdi_elan_total_command_size(ftdi, command_size);
+ urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!urb) {
+ dev_err(&ftdi->udev->dev, "could not get a urb to write %d comm"
+ "ands totaling %d bytes to the Uxxx\n", command_size,
+ total_size);
+ return -ENOMEM;
+ }
+ buf = usb_buffer_alloc(ftdi->udev, total_size, GFP_KERNEL,
+ &urb->transfer_dma);
+ if (!buf) {
+ dev_err(&ftdi->udev->dev, "could not get a buffer to write %d c"
+ "ommands totaling %d bytes to the Uxxx\n", command_size,
+ total_size);
+ usb_free_urb(urb);
+ return -ENOMEM;
+ }
+ ed_commands = fill_buffer_with_all_queued_commands(ftdi, buf,
+ command_size, total_size);
+ usb_fill_bulk_urb(urb, ftdi->udev, usb_sndbulkpipe(ftdi->udev,
+ ftdi->bulk_out_endpointAddr), buf, total_size,
+ ftdi_elan_write_bulk_callback, ftdi);
+ urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+ if (ed_commands) {
+ char diag[40 *3 + 4];
+ char *d = diag;
+ int m = total_size;
+ u8 *c = buf;
+ int s = (sizeof(diag)