summaryrefslogtreecommitdiffstats
path: root/drivers/usb/misc
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/misc')
-rw-r--r--drivers/usb/misc/Kconfig313
-rw-r--r--drivers/usb/misc/Makefile36
-rw-r--r--drivers/usb/misc/adutux.c796
-rw-r--r--drivers/usb/misc/apple-mfi-fastcharge.c241
-rw-r--r--drivers/usb/misc/appledisplay.c349
-rw-r--r--drivers/usb/misc/brcmstb-usb-pinmap.c355
-rw-r--r--drivers/usb/misc/chaoskey.c584
-rw-r--r--drivers/usb/misc/cypress_cy7c63.c259
-rw-r--r--drivers/usb/misc/cytherm.c357
-rw-r--r--drivers/usb/misc/ehset.c190
-rw-r--r--drivers/usb/misc/emi26.c259
-rw-r--r--drivers/usb/misc/emi62.c272
-rw-r--r--drivers/usb/misc/ezusb.c151
-rw-r--r--drivers/usb/misc/ftdi-elan.c2784
-rw-r--r--drivers/usb/misc/idmouse.c407
-rw-r--r--drivers/usb/misc/iowarrior.c926
-rw-r--r--drivers/usb/misc/isight_firmware.c131
-rw-r--r--drivers/usb/misc/ldusb.c797
-rw-r--r--drivers/usb/misc/legousbtower.c879
-rw-r--r--drivers/usb/misc/lvstest.c476
-rw-r--r--drivers/usb/misc/onboard_usb_hub.c461
-rw-r--r--drivers/usb/misc/onboard_usb_hub.h49
-rw-r--r--drivers/usb/misc/onboard_usb_hub_pdevs.c143
-rw-r--r--drivers/usb/misc/qcom_eud.c251
-rw-r--r--drivers/usb/misc/sisusbvga/Kconfig48
-rw-r--r--drivers/usb/misc/sisusbvga/Makefile9
-rw-r--r--drivers/usb/misc/sisusbvga/sisusb.c3244
-rw-r--r--drivers/usb/misc/sisusbvga/sisusb.h299
-rw-r--r--drivers/usb/misc/sisusbvga/sisusb_con.c1496
-rw-r--r--drivers/usb/misc/sisusbvga/sisusb_init.c955
-rw-r--r--drivers/usb/misc/sisusbvga/sisusb_init.h180
-rw-r--r--drivers/usb/misc/sisusbvga/sisusb_struct.h162
-rw-r--r--drivers/usb/misc/sisusbvga/sisusb_tables.h688
-rw-r--r--drivers/usb/misc/trancevibrator.c128
-rw-r--r--drivers/usb/misc/usb251xb.c769
-rw-r--r--drivers/usb/misc/usb3503.c449
-rw-r--r--drivers/usb/misc/usb4604.c164
-rw-r--r--drivers/usb/misc/usb_u132.h97
-rw-r--r--drivers/usb/misc/usblcd.c450
-rw-r--r--drivers/usb/misc/usbsevseg.c396
-rw-r--r--drivers/usb/misc/usbtest.c3078
-rw-r--r--drivers/usb/misc/uss720.c824
-rw-r--r--drivers/usb/misc/yurex.c530
43 files changed, 25432 insertions, 0 deletions
diff --git a/drivers/usb/misc/Kconfig b/drivers/usb/misc/Kconfig
new file mode 100644
index 000000000..9367c12c7
--- /dev/null
+++ b/drivers/usb/misc/Kconfig
@@ -0,0 +1,313 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# USB Miscellaneous driver configuration
+#
+comment "USB Miscellaneous drivers"
+
+config USB_EMI62
+ tristate "EMI 6|2m USB Audio interface support"
+ help
+ This driver loads firmware to Emagic EMI 6|2m low latency USB
+ Audio and Midi interface.
+
+ After firmware load the device is handled with standard linux
+ USB Audio driver.
+
+ This code is also available as a module ( = code which can be
+ inserted in and removed from the running kernel whenever you want).
+ The module will be called audio. If you want to compile it as a
+ module, say M here and read <file:Documentation/kbuild/modules.rst>.
+
+config USB_EMI26
+ tristate "EMI 2|6 USB Audio interface support"
+ help
+ This driver loads firmware to Emagic EMI 2|6 low latency USB
+ Audio interface.
+
+ After firmware load the device is handled with standard linux
+ USB Audio driver.
+
+ To compile this driver as a module, choose M here: the
+ module will be called emi26.
+
+config USB_ADUTUX
+ tristate "ADU devices from Ontrak Control Systems"
+ help
+ Say Y if you want to use an ADU device from Ontrak Control
+ Systems.
+
+ To compile this driver as a module, choose M here. The module
+ will be called adutux.
+
+config USB_SEVSEG
+ tristate "USB 7-Segment LED Display"
+ help
+ Say Y here if you have a USB 7-Segment Display by Delcom
+
+ To compile this driver as a module, choose M here: the
+ module will be called usbsevseg.
+
+config USB_LEGOTOWER
+ tristate "USB Lego Infrared Tower support"
+ help
+ Say Y here if you want to connect a USB Lego Infrared Tower to your
+ computer's USB port.
+
+ This code is also available as a module ( = code which can be
+ inserted in and removed from the running kernel whenever you want).
+ The module will be called legousbtower. If you want to compile it as
+ a module, say M here and read
+ <file:Documentation/kbuild/modules.rst>.
+
+config USB_LCD
+ tristate "USB LCD driver support"
+ help
+ Say Y here if you want to connect an USBLCD to your computer's
+ USB port. The USBLCD is a small USB interface board for
+ alphanumeric LCD modules. See <http://www.usblcd.de/> for more
+ information.
+
+ To compile this driver as a module, choose M here: the
+ module will be called usblcd.
+
+config USB_CYPRESS_CY7C63
+ tristate "Cypress CY7C63xxx USB driver support"
+ help
+ Say Y here if you want to connect a Cypress CY7C63xxx
+ micro controller to your computer's USB port. Currently this
+ driver supports the pre-programmed devices (incl. firmware)
+ by AK Modul-Bus Computer GmbH.
+
+ Please see: https://www.ak-modul-bus.de/stat/mikrocontroller.html
+
+ To compile this driver as a module, choose M here: the
+ module will be called cypress_cy7c63.
+
+config USB_CYTHERM
+ tristate "Cypress USB thermometer driver support"
+ help
+ Say Y here if you want to connect a Cypress USB thermometer
+ device to your computer's USB port. This device is also known
+ as the Cypress USB Starter kit or demo board. The Elektor
+ magazine published a modified version of this device in issue
+ #291.
+
+ To compile this driver as a module, choose M here: the
+ module will be called cytherm.
+
+config USB_IDMOUSE
+ tristate "Siemens ID USB Mouse Fingerprint sensor support"
+ help
+ Say Y here if you want to use the fingerprint sensor on
+ the Siemens ID Mouse. There is also a Siemens ID Mouse
+ _Professional_, which has not been tested with this driver,
+ but uses the same sensor and may therefore work.
+
+ This driver creates an entry "/dev/idmouseX" or "/dev/usb/idmouseX",
+ which can be used by, e.g.,"cat /dev/idmouse0 > fingerprint.pnm".
+
+ See also <https://www.fs.tum.de/~echtler/idmouse/>.
+
+config USB_FTDI_ELAN
+ tristate "Elan PCMCIA CardBus Adapter USB Client"
+ 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"
+ select BACKLIGHT_CLASS_DEVICE
+ help
+ Say Y here if you want to control the backlight of Apple Cinema
+ Displays over USB. This driver provides a sysfs interface.
+
+config USB_QCOM_EUD
+ tristate "QCOM Embedded USB Debugger(EUD) Driver"
+ depends on ARCH_QCOM || COMPILE_TEST
+ select USB_ROLE_SWITCH
+ help
+ This module enables support for Qualcomm Technologies, Inc.
+ Embedded USB Debugger (EUD). The EUD is a control peripheral
+ which reports VBUS attach/detach events and has USB-based
+ debug and trace capabilities. On selecting m, the module name
+ that is built is qcom_eud.ko
+
+config APPLE_MFI_FASTCHARGE
+ tristate "Fast charge control for iOS devices"
+ select POWER_SUPPLY
+ help
+ Say Y here if you want to control whether iOS devices will
+ fast charge from the USB interface, as implemented in "MFi"
+ chargers.
+
+ It is safe to say M here.
+
+source "drivers/usb/misc/sisusbvga/Kconfig"
+
+config USB_LD
+ tristate "USB LD driver"
+ help
+ This driver is for generic USB devices that use interrupt transfers,
+ like LD Didactic's USB devices.
+
+ To compile this driver as a module, choose M here: the
+ module will be called ldusb.
+
+config USB_TRANCEVIBRATOR
+ tristate "PlayStation 2 Trance Vibrator driver support"
+ help
+ Say Y here if you want to connect a PlayStation 2 Trance Vibrator
+ device to your computer's USB port.
+
+ To compile this driver as a module, choose M here: the
+ module will be called trancevibrator.
+
+config USB_IOWARRIOR
+ tristate "IO Warrior driver support"
+ help
+ Say Y here if you want to support the IO Warrior devices from Code
+ Mercenaries. This includes support for the following devices:
+ IO Warrior 40
+ IO Warrior 24
+ IO Warrior 56
+ IO Warrior 24 Power Vampire
+
+ To compile this driver as a module, choose M here: the
+ module will be called iowarrior.
+
+config USB_TEST
+ tristate "USB testing driver"
+ help
+ This driver is for testing host controller software. It is used
+ with specialized device firmware for regression and stress testing,
+ to help prevent problems from cropping up with "real" drivers.
+
+ See <http://www.linux-usb.org/usbtest/> for more information,
+ including sample test device firmware and "how to use it".
+
+config USB_EHSET_TEST_FIXTURE
+ tristate "USB EHSET Test Fixture driver"
+ help
+ Say Y here if you want to support the special test fixture device
+ used for the USB-IF Embedded Host High-Speed Electrical Test procedure.
+
+ When the test fixture is connected, it can enumerate as one of several
+ VID/PID pairs. This driver then initiates a corresponding test mode on
+ the downstream port to which the test fixture is attached.
+
+ See <http://www.usb.org/developers/onthego/EHSET_v1.01.pdf> for more
+ information.
+
+config USB_ISIGHTFW
+ tristate "iSight firmware loading support"
+ select FW_LOADER
+ help
+ This driver loads firmware for USB Apple iSight cameras, allowing
+ them to be driven by the USB video class driver available at
+ http://linux-uvc.berlios.de
+
+ The firmware for this driver must be extracted from the MacOS
+ driver beforehand. Tools for doing so are available at
+ http://bersace03.free.fr
+
+config USB_YUREX
+ tristate "USB YUREX driver support"
+ help
+ Say Y here if you want to connect a YUREX to your computer's
+ USB port. The YUREX is a leg-shakes sensor. See
+ <http://bbu.kayac.com/en/> for further information.
+ This driver supports read/write of leg-shakes counter and
+ fasync for the counter update via a device file /dev/yurex*.
+
+ To compile this driver as a module, choose M here: the
+ module will be called yurex.
+
+config USB_EZUSB_FX2
+ tristate "Functions for loading firmware on EZUSB chips"
+ help
+ Say Y here if you need EZUSB device support.
+ (Cypress FX/FX2/FX2LP microcontrollers)
+
+config USB_HUB_USB251XB
+ tristate "USB251XB Hub Controller Configuration Driver"
+ depends on I2C
+ help
+ This option enables support for configuration via SMBus of the
+ Microchip USB251x/xBi USB 2.0 Hub Controller series. Configuration
+ parameters may be set in devicetree or platform data.
+ Say Y or M here if you need to configure such a device via SMBus.
+
+config USB_HSIC_USB3503
+ tristate "USB3503 HSIC to USB20 Driver"
+ depends on I2C
+ select REGMAP_I2C
+ help
+ This option enables support for SMSC USB3503 HSIC to USB 2.0 Driver.
+
+config USB_HSIC_USB4604
+ tristate "USB4604 HSIC to USB20 Driver"
+ depends on I2C
+ help
+ This option enables support for SMSC USB4604 HSIC to USB 2.0 Driver.
+
+config USB_LINK_LAYER_TEST
+ tristate "USB Link Layer Test driver"
+ help
+ This driver is for generating specific traffic for Super Speed Link
+ Layer Test Device. Say Y only when you want to conduct USB Super Speed
+ Link Layer Test for host controllers.
+
+config USB_CHAOSKEY
+ tristate "ChaosKey random number generator driver support"
+ depends on HW_RANDOM
+ help
+ Say Y here if you want to connect an AltusMetrum ChaosKey or
+ Araneus Alea I to your computer's USB port. These devices
+ are hardware random number generators which hook into the
+ kernel entropy pool to ensure a large supply of entropy for
+ /dev/random and /dev/urandom and also provides direct access
+ via /dev/chaoskeyX
+
+ To compile this driver as a module, choose M here: the
+ module will be called chaoskey.
+
+config BRCM_USB_PINMAP
+ tristate "Broadcom pinmap driver support"
+ depends on (ARCH_BRCMSTB && PHY_BRCM_USB) || COMPILE_TEST
+ default ARCH_BRCMSTB && PHY_BRCM_USB
+ help
+ This option enables support for remapping some USB external
+ signals, which are typically on dedicated pins on the chip,
+ to any gpio.
+
+config USB_ONBOARD_HUB
+ tristate "Onboard USB hub support"
+ depends on OF || COMPILE_TEST
+ help
+ Say Y here if you want to support discrete onboard USB hubs that
+ don't require an additional control bus for initialization, but
+ need some non-trivial form of initialization, such as enabling a
+ power regulator. An example for such a hub is the Realtek
+ RTS5411.
+
+ This driver can be used as a module but its state (module vs
+ builtin) must match the state of the USB subsystem. Enabling
+ this config will enable the driver and it will automatically
+ match the state of the USB subsystem. If this driver is a
+ module it will be called onboard_usb_hub.
diff --git a/drivers/usb/misc/Makefile b/drivers/usb/misc/Makefile
new file mode 100644
index 000000000..93581baec
--- /dev/null
+++ b/drivers/usb/misc/Makefile
@@ -0,0 +1,36 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for the rest of the USB drivers
+# (the ones that don't fit into any other categories)
+#
+obj-$(CONFIG_USB_ADUTUX) += adutux.o
+obj-$(CONFIG_USB_APPLEDISPLAY) += appledisplay.o
+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_EZUSB_FX2) += ezusb.o
+obj-$(CONFIG_USB_FTDI_ELAN) += ftdi-elan.o
+obj-$(CONFIG_APPLE_MFI_FASTCHARGE) += apple-mfi-fastcharge.o
+obj-$(CONFIG_USB_IDMOUSE) += idmouse.o
+obj-$(CONFIG_USB_IOWARRIOR) += iowarrior.o
+obj-$(CONFIG_USB_ISIGHTFW) += isight_firmware.o
+obj-$(CONFIG_USB_LCD) += usblcd.o
+obj-$(CONFIG_USB_LD) += ldusb.o
+obj-$(CONFIG_USB_LEGOTOWER) += legousbtower.o
+obj-$(CONFIG_USB_QCOM_EUD) += qcom_eud.o
+obj-$(CONFIG_USB_TEST) += usbtest.o
+obj-$(CONFIG_USB_EHSET_TEST_FIXTURE) += ehset.o
+obj-$(CONFIG_USB_TRANCEVIBRATOR) += trancevibrator.o
+obj-$(CONFIG_USB_USS720) += uss720.o
+obj-$(CONFIG_USB_SEVSEG) += usbsevseg.o
+obj-$(CONFIG_USB_YUREX) += yurex.o
+obj-$(CONFIG_USB_HUB_USB251XB) += usb251xb.o
+obj-$(CONFIG_USB_HSIC_USB3503) += usb3503.o
+obj-$(CONFIG_USB_HSIC_USB4604) += usb4604.o
+obj-$(CONFIG_USB_CHAOSKEY) += chaoskey.o
+
+obj-$(CONFIG_USB_SISUSBVGA) += sisusbvga/
+obj-$(CONFIG_USB_LINK_LAYER_TEST) += lvstest.o
+obj-$(CONFIG_BRCM_USB_PINMAP) += brcmstb-usb-pinmap.o
+obj-$(CONFIG_USB_ONBOARD_HUB) += onboard_usb_hub.o
diff --git a/drivers/usb/misc/adutux.c b/drivers/usb/misc/adutux.c
new file mode 100644
index 000000000..ed6a19254
--- /dev/null
+++ b/drivers/usb/misc/adutux.c
@@ -0,0 +1,796 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * adutux - driver for ADU devices from Ontrak Control Systems
+ * This is an experimental driver. Use at your own risk.
+ * This driver is not supported by Ontrak Control Systems.
+ *
+ * Copyright (c) 2003 John Homppi (SCO, leave this notice here)
+ *
+ * derived from the Lego USB Tower driver 0.56:
+ * Copyright (c) 2003 David Glance <davidgsf@sourceforge.net>
+ * 2001 Juergen Stuber <stuber@loria.fr>
+ * that was derived from USB Skeleton driver - 0.5
+ * Copyright (c) 2001 Greg Kroah-Hartman (greg@kroah.com)
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/sched/signal.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/mutex.h>
+#include <linux/uaccess.h>
+
+#define DRIVER_AUTHOR "John Homppi"
+#define DRIVER_DESC "adutux (see www.ontrak.net)"
+
+/* Define these values to match your device */
+#define ADU_VENDOR_ID 0x0a07
+#define ADU_PRODUCT_ID 0x0064
+
+/* table of devices that work with this driver */
+static const struct usb_device_id device_table[] = {
+ { USB_DEVICE(ADU_VENDOR_ID, ADU_PRODUCT_ID) }, /* ADU100 */
+ { USB_DEVICE(ADU_VENDOR_ID, ADU_PRODUCT_ID+20) }, /* ADU120 */
+ { USB_DEVICE(ADU_VENDOR_ID, ADU_PRODUCT_ID+30) }, /* ADU130 */
+ { USB_DEVICE(ADU_VENDOR_ID, ADU_PRODUCT_ID+100) }, /* ADU200 */
+ { USB_DEVICE(ADU_VENDOR_ID, ADU_PRODUCT_ID+108) }, /* ADU208 */
+ { USB_DEVICE(ADU_VENDOR_ID, ADU_PRODUCT_ID+118) }, /* ADU218 */
+ { } /* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE(usb, device_table);
+
+#ifdef CONFIG_USB_DYNAMIC_MINORS
+#define ADU_MINOR_BASE 0
+#else
+#define ADU_MINOR_BASE 67
+#endif
+
+/* we can have up to this number of device plugged in at once */
+#define MAX_DEVICES 16
+
+#define COMMAND_TIMEOUT (2*HZ)
+
+/*
+ * The locking scheme is a vanilla 3-lock:
+ * adu_device.buflock: A spinlock, covers what IRQs touch.
+ * adutux_mutex: A Static lock to cover open_count. It would also cover
+ * any globals, but we don't have them in 2.6.
+ * adu_device.mtx: A mutex to hold across sleepers like copy_from_user.
+ * It covers all of adu_device, except the open_count
+ * and what .buflock covers.
+ */
+
+/* Structure to hold all of our device specific stuff */
+struct adu_device {
+ struct mutex mtx;
+ struct usb_device *udev; /* save off the usb device pointer */
+ struct usb_interface *interface;
+ unsigned int minor; /* the starting minor number for this device */
+ char serial_number[8];
+
+ int open_count; /* number of times this port has been opened */
+ unsigned long disconnected:1;
+
+ char *read_buffer_primary;
+ int read_buffer_length;
+ char *read_buffer_secondary;
+ int secondary_head;
+ int secondary_tail;
+ spinlock_t buflock;
+
+ wait_queue_head_t read_wait;
+ wait_queue_head_t write_wait;
+
+ char *interrupt_in_buffer;
+ struct usb_endpoint_descriptor *interrupt_in_endpoint;
+ struct urb *interrupt_in_urb;
+ int read_urb_finished;
+
+ char *interrupt_out_buffer;
+ struct usb_endpoint_descriptor *interrupt_out_endpoint;
+ struct urb *interrupt_out_urb;
+ int out_urb_finished;
+};
+
+static DEFINE_MUTEX(adutux_mutex);
+
+static struct usb_driver adu_driver;
+
+static inline void adu_debug_data(struct device *dev, const char *function,
+ int size, const unsigned char *data)
+{
+ dev_dbg(dev, "%s - length = %d, data = %*ph\n",
+ function, size, size, data);
+}
+
+/*
+ * adu_abort_transfers
+ * aborts transfers and frees associated data structures
+ */
+static void adu_abort_transfers(struct adu_device *dev)
+{
+ unsigned long flags;
+
+ if (dev->disconnected)
+ return;
+
+ /* shutdown transfer */
+
+ /* XXX Anchor these instead */
+ spin_lock_irqsave(&dev->buflock, flags);
+ if (!dev->read_urb_finished) {
+ spin_unlock_irqrestore(&dev->buflock, flags);
+ usb_kill_urb(dev->interrupt_in_urb);
+ } else
+ spin_unlock_irqrestore(&dev->buflock, flags);
+
+ spin_lock_irqsave(&dev->buflock, flags);
+ if (!dev->out_urb_finished) {
+ spin_unlock_irqrestore(&dev->buflock, flags);
+ wait_event_timeout(dev->write_wait, dev->out_urb_finished,
+ COMMAND_TIMEOUT);
+ usb_kill_urb(dev->interrupt_out_urb);
+ } else
+ spin_unlock_irqrestore(&dev->buflock, flags);
+}
+
+static void adu_delete(struct adu_device *dev)
+{
+ /* free data structures */
+ usb_free_urb(dev->interrupt_in_urb);
+ usb_free_urb(dev->interrupt_out_urb);
+ kfree(dev->read_buffer_primary);
+ kfree(dev->read_buffer_secondary);
+ kfree(dev->interrupt_in_buffer);
+ kfree(dev->interrupt_out_buffer);
+ usb_put_dev(dev->udev);
+ kfree(dev);
+}
+
+static void adu_interrupt_in_callback(struct urb *urb)
+{
+ struct adu_device *dev = urb->context;
+ int status = urb->status;
+ unsigned long flags;
+
+ adu_debug_data(&dev->udev->dev, __func__,
+ urb->actual_length, urb->transfer_buffer);
+
+ spin_lock_irqsave(&dev->buflock, flags);
+
+ if (status != 0) {
+ if ((status != -ENOENT) && (status != -ECONNRESET) &&
+ (status != -ESHUTDOWN)) {
+ dev_dbg(&dev->udev->dev,
+ "%s : nonzero status received: %d\n",
+ __func__, status);
+ }
+ goto exit;
+ }
+
+ if (urb->actual_length > 0 && dev->interrupt_in_buffer[0] != 0x00) {
+ if (dev->read_buffer_length <
+ (4 * usb_endpoint_maxp(dev->interrupt_in_endpoint)) -
+ (urb->actual_length)) {
+ memcpy (dev->read_buffer_primary +
+ dev->read_buffer_length,
+ dev->interrupt_in_buffer, urb->actual_length);
+
+ dev->read_buffer_length += urb->actual_length;
+ dev_dbg(&dev->udev->dev, "%s reading %d\n", __func__,
+ urb->actual_length);
+ } else {
+ dev_dbg(&dev->udev->dev, "%s : read_buffer overflow\n",
+ __func__);
+ }
+ }
+
+exit:
+ dev->read_urb_finished = 1;
+ spin_unlock_irqrestore(&dev->buflock, flags);
+ /* always wake up so we recover from errors */
+ wake_up_interruptible(&dev->read_wait);
+}
+
+static void adu_interrupt_out_callback(struct urb *urb)
+{
+ struct adu_device *dev = urb->context;
+ int status = urb->status;
+ unsigned long flags;
+
+ adu_debug_data(&dev->udev->dev, __func__,
+ urb->actual_length, urb->transfer_buffer);
+
+ if (status != 0) {
+ if ((status != -ENOENT) &&
+ (status != -ESHUTDOWN) &&
+ (status != -ECONNRESET)) {
+ dev_dbg(&dev->udev->dev,
+ "%s :nonzero status received: %d\n", __func__,
+ status);
+ }
+ return;
+ }
+
+ spin_lock_irqsave(&dev->buflock, flags);
+ dev->out_urb_finished = 1;
+ wake_up(&dev->write_wait);
+ spin_unlock_irqrestore(&dev->buflock, flags);
+}
+
+static int adu_open(struct inode *inode, struct file *file)
+{
+ struct adu_device *dev = NULL;
+ struct usb_interface *interface;
+ int subminor;
+ int retval;
+
+ subminor = iminor(inode);
+
+ retval = mutex_lock_interruptible(&adutux_mutex);
+ if (retval)
+ goto exit_no_lock;
+
+ interface = usb_find_interface(&adu_driver, subminor);
+ if (!interface) {
+ pr_err("%s - error, can't find device for minor %d\n",
+ __func__, subminor);
+ retval = -ENODEV;
+ goto exit_no_device;
+ }
+
+ dev = usb_get_intfdata(interface);
+ if (!dev) {
+ retval = -ENODEV;
+ goto exit_no_device;
+ }
+
+ /* check that nobody else is using the device */
+ if (dev->open_count) {
+ retval = -EBUSY;
+ goto exit_no_device;
+ }
+
+ ++dev->open_count;
+ dev_dbg(&dev->udev->dev, "%s: open count %d\n", __func__,
+ dev->open_count);
+
+ /* save device in the file's private structure */
+ file->private_data = dev;
+
+ /* initialize in direction */
+ dev->read_buffer_length = 0;
+
+ /* fixup first read by having urb waiting for it */
+ usb_fill_int_urb(dev->interrupt_in_urb, dev->udev,
+ usb_rcvintpipe(dev->udev,
+ dev->interrupt_in_endpoint->bEndpointAddress),
+ dev->interrupt_in_buffer,
+ usb_endpoint_maxp(dev->interrupt_in_endpoint),
+ adu_interrupt_in_callback, dev,
+ dev->interrupt_in_endpoint->bInterval);
+ dev->read_urb_finished = 0;
+ if (usb_submit_urb(dev->interrupt_in_urb, GFP_KERNEL))
+ dev->read_urb_finished = 1;
+ /* we ignore failure */
+ /* end of fixup for first read */
+
+ /* initialize out direction */
+ dev->out_urb_finished = 1;
+
+ retval = 0;
+
+exit_no_device:
+ mutex_unlock(&adutux_mutex);
+exit_no_lock:
+ return retval;
+}
+
+static void adu_release_internal(struct adu_device *dev)
+{
+ /* decrement our usage count for the device */
+ --dev->open_count;
+ dev_dbg(&dev->udev->dev, "%s : open count %d\n", __func__,
+ dev->open_count);
+ if (dev->open_count <= 0) {
+ adu_abort_transfers(dev);
+ dev->open_count = 0;
+ }
+}
+
+static int adu_release(struct inode *inode, struct file *file)
+{
+ struct adu_device *dev;
+ int retval = 0;
+
+ if (file == NULL) {
+ retval = -ENODEV;
+ goto exit;
+ }
+
+ dev = file->private_data;
+ if (dev == NULL) {
+ retval = -ENODEV;
+ goto exit;
+ }
+
+ mutex_lock(&adutux_mutex); /* not interruptible */
+
+ if (dev->open_count <= 0) {
+ dev_dbg(&dev->udev->dev, "%s : device not opened\n", __func__);
+ retval = -ENODEV;
+ goto unlock;
+ }
+
+ adu_release_internal(dev);
+ if (dev->disconnected) {
+ /* the device was unplugged before the file was released */
+ if (!dev->open_count) /* ... and we're the last user */
+ adu_delete(dev);
+ }
+unlock:
+ mutex_unlock(&adutux_mutex);
+exit:
+ return retval;
+}
+
+static ssize_t adu_read(struct file *file, __user char *buffer, size_t count,
+ loff_t *ppos)
+{
+ struct adu_device *dev;
+ size_t bytes_read = 0;
+ size_t bytes_to_read = count;
+ int retval = 0;
+ int timeout = 0;
+ int should_submit = 0;
+ unsigned long flags;
+ DECLARE_WAITQUEUE(wait, current);
+
+ dev = file->private_data;
+ if (mutex_lock_interruptible(&dev->mtx))
+ return -ERESTARTSYS;
+
+ /* verify that the device wasn't unplugged */
+ if (dev->disconnected) {
+ retval = -ENODEV;
+ pr_err("No device or device unplugged %d\n", retval);
+ goto exit;
+ }
+
+ /* verify that some data was requested */
+ if (count == 0) {
+ dev_dbg(&dev->udev->dev, "%s : read request of 0 bytes\n",
+ __func__);
+ goto exit;
+ }
+
+ timeout = COMMAND_TIMEOUT;
+ dev_dbg(&dev->udev->dev, "%s : about to start looping\n", __func__);
+ while (bytes_to_read) {
+ size_t data_in_secondary = dev->secondary_tail - dev->secondary_head;
+ dev_dbg(&dev->udev->dev,
+ "%s : while, data_in_secondary=%zu, status=%d\n",
+ __func__, data_in_secondary,
+ dev->interrupt_in_urb->status);
+
+ if (data_in_secondary) {
+ /* drain secondary buffer */
+ size_t amount = min(bytes_to_read, data_in_secondary);
+ if (copy_to_user(buffer, dev->read_buffer_secondary+dev->secondary_head, amount)) {
+ retval = -EFAULT;
+ goto exit;
+ }
+ dev->secondary_head += amount;
+ bytes_read += amount;
+ bytes_to_read -= amount;
+ } else {
+ /* we check the primary buffer */
+ spin_lock_irqsave (&dev->buflock, flags);
+ if (dev->read_buffer_length) {
+ /* we secure access to the primary */
+ dev_dbg(&dev->udev->dev,
+ "%s : swap, read_buffer_length = %d\n",
+ __func__, dev->read_buffer_length);
+ swap(dev->read_buffer_primary, dev->read_buffer_secondary);
+ dev->secondary_head = 0;
+ dev->secondary_tail = dev->read_buffer_length;
+ dev->read_buffer_length = 0;
+ spin_unlock_irqrestore(&dev->buflock, flags);
+ /* we have a free buffer so use it */
+ should_submit = 1;
+ } else {
+ /* even the primary was empty - we may need to do IO */
+ if (!dev->read_urb_finished) {
+ /* somebody is doing IO */
+ spin_unlock_irqrestore(&dev->buflock, flags);
+ dev_dbg(&dev->udev->dev,
+ "%s : submitted already\n",
+ __func__);
+ } else {
+ /* we must initiate input */
+ dev_dbg(&dev->udev->dev,
+ "%s : initiate input\n",
+ __func__);
+ dev->read_urb_finished = 0;
+ spin_unlock_irqrestore(&dev->buflock, flags);
+
+ usb_fill_int_urb(dev->interrupt_in_urb, dev->udev,
+ usb_rcvintpipe(dev->udev,
+ dev->interrupt_in_endpoint->bEndpointAddress),
+ dev->interrupt_in_buffer,
+ usb_endpoint_maxp(dev->interrupt_in_endpoint),
+ adu_interrupt_in_callback,
+ dev,
+ dev->interrupt_in_endpoint->bInterval);
+ retval = usb_submit_urb(dev->interrupt_in_urb, GFP_KERNEL);
+ if (retval) {
+ dev->read_urb_finished = 1;
+ if (retval == -ENOMEM) {
+ retval = bytes_read ? bytes_read : -ENOMEM;
+ }
+ dev_dbg(&dev->udev->dev,
+ "%s : submit failed\n",
+ __func__);
+ goto exit;
+ }
+ }
+
+ /* we wait for I/O to complete */
+ set_current_state(TASK_INTERRUPTIBLE);
+ add_wait_queue(&dev->read_wait, &wait);
+ spin_lock_irqsave(&dev->buflock, flags);
+ if (!dev->read_urb_finished) {
+ spin_unlock_irqrestore(&dev->buflock, flags);
+ timeout = schedule_timeout(COMMAND_TIMEOUT);
+ } else {
+ spin_unlock_irqrestore(&dev->buflock, flags);
+ set_current_state(TASK_RUNNING);
+ }
+ remove_wait_queue(&dev->read_wait, &wait);
+
+ if (timeout <= 0) {
+ dev_dbg(&dev->udev->dev,
+ "%s : timeout\n", __func__);
+ retval = bytes_read ? bytes_read : -ETIMEDOUT;
+ goto exit;
+ }
+
+ if (signal_pending(current)) {
+ dev_dbg(&dev->udev->dev,
+ "%s : signal pending\n",
+ __func__);
+ retval = bytes_read ? bytes_read : -EINTR;
+ goto exit;
+ }
+ }
+ }
+ }
+
+ retval = bytes_read;
+ /* if the primary buffer is empty then use it */
+ spin_lock_irqsave(&dev->buflock, flags);
+ if (should_submit && dev->read_urb_finished) {
+ dev->read_urb_finished = 0;
+ spin_unlock_irqrestore(&dev->buflock, flags);
+ usb_fill_int_urb(dev->interrupt_in_urb, dev->udev,
+ usb_rcvintpipe(dev->udev,
+ dev->interrupt_in_endpoint->bEndpointAddress),
+ dev->interrupt_in_buffer,
+ usb_endpoint_maxp(dev->interrupt_in_endpoint),
+ adu_interrupt_in_callback,
+ dev,
+ dev->interrupt_in_endpoint->bInterval);
+ if (usb_submit_urb(dev->interrupt_in_urb, GFP_KERNEL) != 0)
+ dev->read_urb_finished = 1;
+ /* we ignore failure */
+ } else {
+ spin_unlock_irqrestore(&dev->buflock, flags);
+ }
+
+exit:
+ /* unlock the device */
+ mutex_unlock(&dev->mtx);
+
+ return retval;
+}
+
+static ssize_t adu_write(struct file *file, const __user char *buffer,
+ size_t count, loff_t *ppos)
+{
+ DECLARE_WAITQUEUE(waita, current);
+ struct adu_device *dev;
+ size_t bytes_written = 0;
+ size_t bytes_to_write;
+ size_t buffer_size;
+ unsigned long flags;
+ int retval;
+
+ dev = file->private_data;
+
+ retval = mutex_lock_interruptible(&dev->mtx);
+ if (retval)
+ goto exit_nolock;
+
+ /* verify that the device wasn't unplugged */
+ if (dev->disconnected) {
+ retval = -ENODEV;
+ pr_err("No device or device unplugged %d\n", retval);
+ goto exit;
+ }
+
+ /* verify that we actually have some data to write */
+ if (count == 0) {
+ dev_dbg(&dev->udev->dev, "%s : write request of 0 bytes\n",
+ __func__);
+ goto exit;
+ }
+
+ while (count > 0) {
+ add_wait_queue(&dev->write_wait, &waita);
+ set_current_state(TASK_INTERRUPTIBLE);
+ spin_lock_irqsave(&dev->buflock, flags);
+ if (!dev->out_urb_finished) {
+ spin_unlock_irqrestore(&dev->buflock, flags);
+
+ mutex_unlock(&dev->mtx);
+ if (signal_pending(current)) {
+ dev_dbg(&dev->udev->dev, "%s : interrupted\n",
+ __func__);
+ set_current_state(TASK_RUNNING);
+ retval = -EINTR;
+ goto exit_onqueue;
+ }
+ if (schedule_timeout(COMMAND_TIMEOUT) == 0) {
+ dev_dbg(&dev->udev->dev,
+ "%s - command timed out.\n", __func__);
+ retval = -ETIMEDOUT;
+ goto exit_onqueue;
+ }
+ remove_wait_queue(&dev->write_wait, &waita);
+ retval = mutex_lock_interruptible(&dev->mtx);
+ if (retval) {
+ retval = bytes_written ? bytes_written : retval;
+ goto exit_nolock;
+ }
+
+ dev_dbg(&dev->udev->dev,
+ "%s : in progress, count = %zd\n",
+ __func__, count);
+ } else {
+ spin_unlock_irqrestore(&dev->buflock, flags);
+ set_current_state(TASK_RUNNING);
+ remove_wait_queue(&dev->write_wait, &waita);
+ dev_dbg(&dev->udev->dev, "%s : sending, count = %zd\n",
+ __func__, count);
+
+ /* write the data into interrupt_out_buffer from userspace */
+ buffer_size = usb_endpoint_maxp(dev->interrupt_out_endpoint);
+ bytes_to_write = count > buffer_size ? buffer_size : count;
+ dev_dbg(&dev->udev->dev,
+ "%s : buffer_size = %zd, count = %zd, bytes_to_write = %zd\n",
+ __func__, buffer_size, count, bytes_to_write);
+
+ if (copy_from_user(dev->interrupt_out_buffer, buffer, bytes_to_write) != 0) {
+ retval = -EFAULT;
+ goto exit;
+ }
+
+ /* send off the urb */
+ usb_fill_int_urb(
+ dev->interrupt_out_urb,
+ dev->udev,
+ usb_sndintpipe(dev->udev, dev->interrupt_out_endpoint->bEndpointAddress),
+ dev->interrupt_out_buffer,
+ bytes_to_write,
+ adu_interrupt_out_callback,
+ dev,
+ dev->interrupt_out_endpoint->bInterval);
+ dev->interrupt_out_urb->actual_length = bytes_to_write;
+ dev->out_urb_finished = 0;
+ retval = usb_submit_urb(dev->interrupt_out_urb, GFP_KERNEL);
+ if (retval < 0) {
+ dev->out_urb_finished = 1;
+ dev_err(&dev->udev->dev, "Couldn't submit "
+ "interrupt_out_urb %d\n", retval);
+ goto exit;
+ }
+
+ buffer += bytes_to_write;
+ count -= bytes_to_write;
+
+ bytes_written += bytes_to_write;
+ }
+ }
+ mutex_unlock(&dev->mtx);
+ return bytes_written;
+
+exit:
+ mutex_unlock(&dev->mtx);
+exit_nolock:
+ return retval;
+
+exit_onqueue:
+ remove_wait_queue(&dev->write_wait, &waita);
+ return retval;
+}
+
+/* file operations needed when we register this driver */
+static const struct file_operations adu_fops = {
+ .owner = THIS_MODULE,
+ .read = adu_read,
+ .write = adu_write,
+ .open = adu_open,
+ .release = adu_release,
+ .llseek = noop_llseek,
+};
+
+/*
+ * usb class driver info in order to get a minor number from the usb core,
+ * and to have the device registered with devfs and the driver core
+ */
+static struct usb_class_driver adu_class = {
+ .name = "usb/adutux%d",
+ .fops = &adu_fops,
+ .minor_base = ADU_MINOR_BASE,
+};
+
+/*
+ * adu_probe
+ *
+ * Called by the usb core when a new device is connected that it thinks
+ * this driver might be interested in.
+ */
+static int adu_probe(struct usb_interface *interface,
+ const struct usb_device_id *id)
+{
+ struct usb_device *udev = interface_to_usbdev(interface);
+ struct adu_device *dev = NULL;
+ int retval = -ENOMEM;
+ int in_end_size;
+ int out_end_size;
+ int res;
+
+ /* allocate memory for our device state and initialize it */
+ dev = kzalloc(sizeof(struct adu_device), GFP_KERNEL);
+ if (!dev)
+ return -ENOMEM;
+
+ mutex_init(&dev->mtx);
+ spin_lock_init(&dev->buflock);
+ dev->udev = usb_get_dev(udev);
+ init_waitqueue_head(&dev->read_wait);
+ init_waitqueue_head(&dev->write_wait);
+
+ res = usb_find_common_endpoints_reverse(interface->cur_altsetting,
+ NULL, NULL,
+ &dev->interrupt_in_endpoint,
+ &dev->interrupt_out_endpoint);
+ if (res) {
+ dev_err(&interface->dev, "interrupt endpoints not found\n");
+ retval = res;
+ goto error;
+ }
+
+ in_end_size = usb_endpoint_maxp(dev->interrupt_in_endpoint);
+ out_end_size = usb_endpoint_maxp(dev->interrupt_out_endpoint);
+
+ dev->read_buffer_primary = kmalloc((4 * in_end_size), GFP_KERNEL);
+ if (!dev->read_buffer_primary)
+ goto error;
+
+ /* debug code prime the buffer */
+ memset(dev->read_buffer_primary, 'a', in_end_size);
+ memset(dev->read_buffer_primary + in_end_size, 'b', in_end_size);
+ memset(dev->read_buffer_primary + (2 * in_end_size), 'c', in_end_size);
+ memset(dev->read_buffer_primary + (3 * in_end_size), 'd', in_end_size);
+
+ dev->read_buffer_secondary = kmalloc((4 * in_end_size), GFP_KERNEL);
+ if (!dev->read_buffer_secondary)
+ goto error;
+
+ /* debug code prime the buffer */
+ memset(dev->read_buffer_secondary, 'e', in_end_size);
+ memset(dev->read_buffer_secondary + in_end_size, 'f', in_end_size);
+ memset(dev->read_buffer_secondary + (2 * in_end_size), 'g', in_end_size);
+ memset(dev->read_buffer_secondary + (3 * in_end_size), 'h', in_end_size);
+
+ dev->interrupt_in_buffer = kmalloc(in_end_size, GFP_KERNEL);
+ if (!dev->interrupt_in_buffer)
+ goto error;
+
+ /* debug code prime the buffer */
+ memset(dev->interrupt_in_buffer, 'i', in_end_size);
+
+ dev->interrupt_in_urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!dev->interrupt_in_urb)
+ goto error;
+ dev->interrupt_out_buffer = kmalloc(out_end_size, GFP_KERNEL);
+ if (!dev->interrupt_out_buffer)
+ goto error;
+ dev->interrupt_out_urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!dev->interrupt_out_urb)
+ goto error;
+
+ if (!usb_string(udev, udev->descriptor.iSerialNumber, dev->serial_number,
+ sizeof(dev->serial_number))) {
+ dev_err(&interface->dev, "Could not retrieve serial number\n");
+ retval = -EIO;
+ goto error;
+ }
+ dev_dbg(&interface->dev, "serial_number=%s", dev->serial_number);
+
+ /* we can register the device now, as it is ready */
+ usb_set_intfdata(interface, dev);
+
+ retval = usb_register_dev(interface, &adu_class);
+
+ if (retval) {
+ /* something prevented us from registering this driver */
+ dev_err(&interface->dev, "Not able to get a minor for this device.\n");
+ usb_set_intfdata(interface, NULL);
+ goto error;
+ }
+
+ dev->minor = interface->minor;
+
+ /* let the user know what node this device is now attached to */
+ dev_info(&interface->dev, "ADU%d %s now attached to /dev/usb/adutux%d\n",
+ le16_to_cpu(udev->descriptor.idProduct), dev->serial_number,
+ (dev->minor - ADU_MINOR_BASE));
+
+ return 0;
+
+error:
+ adu_delete(dev);
+ return retval;
+}
+
+/*
+ * adu_disconnect
+ *
+ * Called by the usb core when the device is removed from the system.
+ */
+static void adu_disconnect(struct usb_interface *interface)
+{
+ struct adu_device *dev;
+
+ dev = usb_get_intfdata(interface);
+
+ usb_deregister_dev(interface, &adu_class);
+
+ usb_poison_urb(dev->interrupt_in_urb);
+ usb_poison_urb(dev->interrupt_out_urb);
+
+ mutex_lock(&adutux_mutex);
+ usb_set_intfdata(interface, NULL);
+
+ mutex_lock(&dev->mtx); /* not interruptible */
+ dev->disconnected = 1;
+ mutex_unlock(&dev->mtx);
+
+ /* if the device is not opened, then we clean up right now */
+ if (!dev->open_count)
+ adu_delete(dev);
+
+ mutex_unlock(&adutux_mutex);
+}
+
+/* usb specific object needed to register this driver with the usb subsystem */
+static struct usb_driver adu_driver = {
+ .name = "adutux",
+ .probe = adu_probe,
+ .disconnect = adu_disconnect,
+ .id_table = device_table,
+};
+
+module_usb_driver(adu_driver);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/misc/apple-mfi-fastcharge.c b/drivers/usb/misc/apple-mfi-fastcharge.c
new file mode 100644
index 000000000..ac8695195
--- /dev/null
+++ b/drivers/usb/misc/apple-mfi-fastcharge.c
@@ -0,0 +1,241 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Fast-charge control for Apple "MFi" devices
+ *
+ * Copyright (C) 2019 Bastien Nocera <hadess@hadess.net>
+ */
+
+/* Standard include files */
+#include <linux/module.h>
+#include <linux/power_supply.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+
+MODULE_AUTHOR("Bastien Nocera <hadess@hadess.net>");
+MODULE_DESCRIPTION("Fast-charge control for Apple \"MFi\" devices");
+MODULE_LICENSE("GPL");
+
+#define TRICKLE_CURRENT_MA 0
+#define FAST_CURRENT_MA 2500
+
+#define APPLE_VENDOR_ID 0x05ac /* Apple */
+
+/* The product ID is defined as starting with 0x12nn, as per the
+ * "Choosing an Apple Device USB Configuration" section in
+ * release R9 (2012) of the "MFi Accessory Hardware Specification"
+ *
+ * To distinguish an Apple device, a USB host can check the device
+ * descriptor of attached USB devices for the following fields:
+ * ■ Vendor ID: 0x05AC
+ * ■ Product ID: 0x12nn
+ *
+ * Those checks will be done in .match() and .probe().
+ */
+
+static const struct usb_device_id mfi_fc_id_table[] = {
+ { .idVendor = APPLE_VENDOR_ID,
+ .match_flags = USB_DEVICE_ID_MATCH_VENDOR },
+ {},
+};
+
+MODULE_DEVICE_TABLE(usb, mfi_fc_id_table);
+
+/* Driver-local specific stuff */
+struct mfi_device {
+ struct usb_device *udev;
+ struct power_supply *battery;
+ int charge_type;
+};
+
+static int apple_mfi_fc_set_charge_type(struct mfi_device *mfi,
+ const union power_supply_propval *val)
+{
+ int current_ma;
+ int retval;
+ __u8 request_type;
+
+ if (mfi->charge_type == val->intval) {
+ dev_dbg(&mfi->udev->dev, "charge type %d already set\n",
+ mfi->charge_type);
+ return 0;
+ }
+
+ switch (val->intval) {
+ case POWER_SUPPLY_CHARGE_TYPE_TRICKLE:
+ current_ma = TRICKLE_CURRENT_MA;
+ break;
+ case POWER_SUPPLY_CHARGE_TYPE_FAST:
+ current_ma = FAST_CURRENT_MA;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ request_type = USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE;
+ retval = usb_control_msg(mfi->udev, usb_sndctrlpipe(mfi->udev, 0),
+ 0x40, /* Vendor‐defined power request */
+ request_type,
+ current_ma, /* wValue, current offset */
+ current_ma, /* wIndex, current offset */
+ NULL, 0, USB_CTRL_GET_TIMEOUT);
+ if (retval) {
+ dev_dbg(&mfi->udev->dev, "retval = %d\n", retval);
+ return retval;
+ }
+
+ mfi->charge_type = val->intval;
+
+ return 0;
+}
+
+static int apple_mfi_fc_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct mfi_device *mfi = power_supply_get_drvdata(psy);
+
+ dev_dbg(&mfi->udev->dev, "prop: %d\n", psp);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ val->intval = mfi->charge_type;
+ break;
+ case POWER_SUPPLY_PROP_SCOPE:
+ val->intval = POWER_SUPPLY_SCOPE_DEVICE;
+ break;
+ default:
+ return -ENODATA;
+ }
+
+ return 0;
+}
+
+static int apple_mfi_fc_set_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct mfi_device *mfi = power_supply_get_drvdata(psy);
+ int ret;
+
+ dev_dbg(&mfi->udev->dev, "prop: %d\n", psp);
+
+ ret = pm_runtime_get_sync(&mfi->udev->dev);
+ if (ret < 0) {
+ pm_runtime_put_noidle(&mfi->udev->dev);
+ return ret;
+ }
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ ret = apple_mfi_fc_set_charge_type(mfi, val);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ pm_runtime_mark_last_busy(&mfi->udev->dev);
+ pm_runtime_put_autosuspend(&mfi->udev->dev);
+
+ return ret;
+}
+
+static int apple_mfi_fc_property_is_writeable(struct power_supply *psy,
+ enum power_supply_property psp)
+{
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+static enum power_supply_property apple_mfi_fc_properties[] = {
+ POWER_SUPPLY_PROP_CHARGE_TYPE,
+ POWER_SUPPLY_PROP_SCOPE
+};
+
+static const struct power_supply_desc apple_mfi_fc_desc = {
+ .name = "apple_mfi_fastcharge",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = apple_mfi_fc_properties,
+ .num_properties = ARRAY_SIZE(apple_mfi_fc_properties),
+ .get_property = apple_mfi_fc_get_property,
+ .set_property = apple_mfi_fc_set_property,
+ .property_is_writeable = apple_mfi_fc_property_is_writeable
+};
+
+static bool mfi_fc_match(struct usb_device *udev)
+{
+ int idProduct;
+
+ idProduct = le16_to_cpu(udev->descriptor.idProduct);
+ /* See comment above mfi_fc_id_table[] */
+ return (idProduct >= 0x1200 && idProduct <= 0x12ff);
+}
+
+static int mfi_fc_probe(struct usb_device *udev)
+{
+ struct power_supply_config battery_cfg = {};
+ struct mfi_device *mfi = NULL;
+ int err;
+
+ if (!mfi_fc_match(udev))
+ return -ENODEV;
+
+ mfi = kzalloc(sizeof(struct mfi_device), GFP_KERNEL);
+ if (!mfi)
+ return -ENOMEM;
+
+ battery_cfg.drv_data = mfi;
+
+ mfi->charge_type = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
+ mfi->battery = power_supply_register(&udev->dev,
+ &apple_mfi_fc_desc,
+ &battery_cfg);
+ if (IS_ERR(mfi->battery)) {
+ dev_err(&udev->dev, "Can't register battery\n");
+ err = PTR_ERR(mfi->battery);
+ kfree(mfi);
+ return err;
+ }
+
+ mfi->udev = usb_get_dev(udev);
+ dev_set_drvdata(&udev->dev, mfi);
+
+ return 0;
+}
+
+static void mfi_fc_disconnect(struct usb_device *udev)
+{
+ struct mfi_device *mfi;
+
+ mfi = dev_get_drvdata(&udev->dev);
+ if (mfi->battery)
+ power_supply_unregister(mfi->battery);
+ dev_set_drvdata(&udev->dev, NULL);
+ usb_put_dev(mfi->udev);
+ kfree(mfi);
+}
+
+static struct usb_device_driver mfi_fc_driver = {
+ .name = "apple-mfi-fastcharge",
+ .probe = mfi_fc_probe,
+ .disconnect = mfi_fc_disconnect,
+ .id_table = mfi_fc_id_table,
+ .match = mfi_fc_match,
+ .generic_subclass = 1,
+};
+
+static int __init mfi_fc_driver_init(void)
+{
+ return usb_register_device_driver(&mfi_fc_driver, THIS_MODULE);
+}
+
+static void __exit mfi_fc_driver_exit(void)
+{
+ usb_deregister_device_driver(&mfi_fc_driver);
+}
+
+module_init(mfi_fc_driver_init);
+module_exit(mfi_fc_driver_exit);
diff --git a/drivers/usb/misc/appledisplay.c b/drivers/usb/misc/appledisplay.c
new file mode 100644
index 000000000..c8098e9b4
--- /dev/null
+++ b/drivers/usb/misc/appledisplay.c
@@ -0,0 +1,349 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Apple Cinema Display driver
+ *
+ * Copyright (C) 2006 Michael Hanselmann (linux-kernel@hansmi.ch)
+ *
+ * Thanks to Caskey L. Dickson for his work with acdctl.
+ */
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <linux/backlight.h>
+#include <linux/timer.h>
+#include <linux/workqueue.h>
+#include <linux/atomic.h>
+
+#define APPLE_VENDOR_ID 0x05AC
+
+#define USB_REQ_GET_REPORT 0x01
+#define USB_REQ_SET_REPORT 0x09
+
+#define ACD_USB_TIMEOUT 250
+
+#define ACD_USB_EDID 0x0302
+#define ACD_USB_BRIGHTNESS 0x0310
+
+#define ACD_BTN_NONE 0
+#define ACD_BTN_BRIGHT_UP 3
+#define ACD_BTN_BRIGHT_DOWN 4
+
+#define ACD_URB_BUFFER_LEN 2
+#define ACD_MSG_BUFFER_LEN 2
+
+#define APPLEDISPLAY_DEVICE(prod) \
+ .match_flags = USB_DEVICE_ID_MATCH_DEVICE | \
+ USB_DEVICE_ID_MATCH_INT_CLASS | \
+ USB_DEVICE_ID_MATCH_INT_PROTOCOL, \
+ .idVendor = APPLE_VENDOR_ID, \
+ .idProduct = (prod), \
+ .bInterfaceClass = USB_CLASS_HID, \
+ .bInterfaceProtocol = 0x00
+
+/* table of devices that work with this driver */
+static const struct usb_device_id appledisplay_table[] = {
+ { APPLEDISPLAY_DEVICE(0x9218) },
+ { APPLEDISPLAY_DEVICE(0x9219) },
+ { APPLEDISPLAY_DEVICE(0x921c) },
+ { APPLEDISPLAY_DEVICE(0x921d) },
+ { APPLEDISPLAY_DEVICE(0x9222) },
+ { APPLEDISPLAY_DEVICE(0x9226) },
+ { APPLEDISPLAY_DEVICE(0x9236) },
+
+ /* Terminating entry */
+ { }
+};
+MODULE_DEVICE_TABLE(usb, appledisplay_table);
+
+/* Structure to hold all of our device specific stuff */
+struct appledisplay {
+ struct usb_device *udev; /* usb device */
+ struct urb *urb; /* usb request block */
+ struct backlight_device *bd; /* backlight device */
+ u8 *urbdata; /* interrupt URB data buffer */
+ u8 *msgdata; /* control message data buffer */
+
+ struct delayed_work work;
+ int button_pressed;
+ struct mutex sysfslock; /* concurrent read and write */
+};
+
+static atomic_t count_displays = ATOMIC_INIT(0);
+
+static void appledisplay_complete(struct urb *urb)
+{
+ struct appledisplay *pdata = urb->context;
+ struct device *dev = &pdata->udev->dev;
+ int status = urb->status;
+ int retval;
+
+ switch (status) {
+ case 0:
+ /* success */
+ break;
+ case -EOVERFLOW:
+ dev_err(dev,
+ "OVERFLOW with data length %d, actual length is %d\n",
+ ACD_URB_BUFFER_LEN, pdata->urb->actual_length);
+ fallthrough;
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ /* This urb is terminated, clean up */
+ dev_dbg(dev, "%s - urb shuttingdown with status: %d\n",
+ __func__, status);
+ return;
+ default:
+ dev_dbg(dev, "%s - nonzero urb status received: %d\n",
+ __func__, status);
+ goto exit;
+ }
+
+ switch(pdata->urbdata[1]) {
+ case ACD_BTN_BRIGHT_UP:
+ case ACD_BTN_BRIGHT_DOWN:
+ pdata->button_pressed = 1;
+ schedule_delayed_work(&pdata->work, 0);
+ break;
+ case ACD_BTN_NONE:
+ default:
+ pdata->button_pressed = 0;
+ break;
+ }
+
+exit:
+ retval = usb_submit_urb(pdata->urb, GFP_ATOMIC);
+ if (retval) {
+ dev_err(dev, "%s - usb_submit_urb failed with result %d\n",
+ __func__, retval);
+ }
+}
+
+static int appledisplay_bl_update_status(struct backlight_device *bd)
+{
+ struct appledisplay *pdata = bl_get_data(bd);
+ int retval;
+
+ mutex_lock(&pdata->sysfslock);
+ pdata->msgdata[0] = 0x10;
+ pdata->msgdata[1] = bd->props.brightness;
+
+ retval = usb_control_msg(
+ pdata->udev,
+ usb_sndctrlpipe(pdata->udev, 0),
+ USB_REQ_SET_REPORT,
+ USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+ ACD_USB_BRIGHTNESS,
+ 0,
+ pdata->msgdata, 2,
+ ACD_USB_TIMEOUT);
+ mutex_unlock(&pdata->sysfslock);
+
+ if (retval < 0)
+ return retval;
+ else
+ return 0;
+}
+
+static int appledisplay_bl_get_brightness(struct backlight_device *bd)
+{
+ struct appledisplay *pdata = bl_get_data(bd);
+ int retval, brightness;
+
+ mutex_lock(&pdata->sysfslock);
+ retval = usb_control_msg(
+ pdata->udev,
+ usb_rcvctrlpipe(pdata->udev, 0),
+ USB_REQ_GET_REPORT,
+ USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+ ACD_USB_BRIGHTNESS,
+ 0,
+ pdata->msgdata, 2,
+ ACD_USB_TIMEOUT);
+ if (retval < 2) {
+ if (retval >= 0)
+ retval = -EMSGSIZE;
+ } else {
+ brightness = pdata->msgdata[1];
+ }
+ mutex_unlock(&pdata->sysfslock);
+
+ if (retval < 0)
+ return retval;
+ else
+ return brightness;
+}
+
+static const struct backlight_ops appledisplay_bl_data = {
+ .get_brightness = appledisplay_bl_get_brightness,
+ .update_status = appledisplay_bl_update_status,
+};
+
+static void appledisplay_work(struct work_struct *work)
+{
+ struct appledisplay *pdata =
+ container_of(work, struct appledisplay, work.work);
+ int retval;
+
+ retval = appledisplay_bl_get_brightness(pdata->bd);
+ if (retval >= 0)
+ pdata->bd->props.brightness = retval;
+
+ /* Poll again in about 125ms if there's still a button pressed */
+ if (pdata->button_pressed)
+ schedule_delayed_work(&pdata->work, HZ / 8);
+}
+
+static int appledisplay_probe(struct usb_interface *iface,
+ const struct usb_device_id *id)
+{
+ struct backlight_properties props;
+ struct appledisplay *pdata;
+ struct usb_device *udev = interface_to_usbdev(iface);
+ struct usb_endpoint_descriptor *endpoint;
+ int int_in_endpointAddr = 0;
+ int retval, brightness;
+ char bl_name[20];
+
+ /* set up the endpoint information */
+ /* use only the first interrupt-in endpoint */
+ retval = usb_find_int_in_endpoint(iface->cur_altsetting, &endpoint);
+ if (retval) {
+ dev_err(&iface->dev, "Could not find int-in endpoint\n");
+ return retval;
+ }
+
+ int_in_endpointAddr = endpoint->bEndpointAddress;
+
+ /* allocate memory for our device state and initialize it */
+ pdata = kzalloc(sizeof(struct appledisplay), GFP_KERNEL);
+ if (!pdata) {
+ retval = -ENOMEM;
+ goto error;
+ }
+
+ pdata->udev = udev;
+
+ INIT_DELAYED_WORK(&pdata->work, appledisplay_work);
+ mutex_init(&pdata->sysfslock);
+
+ /* Allocate buffer for control messages */
+ pdata->msgdata = kmalloc(ACD_MSG_BUFFER_LEN, GFP_KERNEL);
+ if (!pdata->msgdata) {
+ retval = -ENOMEM;
+ goto error;
+ }
+
+ /* Allocate interrupt URB */
+ pdata->urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!pdata->urb) {
+ retval = -ENOMEM;
+ goto error;
+ }
+
+ /* Allocate buffer for interrupt data */
+ pdata->urbdata = usb_alloc_coherent(pdata->udev, ACD_URB_BUFFER_LEN,
+ GFP_KERNEL, &pdata->urb->transfer_dma);
+ if (!pdata->urbdata) {
+ retval = -ENOMEM;
+ dev_err(&iface->dev, "Allocating URB buffer failed\n");
+ goto error;
+ }
+
+ /* Configure interrupt URB */
+ usb_fill_int_urb(pdata->urb, udev,
+ usb_rcvintpipe(udev, int_in_endpointAddr),
+ pdata->urbdata, ACD_URB_BUFFER_LEN, appledisplay_complete,
+ pdata, 1);
+ pdata->urb->transfer_flags = URB_NO_TRANSFER_DMA_MAP;
+ if (usb_submit_urb(pdata->urb, GFP_KERNEL)) {
+ retval = -EIO;
+ dev_err(&iface->dev, "Submitting URB failed\n");
+ goto error;
+ }
+
+ /* Register backlight device */
+ snprintf(bl_name, sizeof(bl_name), "appledisplay%d",
+ atomic_inc_return(&count_displays) - 1);
+ memset(&props, 0, sizeof(struct backlight_properties));
+ props.type = BACKLIGHT_RAW;
+ props.max_brightness = 0xff;
+ pdata->bd = backlight_device_register(bl_name, NULL, pdata,
+ &appledisplay_bl_data, &props);
+ if (IS_ERR(pdata->bd)) {
+ dev_err(&iface->dev, "Backlight registration failed\n");
+ retval = PTR_ERR(pdata->bd);
+ goto error;
+ }
+
+ /* Try to get brightness */
+ brightness = appledisplay_bl_get_brightness(pdata->bd);
+
+ if (brightness < 0) {
+ retval = brightness;
+ dev_err(&iface->dev,
+ "Error while getting initial brightness: %d\n", retval);
+ goto error;
+ }
+
+ /* Set brightness in backlight device */
+ pdata->bd->props.brightness = brightness;
+
+ /* save our data pointer in the interface device */
+ usb_set_intfdata(iface, pdata);
+
+ printk(KERN_INFO "appledisplay: Apple Cinema Display connected\n");
+
+ return 0;
+
+error:
+ if (pdata) {
+ if (pdata->urb) {
+ usb_kill_urb(pdata->urb);
+ cancel_delayed_work_sync(&pdata->work);
+ usb_free_coherent(pdata->udev, ACD_URB_BUFFER_LEN,
+ pdata->urbdata, pdata->urb->transfer_dma);
+ usb_free_urb(pdata->urb);
+ }
+ if (!IS_ERR(pdata->bd))
+ backlight_device_unregister(pdata->bd);
+ kfree(pdata->msgdata);
+ }
+ usb_set_intfdata(iface, NULL);
+ kfree(pdata);
+ return retval;
+}
+
+static void appledisplay_disconnect(struct usb_interface *iface)
+{
+ struct appledisplay *pdata = usb_get_intfdata(iface);
+
+ if (pdata) {
+ usb_kill_urb(pdata->urb);
+ cancel_delayed_work_sync(&pdata->work);
+ backlight_device_unregister(pdata->bd);
+ usb_free_coherent(pdata->udev, ACD_URB_BUFFER_LEN,
+ pdata->urbdata, pdata->urb->transfer_dma);
+ usb_free_urb(pdata->urb);
+ kfree(pdata->msgdata);
+ kfree(pdata);
+ }
+
+ printk(KERN_INFO "appledisplay: Apple Cinema Display disconnected\n");
+}
+
+static struct usb_driver appledisplay_driver = {
+ .name = "appledisplay",
+ .probe = appledisplay_probe,
+ .disconnect = appledisplay_disconnect,
+ .id_table = appledisplay_table,
+};
+module_usb_driver(appledisplay_driver);
+
+MODULE_AUTHOR("Michael Hanselmann");
+MODULE_DESCRIPTION("Apple Cinema Display driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/misc/brcmstb-usb-pinmap.c b/drivers/usb/misc/brcmstb-usb-pinmap.c
new file mode 100644
index 000000000..2b2019c19
--- /dev/null
+++ b/drivers/usb/misc/brcmstb-usb-pinmap.c
@@ -0,0 +1,355 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Copyright (c) 2020, Broadcom */
+
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/device.h>
+#include <linux/of.h>
+#include <linux/kernel.h>
+#include <linux/kdebug.h>
+#include <linux/gpio/consumer.h>
+
+struct out_pin {
+ u32 enable_mask;
+ u32 value_mask;
+ u32 changed_mask;
+ u32 clr_changed_mask;
+ struct gpio_desc *gpiod;
+ const char *name;
+};
+
+struct in_pin {
+ u32 enable_mask;
+ u32 value_mask;
+ struct gpio_desc *gpiod;
+ const char *name;
+ struct brcmstb_usb_pinmap_data *pdata;
+};
+
+struct brcmstb_usb_pinmap_data {
+ void __iomem *regs;
+ int in_count;
+ struct in_pin *in_pins;
+ int out_count;
+ struct out_pin *out_pins;
+};
+
+
+static void pinmap_set(void __iomem *reg, u32 mask)
+{
+ u32 val;
+
+ val = readl(reg);
+ val |= mask;
+ writel(val, reg);
+}
+
+static void pinmap_unset(void __iomem *reg, u32 mask)
+{
+ u32 val;
+
+ val = readl(reg);
+ val &= ~mask;
+ writel(val, reg);
+}
+
+static void sync_in_pin(struct in_pin *pin)
+{
+ u32 val;
+
+ val = gpiod_get_value(pin->gpiod);
+ if (val)
+ pinmap_set(pin->pdata->regs, pin->value_mask);
+ else
+ pinmap_unset(pin->pdata->regs, pin->value_mask);
+}
+
+/*
+ * Interrupt from override register, propagate from override bit
+ * to GPIO.
+ */
+static irqreturn_t brcmstb_usb_pinmap_ovr_isr(int irq, void *dev_id)
+{
+ struct brcmstb_usb_pinmap_data *pdata = dev_id;
+ struct out_pin *pout;
+ u32 val;
+ u32 bit;
+ int x;
+
+ pr_debug("%s: reg: 0x%x\n", __func__, readl(pdata->regs));
+ pout = pdata->out_pins;
+ for (x = 0; x < pdata->out_count; x++) {
+ val = readl(pdata->regs);
+ if (val & pout->changed_mask) {
+ pinmap_set(pdata->regs, pout->clr_changed_mask);
+ pinmap_unset(pdata->regs, pout->clr_changed_mask);
+ bit = val & pout->value_mask;
+ gpiod_set_value(pout->gpiod, bit ? 1 : 0);
+ pr_debug("%s: %s bit changed state to %d\n",
+ __func__, pout->name, bit ? 1 : 0);
+ }
+ }
+ return IRQ_HANDLED;
+}
+
+/*
+ * Interrupt from GPIO, propagate from GPIO to override bit.
+ */
+static irqreturn_t brcmstb_usb_pinmap_gpio_isr(int irq, void *dev_id)
+{
+ struct in_pin *pin = dev_id;
+
+ pr_debug("%s: %s pin changed state\n", __func__, pin->name);
+ sync_in_pin(pin);
+ return IRQ_HANDLED;
+}
+
+
+static void get_pin_counts(struct device_node *dn, int *in_count,
+ int *out_count)
+{
+ int in;
+ int out;
+
+ *in_count = 0;
+ *out_count = 0;
+ in = of_property_count_strings(dn, "brcm,in-functions");
+ if (in < 0)
+ return;
+ out = of_property_count_strings(dn, "brcm,out-functions");
+ if (out < 0)
+ return;
+ *in_count = in;
+ *out_count = out;
+}
+
+static int parse_pins(struct device *dev, struct device_node *dn,
+ struct brcmstb_usb_pinmap_data *pdata)
+{
+ struct out_pin *pout;
+ struct in_pin *pin;
+ int index;
+ int res;
+ int x;
+
+ pin = pdata->in_pins;
+ for (x = 0, index = 0; x < pdata->in_count; x++) {
+ pin->gpiod = devm_gpiod_get_index(dev, "in", x, GPIOD_IN);
+ if (IS_ERR(pin->gpiod)) {
+ dev_err(dev, "Error getting gpio %s\n", pin->name);
+ return PTR_ERR(pin->gpiod);
+
+ }
+ res = of_property_read_string_index(dn, "brcm,in-functions", x,
+ &pin->name);
+ if (res < 0) {
+ dev_err(dev, "Error getting brcm,in-functions for %s\n",
+ pin->name);
+ return res;
+ }
+ res = of_property_read_u32_index(dn, "brcm,in-masks", index++,
+ &pin->enable_mask);
+ if (res < 0) {
+ dev_err(dev, "Error getting 1st brcm,in-masks for %s\n",
+ pin->name);
+ return res;
+ }
+ res = of_property_read_u32_index(dn, "brcm,in-masks", index++,
+ &pin->value_mask);
+ if (res < 0) {
+ dev_err(dev, "Error getting 2nd brcm,in-masks for %s\n",
+ pin->name);
+ return res;
+ }
+ pin->pdata = pdata;
+ pin++;
+ }
+ pout = pdata->out_pins;
+ for (x = 0, index = 0; x < pdata->out_count; x++) {
+ pout->gpiod = devm_gpiod_get_index(dev, "out", x,
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(pout->gpiod)) {
+ dev_err(dev, "Error getting gpio %s\n", pin->name);
+ return PTR_ERR(pout->gpiod);
+ }
+ res = of_property_read_string_index(dn, "brcm,out-functions", x,
+ &pout->name);
+ if (res < 0) {
+ dev_err(dev, "Error getting brcm,out-functions for %s\n",
+ pout->name);
+ return res;
+ }
+ res = of_property_read_u32_index(dn, "brcm,out-masks", index++,
+ &pout->enable_mask);
+ if (res < 0) {
+ dev_err(dev, "Error getting 1st brcm,out-masks for %s\n",
+ pout->name);
+ return res;
+ }
+ res = of_property_read_u32_index(dn, "brcm,out-masks", index++,
+ &pout->value_mask);
+ if (res < 0) {
+ dev_err(dev, "Error getting 2nd brcm,out-masks for %s\n",
+ pout->name);
+ return res;
+ }
+ res = of_property_read_u32_index(dn, "brcm,out-masks", index++,
+ &pout->changed_mask);
+ if (res < 0) {
+ dev_err(dev, "Error getting 3rd brcm,out-masks for %s\n",
+ pout->name);
+ return res;
+ }
+ res = of_property_read_u32_index(dn, "brcm,out-masks", index++,
+ &pout->clr_changed_mask);
+ if (res < 0) {
+ dev_err(dev, "Error getting 4th out-masks for %s\n",
+ pout->name);
+ return res;
+ }
+ pout++;
+ }
+ return 0;
+}
+
+static void sync_all_pins(struct brcmstb_usb_pinmap_data *pdata)
+{
+ struct out_pin *pout;
+ struct in_pin *pin;
+ int val;
+ int x;
+
+ /*
+ * Enable the override, clear any changed condition and
+ * propagate the state to the GPIO for all out pins.
+ */
+ pout = pdata->out_pins;
+ for (x = 0; x < pdata->out_count; x++) {
+ pinmap_set(pdata->regs, pout->enable_mask);
+ pinmap_set(pdata->regs, pout->clr_changed_mask);
+ pinmap_unset(pdata->regs, pout->clr_changed_mask);
+ val = readl(pdata->regs) & pout->value_mask;
+ gpiod_set_value(pout->gpiod, val ? 1 : 0);
+ pout++;
+ }
+
+ /* sync and enable all in pins. */
+ pin = pdata->in_pins;
+ for (x = 0; x < pdata->in_count; x++) {
+ sync_in_pin(pin);
+ pinmap_set(pdata->regs, pin->enable_mask);
+ pin++;
+ }
+}
+
+static int __init brcmstb_usb_pinmap_probe(struct platform_device *pdev)
+{
+ struct device_node *dn = pdev->dev.of_node;
+ struct brcmstb_usb_pinmap_data *pdata;
+ struct in_pin *pin;
+ struct resource *r;
+ int out_count;
+ int in_count;
+ int err;
+ int irq;
+ int x;
+
+ get_pin_counts(dn, &in_count, &out_count);
+ if ((in_count + out_count) == 0)
+ return -EINVAL;
+
+ r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!r)
+ return -EINVAL;
+
+ pdata = devm_kzalloc(&pdev->dev,
+ sizeof(*pdata) +
+ (sizeof(struct in_pin) * in_count) +
+ (sizeof(struct out_pin) * out_count), GFP_KERNEL);
+ if (!pdata)
+ return -ENOMEM;
+
+ pdata->in_count = in_count;
+ pdata->out_count = out_count;
+ pdata->in_pins = (struct in_pin *)(pdata + 1);
+ pdata->out_pins = (struct out_pin *)(pdata->in_pins + in_count);
+
+ pdata->regs = devm_ioremap(&pdev->dev, r->start, resource_size(r));
+ if (!pdata->regs)
+ return -ENOMEM;
+ platform_set_drvdata(pdev, pdata);
+
+ err = parse_pins(&pdev->dev, dn, pdata);
+ if (err)
+ return err;
+
+ sync_all_pins(pdata);
+
+ if (out_count) {
+
+ /* Enable interrupt for out pins */
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+ err = devm_request_irq(&pdev->dev, irq,
+ brcmstb_usb_pinmap_ovr_isr,
+ IRQF_TRIGGER_RISING,
+ pdev->name, pdata);
+ if (err < 0) {
+ dev_err(&pdev->dev, "Error requesting IRQ\n");
+ return err;
+ }
+ }
+
+ for (x = 0, pin = pdata->in_pins; x < pdata->in_count; x++, pin++) {
+ irq = gpiod_to_irq(pin->gpiod);
+ if (irq < 0) {
+ dev_err(&pdev->dev, "Error getting IRQ for %s pin\n",
+ pin->name);
+ return irq;
+ }
+ err = devm_request_irq(&pdev->dev, irq,
+ brcmstb_usb_pinmap_gpio_isr,
+ IRQF_SHARED | IRQF_TRIGGER_RISING |
+ IRQF_TRIGGER_FALLING,
+ pdev->name, pin);
+ if (err < 0) {
+ dev_err(&pdev->dev, "Error requesting IRQ for %s pin\n",
+ pin->name);
+ return err;
+ }
+ }
+
+ dev_dbg(&pdev->dev, "Driver probe succeeded\n");
+ dev_dbg(&pdev->dev, "In pin count: %d, out pin count: %d\n",
+ pdata->in_count, pdata->out_count);
+ return 0;
+}
+
+
+static const struct of_device_id brcmstb_usb_pinmap_of_match[] = {
+ { .compatible = "brcm,usb-pinmap" },
+ { },
+};
+
+static struct platform_driver brcmstb_usb_pinmap_driver = {
+ .driver = {
+ .name = "brcm-usb-pinmap",
+ .of_match_table = brcmstb_usb_pinmap_of_match,
+ },
+};
+
+static int __init brcmstb_usb_pinmap_init(void)
+{
+ return platform_driver_probe(&brcmstb_usb_pinmap_driver,
+ brcmstb_usb_pinmap_probe);
+}
+
+module_init(brcmstb_usb_pinmap_init);
+MODULE_AUTHOR("Al Cooper <alcooperx@gmail.com>");
+MODULE_DESCRIPTION("Broadcom USB Pinmap Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/misc/chaoskey.c b/drivers/usb/misc/chaoskey.c
new file mode 100644
index 000000000..87067c3d6
--- /dev/null
+++ b/drivers/usb/misc/chaoskey.c
@@ -0,0 +1,584 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * chaoskey - driver for ChaosKey device from Altus Metrum.
+ *
+ * This device provides true random numbers using a noise source based
+ * on a reverse-biased p-n junction in avalanche breakdown. More
+ * details can be found at http://chaoskey.org
+ *
+ * The driver connects to the kernel hardware RNG interface to provide
+ * entropy for /dev/random and other kernel activities. It also offers
+ * a separate /dev/ entry to allow for direct access to the random
+ * bit stream.
+ *
+ * Copyright © 2015 Keith Packard <keithp@keithp.com>
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <linux/wait.h>
+#include <linux/hw_random.h>
+#include <linux/mutex.h>
+#include <linux/uaccess.h>
+
+static struct usb_driver chaoskey_driver;
+static struct usb_class_driver chaoskey_class;
+static int chaoskey_rng_read(struct hwrng *rng, void *data,
+ size_t max, bool wait);
+
+#define usb_dbg(usb_if, format, arg...) \
+ dev_dbg(&(usb_if)->dev, format, ## arg)
+
+#define usb_err(usb_if, format, arg...) \
+ dev_err(&(usb_if)->dev, format, ## arg)
+
+/* Version Information */
+#define DRIVER_AUTHOR "Keith Packard, keithp@keithp.com"
+#define DRIVER_DESC "Altus Metrum ChaosKey driver"
+#define DRIVER_SHORT "chaoskey"
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
+
+#define CHAOSKEY_VENDOR_ID 0x1d50 /* OpenMoko */
+#define CHAOSKEY_PRODUCT_ID 0x60c6 /* ChaosKey */
+
+#define ALEA_VENDOR_ID 0x12d8 /* Araneus */
+#define ALEA_PRODUCT_ID 0x0001 /* Alea I */
+
+#define CHAOSKEY_BUF_LEN 64 /* max size of USB full speed packet */
+
+#define NAK_TIMEOUT (HZ) /* normal stall/wait timeout */
+#define ALEA_FIRST_TIMEOUT (HZ*3) /* first stall/wait timeout for Alea */
+
+#ifdef CONFIG_USB_DYNAMIC_MINORS
+#define USB_CHAOSKEY_MINOR_BASE 0
+#else
+
+/* IOWARRIOR_MINOR_BASE + 16, not official yet */
+#define USB_CHAOSKEY_MINOR_BASE 224
+#endif
+
+static const struct usb_device_id chaoskey_table[] = {
+ { USB_DEVICE(CHAOSKEY_VENDOR_ID, CHAOSKEY_PRODUCT_ID) },
+ { USB_DEVICE(ALEA_VENDOR_ID, ALEA_PRODUCT_ID) },
+ { },
+};
+MODULE_DEVICE_TABLE(usb, chaoskey_table);
+
+static void chaos_read_callback(struct urb *urb);
+
+/* Driver-local specific stuff */
+struct chaoskey {
+ struct usb_interface *interface;
+ char in_ep;
+ struct mutex lock;
+ struct mutex rng_lock;
+ int open; /* open count */
+ bool present; /* device not disconnected */
+ bool reading; /* ongoing IO */
+ bool reads_started; /* track first read for Alea */
+ int size; /* size of buf */
+ int valid; /* bytes of buf read */
+ int used; /* bytes of buf consumed */
+ char *name; /* product + serial */
+ struct hwrng hwrng; /* Embedded struct for hwrng */
+ int hwrng_registered; /* registered with hwrng API */
+ wait_queue_head_t wait_q; /* for timeouts */
+ struct urb *urb; /* for performing IO */
+ char *buf;
+};
+
+static void chaoskey_free(struct chaoskey *dev)
+{
+ if (dev) {
+ usb_dbg(dev->interface, "free");
+ usb_free_urb(dev->urb);
+ kfree(dev->name);
+ kfree(dev->buf);
+ usb_put_intf(dev->interface);
+ kfree(dev);
+ }
+}
+
+static int chaoskey_probe(struct usb_interface *interface,
+ const struct usb_device_id *id)
+{
+ struct usb_device *udev = interface_to_usbdev(interface);
+ struct usb_host_interface *altsetting = interface->cur_altsetting;
+ struct usb_endpoint_descriptor *epd;
+ int in_ep;
+ struct chaoskey *dev;
+ int result = -ENOMEM;
+ int size;
+ int res;
+
+ usb_dbg(interface, "probe %s-%s", udev->product, udev->serial);
+
+ /* Find the first bulk IN endpoint and its packet size */
+ res = usb_find_bulk_in_endpoint(altsetting, &epd);
+ if (res) {
+ usb_dbg(interface, "no IN endpoint found");
+ return res;
+ }
+
+ in_ep = usb_endpoint_num(epd);
+ size = usb_endpoint_maxp(epd);
+
+ /* Validate endpoint and size */
+ if (size <= 0) {
+ usb_dbg(interface, "invalid size (%d)", size);
+ return -ENODEV;
+ }
+
+ if (size > CHAOSKEY_BUF_LEN) {
+ usb_dbg(interface, "size reduced from %d to %d\n",
+ size, CHAOSKEY_BUF_LEN);
+ size = CHAOSKEY_BUF_LEN;
+ }
+
+ /* Looks good, allocate and initialize */
+
+ dev = kzalloc(sizeof(struct chaoskey), GFP_KERNEL);
+
+ if (dev == NULL)
+ goto out;
+
+ dev->interface = usb_get_intf(interface);
+
+ dev->buf = kmalloc(size, GFP_KERNEL);
+
+ if (dev->buf == NULL)
+ goto out;
+
+ dev->urb = usb_alloc_urb(0, GFP_KERNEL);
+
+ if (!dev->urb)
+ goto out;
+
+ usb_fill_bulk_urb(dev->urb,
+ udev,
+ usb_rcvbulkpipe(udev, in_ep),
+ dev->buf,
+ size,
+ chaos_read_callback,
+ dev);
+
+ /* Construct a name using the product and serial values. Each
+ * device needs a unique name for the hwrng code
+ */
+
+ if (udev->product && udev->serial) {
+ dev->name = kasprintf(GFP_KERNEL, "%s-%s", udev->product,
+ udev->serial);
+ if (dev->name == NULL)
+ goto out;
+ }
+
+ dev->in_ep = in_ep;
+
+ if (le16_to_cpu(udev->descriptor.idVendor) != ALEA_VENDOR_ID)
+ dev->reads_started = true;
+
+ dev->size = size;
+ dev->present = true;
+
+ init_waitqueue_head(&dev->wait_q);
+
+ mutex_init(&dev->lock);
+ mutex_init(&dev->rng_lock);
+
+ usb_set_intfdata(interface, dev);
+
+ result = usb_register_dev(interface, &chaoskey_class);
+ if (result) {
+ usb_err(interface, "Unable to allocate minor number.");
+ goto out;
+ }
+
+ dev->hwrng.name = dev->name ? dev->name : chaoskey_driver.name;
+ dev->hwrng.read = chaoskey_rng_read;
+ dev->hwrng.quality = 1024;
+
+ dev->hwrng_registered = (hwrng_register(&dev->hwrng) == 0);
+ if (!dev->hwrng_registered)
+ usb_err(interface, "Unable to register with hwrng");
+
+ usb_enable_autosuspend(udev);
+
+ usb_dbg(interface, "chaoskey probe success, size %d", dev->size);
+ return 0;
+
+out:
+ usb_set_intfdata(interface, NULL);
+ chaoskey_free(dev);
+ return result;
+}
+
+static void chaoskey_disconnect(struct usb_interface *interface)
+{
+ struct chaoskey *dev;
+
+ usb_dbg(interface, "disconnect");
+ dev = usb_get_intfdata(interface);
+ if (!dev) {
+ usb_dbg(interface, "disconnect failed - no dev");
+ return;
+ }
+
+ if (dev->hwrng_registered)
+ hwrng_unregister(&dev->hwrng);
+
+ usb_deregister_dev(interface, &chaoskey_class);
+
+ usb_set_intfdata(interface, NULL);
+ mutex_lock(&dev->lock);
+
+ dev->present = false;
+ usb_poison_urb(dev->urb);
+
+ if (!dev->open) {
+ mutex_unlock(&dev->lock);
+ chaoskey_free(dev);
+ } else
+ mutex_unlock(&dev->lock);
+
+ usb_dbg(interface, "disconnect done");
+}
+
+static int chaoskey_open(struct inode *inode, struct file *file)
+{
+ struct chaoskey *dev;
+ struct usb_interface *interface;
+
+ /* get the interface from minor number and driver information */
+ interface = usb_find_interface(&chaoskey_driver, iminor(inode));
+ if (!interface)
+ return -ENODEV;
+
+ usb_dbg(interface, "open");
+
+ dev = usb_get_intfdata(interface);
+ if (!dev) {
+ usb_dbg(interface, "open (dev)");
+ return -ENODEV;
+ }
+
+ file->private_data = dev;
+ mutex_lock(&dev->lock);
+ ++dev->open;
+ mutex_unlock(&dev->lock);
+
+ usb_dbg(interface, "open success");
+ return 0;
+}
+
+static int chaoskey_release(struct inode *inode, struct file *file)
+{
+ struct chaoskey *dev = file->private_data;
+ struct usb_interface *interface;
+
+ if (dev == NULL)
+ return -ENODEV;
+
+ interface = dev->interface;
+
+ usb_dbg(interface, "release");
+
+ mutex_lock(&dev->lock);
+
+ usb_dbg(interface, "open count at release is %d", dev->open);
+
+ if (dev->open <= 0) {
+ usb_dbg(interface, "invalid open count (%d)", dev->open);
+ mutex_unlock(&dev->lock);
+ return -ENODEV;
+ }
+
+ --dev->open;
+
+ if (!dev->present) {
+ if (dev->open == 0) {
+ mutex_unlock(&dev->lock);
+ chaoskey_free(dev);
+ } else
+ mutex_unlock(&dev->lock);
+ } else
+ mutex_unlock(&dev->lock);
+
+ usb_dbg(interface, "release success");
+ return 0;
+}
+
+static void chaos_read_callback(struct urb *urb)
+{
+ struct chaoskey *dev = urb->context;
+ int status = urb->status;
+
+ usb_dbg(dev->interface, "callback status (%d)", status);
+
+ if (status == 0)
+ dev->valid = urb->actual_length;
+ else
+ dev->valid = 0;
+
+ dev->used = 0;
+
+ /* must be seen first before validity is announced */
+ smp_wmb();
+
+ dev->reading = false;
+ wake_up(&dev->wait_q);
+}
+
+/* Fill the buffer. Called with dev->lock held
+ */
+static int _chaoskey_fill(struct chaoskey *dev)
+{
+ DEFINE_WAIT(wait);
+ int result;
+ bool started;
+
+ usb_dbg(dev->interface, "fill");
+
+ /* Return immediately if someone called before the buffer was
+ * empty */
+ if (dev->valid != dev->used) {
+ usb_dbg(dev->interface, "not empty yet (valid %d used %d)",
+ dev->valid, dev->used);
+ return 0;
+ }
+
+ /* Bail if the device has been removed */
+ if (!dev->present) {
+ usb_dbg(dev->interface, "device not present");
+ return -ENODEV;
+ }
+
+ /* Make sure the device is awake */
+ result = usb_autopm_get_interface(dev->interface);
+ if (result) {
+ usb_dbg(dev->interface, "wakeup failed (result %d)", result);
+ return result;
+ }
+
+ dev->reading = true;
+ result = usb_submit_urb(dev->urb, GFP_KERNEL);
+ if (result < 0) {
+ result = usb_translate_errors(result);
+ dev->reading = false;
+ goto out;
+ }
+
+ /* The first read on the Alea takes a little under 2 seconds.
+ * Reads after the first read take only a few microseconds
+ * though. Presumably the entropy-generating circuit needs
+ * time to ramp up. So, we wait longer on the first read.
+ */
+ started = dev->reads_started;
+ dev->reads_started = true;
+ result = wait_event_interruptible_timeout(
+ dev->wait_q,
+ !dev->reading,
+ (started ? NAK_TIMEOUT : ALEA_FIRST_TIMEOUT) );
+
+ if (result < 0) {
+ usb_kill_urb(dev->urb);
+ goto out;
+ }
+
+ if (result == 0) {
+ result = -ETIMEDOUT;
+ usb_kill_urb(dev->urb);
+ } else {
+ result = dev->valid;
+ }
+out:
+ /* Let the device go back to sleep eventually */
+ usb_autopm_put_interface(dev->interface);
+
+ usb_dbg(dev->interface, "read %d bytes", dev->valid);
+
+ return result;
+}
+
+static ssize_t chaoskey_read(struct file *file,
+ char __user *buffer,
+ size_t count,
+ loff_t *ppos)
+{
+ struct chaoskey *dev;
+ ssize_t read_count = 0;
+ int this_time;
+ int result = 0;
+ unsigned long remain;
+
+ dev = file->private_data;
+
+ if (dev == NULL || !dev->present)
+ return -ENODEV;
+
+ usb_dbg(dev->interface, "read %zu", count);
+
+ while (count > 0) {
+
+ /* Grab the rng_lock briefly to ensure that the hwrng interface
+ * gets priority over other user access
+ */
+ result = mutex_lock_interruptible(&dev->rng_lock);
+ if (result)
+ goto bail;
+ mutex_unlock(&dev->rng_lock);
+
+ result = mutex_lock_interruptible(&dev->lock);
+ if (result)
+ goto bail;
+ if (dev->valid == dev->used) {
+ result = _chaoskey_fill(dev);
+ if (result < 0) {
+ mutex_unlock(&dev->lock);
+ goto bail;
+ }
+ }
+
+ this_time = dev->valid - dev->used;
+ if (this_time > count)
+ this_time = count;
+
+ remain = copy_to_user(buffer, dev->buf + dev->used, this_time);
+ if (remain) {
+ result = -EFAULT;
+
+ /* Consume the bytes that were copied so we don't leak
+ * data to user space
+ */
+ dev->used += this_time - remain;
+ mutex_unlock(&dev->lock);
+ goto bail;
+ }
+
+ count -= this_time;
+ read_count += this_time;
+ buffer += this_time;
+ dev->used += this_time;
+ mutex_unlock(&dev->lock);
+ }
+bail:
+ if (read_count) {
+ usb_dbg(dev->interface, "read %zu bytes", read_count);
+ return read_count;
+ }
+ usb_dbg(dev->interface, "empty read, result %d", result);
+ if (result == -ETIMEDOUT)
+ result = -EAGAIN;
+ return result;
+}
+
+static int chaoskey_rng_read(struct hwrng *rng, void *data,
+ size_t max, bool wait)
+{
+ struct chaoskey *dev = container_of(rng, struct chaoskey, hwrng);
+ int this_time;
+
+ usb_dbg(dev->interface, "rng_read max %zu wait %d", max, wait);
+
+ if (!dev->present) {
+ usb_dbg(dev->interface, "device not present");
+ return 0;
+ }
+
+ /* Hold the rng_lock until we acquire the device lock so that
+ * this operation gets priority over other user access to the
+ * device
+ */
+ mutex_lock(&dev->rng_lock);
+
+ mutex_lock(&dev->lock);
+
+ mutex_unlock(&dev->rng_lock);
+
+ /* Try to fill the buffer if empty. It doesn't actually matter
+ * if _chaoskey_fill works; we'll just return zero bytes as
+ * the buffer will still be empty
+ */
+ if (dev->valid == dev->used)
+ (void) _chaoskey_fill(dev);
+
+ this_time = dev->valid - dev->used;
+ if (this_time > max)
+ this_time = max;
+
+ memcpy(data, dev->buf + dev->used, this_time);
+
+ dev->used += this_time;
+
+ mutex_unlock(&dev->lock);
+
+ usb_dbg(dev->interface, "rng_read this_time %d\n", this_time);
+ return this_time;
+}
+
+#ifdef CONFIG_PM
+static int chaoskey_suspend(struct usb_interface *interface,
+ pm_message_t message)
+{
+ usb_dbg(interface, "suspend");
+ return 0;
+}
+
+static int chaoskey_resume(struct usb_interface *interface)
+{
+ struct chaoskey *dev;
+ struct usb_device *udev = interface_to_usbdev(interface);
+
+ usb_dbg(interface, "resume");
+ dev = usb_get_intfdata(interface);
+
+ /*
+ * We may have lost power.
+ * In that case the device that needs a long time
+ * for the first requests needs an extended timeout
+ * again
+ */
+ if (le16_to_cpu(udev->descriptor.idVendor) == ALEA_VENDOR_ID)
+ dev->reads_started = false;
+
+ return 0;
+}
+#else
+#define chaoskey_suspend NULL
+#define chaoskey_resume NULL
+#endif
+
+/* file operation pointers */
+static const struct file_operations chaoskey_fops = {
+ .owner = THIS_MODULE,
+ .read = chaoskey_read,
+ .open = chaoskey_open,
+ .release = chaoskey_release,
+ .llseek = default_llseek,
+};
+
+/* class driver information */
+static struct usb_class_driver chaoskey_class = {
+ .name = "chaoskey%d",
+ .fops = &chaoskey_fops,
+ .minor_base = USB_CHAOSKEY_MINOR_BASE,
+};
+
+/* usb specific object needed to register this driver with the usb subsystem */
+static struct usb_driver chaoskey_driver = {
+ .name = DRIVER_SHORT,
+ .probe = chaoskey_probe,
+ .disconnect = chaoskey_disconnect,
+ .suspend = chaoskey_suspend,
+ .resume = chaoskey_resume,
+ .reset_resume = chaoskey_resume,
+ .id_table = chaoskey_table,
+ .supports_autosuspend = 1,
+};
+
+module_usb_driver(chaoskey_driver);
+
diff --git a/drivers/usb/misc/cypress_cy7c63.c b/drivers/usb/misc/cypress_cy7c63.c
new file mode 100644
index 000000000..14faec51d
--- /dev/null
+++ b/drivers/usb/misc/cypress_cy7c63.c
@@ -0,0 +1,259 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+* cypress_cy7c63.c
+*
+* Copyright (c) 2006-2007 Oliver Bock (bock@tfh-berlin.de)
+*
+* This driver is based on the Cypress USB Driver by Marcus Maul
+* (cyport) and the 2.0 version of Greg Kroah-Hartman's
+* USB Skeleton driver.
+*
+* This is a generic driver for the Cypress CY7C63xxx family.
+* For the time being it enables you to read from and write to
+* the single I/O ports of the device.
+*
+* Supported vendors: AK Modul-Bus Computer GmbH
+* (Firmware "Port-Chip")
+*
+* Supported devices: CY7C63001A-PC
+* CY7C63001C-PXC
+* CY7C63001C-SXC
+*
+* Supported functions: Read/Write Ports
+*
+*
+* For up-to-date information please visit:
+* http://www.obock.de/kernel/cypress
+*/
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+
+#define DRIVER_AUTHOR "Oliver Bock (bock@tfh-berlin.de)"
+#define DRIVER_DESC "Cypress CY7C63xxx USB driver"
+
+#define CYPRESS_VENDOR_ID 0xa2c
+#define CYPRESS_PRODUCT_ID 0x8
+
+#define CYPRESS_READ_PORT 0x4
+#define CYPRESS_WRITE_PORT 0x5
+
+#define CYPRESS_READ_RAM 0x2
+#define CYPRESS_WRITE_RAM 0x3
+#define CYPRESS_READ_ROM 0x1
+
+#define CYPRESS_READ_PORT_ID0 0
+#define CYPRESS_WRITE_PORT_ID0 0
+#define CYPRESS_READ_PORT_ID1 0x2
+#define CYPRESS_WRITE_PORT_ID1 1
+
+#define CYPRESS_MAX_REQSIZE 8
+
+
+/* table of devices that work with this driver */
+static const struct usb_device_id cypress_table[] = {
+ { USB_DEVICE(CYPRESS_VENDOR_ID, CYPRESS_PRODUCT_ID) },
+ { }
+};
+MODULE_DEVICE_TABLE(usb, cypress_table);
+
+/* structure to hold all of our device specific stuff */
+struct cypress {
+ struct usb_device * udev;
+ unsigned char port[2];
+};
+
+/* used to send usb control messages to device */
+static int vendor_command(struct cypress *dev, unsigned char request,
+ unsigned char address, unsigned char data)
+{
+ int retval = 0;
+ unsigned int pipe;
+ unsigned char *iobuf;
+
+ /* allocate some memory for the i/o buffer*/
+ iobuf = kzalloc(CYPRESS_MAX_REQSIZE, GFP_KERNEL);
+ if (!iobuf) {
+ retval = -ENOMEM;
+ goto error;
+ }
+
+ dev_dbg(&dev->udev->dev, "Sending usb_control_msg (data: %d)\n", data);
+
+ /* prepare usb control message and send it upstream */
+ pipe = usb_rcvctrlpipe(dev->udev, 0);
+ retval = usb_control_msg(dev->udev, pipe, request,
+ USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_OTHER,
+ address, data, iobuf, CYPRESS_MAX_REQSIZE,
+ USB_CTRL_GET_TIMEOUT);
+
+ /* store returned data (more READs to be added) */
+ switch (request) {
+ case CYPRESS_READ_PORT:
+ if (address == CYPRESS_READ_PORT_ID0) {
+ dev->port[0] = iobuf[1];
+ dev_dbg(&dev->udev->dev,
+ "READ_PORT0 returned: %d\n",
+ dev->port[0]);
+ }
+ else if (address == CYPRESS_READ_PORT_ID1) {
+ dev->port[1] = iobuf[1];
+ dev_dbg(&dev->udev->dev,
+ "READ_PORT1 returned: %d\n",
+ dev->port[1]);
+ }
+ break;
+ }
+
+ kfree(iobuf);
+error:
+ return retval;
+}
+
+/* write port value */
+static ssize_t write_port(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count,
+ int port_num, int write_id)
+{
+ int value = -1;
+ int result = 0;
+
+ struct usb_interface *intf = to_usb_interface(dev);
+ struct cypress *cyp = usb_get_intfdata(intf);
+
+ dev_dbg(&cyp->udev->dev, "WRITE_PORT%d called\n", port_num);
+
+ /* validate input data */
+ if (sscanf(buf, "%d", &value) < 1) {
+ result = -EINVAL;
+ goto error;
+ }
+ if (value < 0 || value > 255) {
+ result = -EINVAL;
+ goto error;
+ }
+
+ result = vendor_command(cyp, CYPRESS_WRITE_PORT, write_id,
+ (unsigned char)value);
+
+ dev_dbg(&cyp->udev->dev, "Result of vendor_command: %d\n\n", result);
+error:
+ return result < 0 ? result : count;
+}
+
+/* attribute callback handler (write) */
+static ssize_t port0_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ return write_port(dev, attr, buf, count, 0, CYPRESS_WRITE_PORT_ID0);
+}
+
+/* attribute callback handler (write) */
+static ssize_t port1_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ return write_port(dev, attr, buf, count, 1, CYPRESS_WRITE_PORT_ID1);
+}
+
+/* read port value */
+static ssize_t read_port(struct device *dev, struct device_attribute *attr,
+ char *buf, int port_num, int read_id)
+{
+ int result = 0;
+
+ struct usb_interface *intf = to_usb_interface(dev);
+ struct cypress *cyp = usb_get_intfdata(intf);
+
+ dev_dbg(&cyp->udev->dev, "READ_PORT%d called\n", port_num);
+
+ result = vendor_command(cyp, CYPRESS_READ_PORT, read_id, 0);
+
+ dev_dbg(&cyp->udev->dev, "Result of vendor_command: %d\n\n", result);
+
+ return sprintf(buf, "%d", cyp->port[port_num]);
+}
+
+/* attribute callback handler (read) */
+static ssize_t port0_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return read_port(dev, attr, buf, 0, CYPRESS_READ_PORT_ID0);
+}
+static DEVICE_ATTR_RW(port0);
+
+/* attribute callback handler (read) */
+static ssize_t port1_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return read_port(dev, attr, buf, 1, CYPRESS_READ_PORT_ID1);
+}
+static DEVICE_ATTR_RW(port1);
+
+static struct attribute *cypress_attrs[] = {
+ &dev_attr_port0.attr,
+ &dev_attr_port1.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(cypress);
+
+static int cypress_probe(struct usb_interface *interface,
+ const struct usb_device_id *id)
+{
+ struct cypress *dev = NULL;
+ int retval = -ENOMEM;
+
+ /* allocate memory for our device state and initialize it */
+ dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+ if (!dev)
+ goto error_mem;
+
+ dev->udev = usb_get_dev(interface_to_usbdev(interface));
+
+ /* save our data pointer in this interface device */
+ usb_set_intfdata(interface, dev);
+
+ /* let the user know that the device is now attached */
+ dev_info(&interface->dev,
+ "Cypress CY7C63xxx device now attached\n");
+ return 0;
+
+error_mem:
+ return retval;
+}
+
+static void cypress_disconnect(struct usb_interface *interface)
+{
+ struct cypress *dev;
+
+ dev = usb_get_intfdata(interface);
+
+ /* the intfdata can be set to NULL only after the
+ * device files have been removed */
+ usb_set_intfdata(interface, NULL);
+
+ usb_put_dev(dev->udev);
+
+ dev_info(&interface->dev,
+ "Cypress CY7C63xxx device now disconnected\n");
+
+ kfree(dev);
+}
+
+static struct usb_driver cypress_driver = {
+ .name = "cypress_cy7c63",
+ .probe = cypress_probe,
+ .disconnect = cypress_disconnect,
+ .id_table = cypress_table,
+ .dev_groups = cypress_groups,
+};
+
+module_usb_driver(cypress_driver);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/misc/cytherm.c b/drivers/usb/misc/cytherm.c
new file mode 100644
index 000000000..3e3802aae
--- /dev/null
+++ b/drivers/usb/misc/cytherm.c
@@ -0,0 +1,357 @@
+// SPDX-License-Identifier: GPL-2.0
+/* -*- linux-c -*-
+ * Cypress USB Thermometer driver
+ *
+ * Copyright (c) 2004 Erik Rigtorp <erkki@linux.nu> <erik@rigtorp.com>
+ *
+ * This driver works with Elektor magazine USB Interface as published in
+ * issue #291. It should also work with the original starter kit/demo board
+ * from Cypress.
+ */
+
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+
+#define DRIVER_AUTHOR "Erik Rigtorp"
+#define DRIVER_DESC "Cypress USB Thermometer driver"
+
+#define USB_SKEL_VENDOR_ID 0x04b4
+#define USB_SKEL_PRODUCT_ID 0x0002
+
+static const struct usb_device_id id_table[] = {
+ { USB_DEVICE(USB_SKEL_VENDOR_ID, USB_SKEL_PRODUCT_ID) },
+ { }
+};
+MODULE_DEVICE_TABLE (usb, id_table);
+
+/* Structure to hold all of our device specific stuff */
+struct usb_cytherm {
+ struct usb_device *udev; /* save off the usb device pointer */
+ struct usb_interface *interface; /* the interface for this device */
+ int brightness;
+};
+
+
+/* Vendor requests */
+/* They all operate on one byte at a time */
+#define PING 0x00
+#define READ_ROM 0x01 /* Reads form ROM, value = address */
+#define READ_RAM 0x02 /* Reads form RAM, value = address */
+#define WRITE_RAM 0x03 /* Write to RAM, value = address, index = data */
+#define READ_PORT 0x04 /* Reads from port, value = address */
+#define WRITE_PORT 0x05 /* Write to port, value = address, index = data */
+
+
+/* Send a vendor command to device */
+static int vendor_command(struct usb_device *dev, unsigned char request,
+ unsigned char value, unsigned char index,
+ void *buf, int size)
+{
+ return usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
+ request,
+ USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_OTHER,
+ value,
+ index, buf, size,
+ USB_CTRL_GET_TIMEOUT);
+}
+
+
+
+#define BRIGHTNESS 0x2c /* RAM location for brightness value */
+#define BRIGHTNESS_SEM 0x2b /* RAM location for brightness semaphore */
+
+static ssize_t brightness_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct usb_interface *intf = to_usb_interface(dev);
+ struct usb_cytherm *cytherm = usb_get_intfdata(intf);
+
+ return sprintf(buf, "%i", cytherm->brightness);
+}
+
+static ssize_t brightness_store(struct device *dev, struct device_attribute *attr, const char *buf,
+ size_t count)
+{
+ struct usb_interface *intf = to_usb_interface(dev);
+ struct usb_cytherm *cytherm = usb_get_intfdata(intf);
+
+ unsigned char *buffer;
+ int retval;
+
+ buffer = kmalloc(8, GFP_KERNEL);
+ if (!buffer)
+ return 0;
+
+ cytherm->brightness = simple_strtoul(buf, NULL, 10);
+
+ if (cytherm->brightness > 0xFF)
+ cytherm->brightness = 0xFF;
+ else if (cytherm->brightness < 0)
+ cytherm->brightness = 0;
+
+ /* Set brightness */
+ retval = vendor_command(cytherm->udev, WRITE_RAM, BRIGHTNESS,
+ cytherm->brightness, buffer, 8);
+ if (retval)
+ dev_dbg(&cytherm->udev->dev, "retval = %d\n", retval);
+ /* Inform µC that we have changed the brightness setting */
+ retval = vendor_command(cytherm->udev, WRITE_RAM, BRIGHTNESS_SEM,
+ 0x01, buffer, 8);
+ if (retval)
+ dev_dbg(&cytherm->udev->dev, "retval = %d\n", retval);
+
+ kfree(buffer);
+
+ return count;
+}
+static DEVICE_ATTR_RW(brightness);
+
+
+#define TEMP 0x33 /* RAM location for temperature */
+#define SIGN 0x34 /* RAM location for temperature sign */
+
+static ssize_t temp_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+
+ struct usb_interface *intf = to_usb_interface(dev);
+ struct usb_cytherm *cytherm = usb_get_intfdata(intf);
+
+ int retval;
+ unsigned char *buffer;
+
+ int temp, sign;
+
+ buffer = kmalloc(8, GFP_KERNEL);
+ if (!buffer)
+ return 0;
+
+ /* read temperature */
+ retval = vendor_command(cytherm->udev, READ_RAM, TEMP, 0, buffer, 8);
+ if (retval)
+ dev_dbg(&cytherm->udev->dev, "retval = %d\n", retval);
+ temp = buffer[1];
+
+ /* read sign */
+ retval = vendor_command(cytherm->udev, READ_RAM, SIGN, 0, buffer, 8);
+ if (retval)
+ dev_dbg(&cytherm->udev->dev, "retval = %d\n", retval);
+ sign = buffer[1];
+
+ kfree(buffer);
+
+ return sprintf(buf, "%c%i.%i", sign ? '-' : '+', temp >> 1,
+ 5*(temp - ((temp >> 1) << 1)));
+}
+static DEVICE_ATTR_RO(temp);
+
+
+#define BUTTON 0x7a
+
+static ssize_t button_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+
+ struct usb_interface *intf = to_usb_interface(dev);
+ struct usb_cytherm *cytherm = usb_get_intfdata(intf);
+
+ int retval;
+ unsigned char *buffer;
+
+ buffer = kmalloc(8, GFP_KERNEL);
+ if (!buffer)
+ return 0;
+
+ /* check button */
+ retval = vendor_command(cytherm->udev, READ_RAM, BUTTON, 0, buffer, 8);
+ if (retval)
+ dev_dbg(&cytherm->udev->dev, "retval = %d\n", retval);
+
+ retval = buffer[1];
+
+ kfree(buffer);
+
+ if (retval)
+ return sprintf(buf, "1");
+ else
+ return sprintf(buf, "0");
+}
+static DEVICE_ATTR_RO(button);
+
+
+static ssize_t port0_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct usb_interface *intf = to_usb_interface(dev);
+ struct usb_cytherm *cytherm = usb_get_intfdata(intf);
+
+ int retval;
+ unsigned char *buffer;
+
+ buffer = kmalloc(8, GFP_KERNEL);
+ if (!buffer)
+ return 0;
+
+ retval = vendor_command(cytherm->udev, READ_PORT, 0, 0, buffer, 8);
+ if (retval)
+ dev_dbg(&cytherm->udev->dev, "retval = %d\n", retval);
+
+ retval = buffer[1];
+
+ kfree(buffer);
+
+ return sprintf(buf, "%d", retval);
+}
+
+
+static ssize_t port0_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct usb_interface *intf = to_usb_interface(dev);
+ struct usb_cytherm *cytherm = usb_get_intfdata(intf);
+
+ unsigned char *buffer;
+ int retval;
+ int tmp;
+
+ buffer = kmalloc(8, GFP_KERNEL);
+ if (!buffer)
+ return 0;
+
+ tmp = simple_strtoul(buf, NULL, 10);
+
+ if (tmp > 0xFF)
+ tmp = 0xFF;
+ else if (tmp < 0)
+ tmp = 0;
+
+ retval = vendor_command(cytherm->udev, WRITE_PORT, 0,
+ tmp, buffer, 8);
+ if (retval)
+ dev_dbg(&cytherm->udev->dev, "retval = %d\n", retval);
+
+ kfree(buffer);
+
+ return count;
+}
+static DEVICE_ATTR_RW(port0);
+
+static ssize_t port1_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct usb_interface *intf = to_usb_interface(dev);
+ struct usb_cytherm *cytherm = usb_get_intfdata(intf);
+
+ int retval;
+ unsigned char *buffer;
+
+ buffer = kmalloc(8, GFP_KERNEL);
+ if (!buffer)
+ return 0;
+
+ retval = vendor_command(cytherm->udev, READ_PORT, 1, 0, buffer, 8);
+ if (retval)
+ dev_dbg(&cytherm->udev->dev, "retval = %d\n", retval);
+
+ retval = buffer[1];
+
+ kfree(buffer);
+
+ return sprintf(buf, "%d", retval);
+}
+
+
+static ssize_t port1_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct usb_interface *intf = to_usb_interface(dev);
+ struct usb_cytherm *cytherm = usb_get_intfdata(intf);
+
+ unsigned char *buffer;
+ int retval;
+ int tmp;
+
+ buffer = kmalloc(8, GFP_KERNEL);
+ if (!buffer)
+ return 0;
+
+ tmp = simple_strtoul(buf, NULL, 10);
+
+ if (tmp > 0xFF)
+ tmp = 0xFF;
+ else if (tmp < 0)
+ tmp = 0;
+
+ retval = vendor_command(cytherm->udev, WRITE_PORT, 1,
+ tmp, buffer, 8);
+ if (retval)
+ dev_dbg(&cytherm->udev->dev, "retval = %d\n", retval);
+
+ kfree(buffer);
+
+ return count;
+}
+static DEVICE_ATTR_RW(port1);
+
+static struct attribute *cytherm_attrs[] = {
+ &dev_attr_brightness.attr,
+ &dev_attr_temp.attr,
+ &dev_attr_button.attr,
+ &dev_attr_port0.attr,
+ &dev_attr_port1.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(cytherm);
+
+static int cytherm_probe(struct usb_interface *interface,
+ const struct usb_device_id *id)
+{
+ struct usb_device *udev = interface_to_usbdev(interface);
+ struct usb_cytherm *dev = NULL;
+ int retval = -ENOMEM;
+
+ dev = kzalloc (sizeof(struct usb_cytherm), GFP_KERNEL);
+ if (!dev)
+ goto error_mem;
+
+ dev->udev = usb_get_dev(udev);
+
+ usb_set_intfdata (interface, dev);
+
+ dev->brightness = 0xFF;
+
+ dev_info (&interface->dev,
+ "Cypress thermometer device now attached\n");
+ return 0;
+
+error_mem:
+ return retval;
+}
+
+static void cytherm_disconnect(struct usb_interface *interface)
+{
+ struct usb_cytherm *dev;
+
+ dev = usb_get_intfdata (interface);
+
+ /* first remove the files, then NULL the pointer */
+ usb_set_intfdata (interface, NULL);
+
+ usb_put_dev(dev->udev);
+
+ kfree(dev);
+
+ dev_info(&interface->dev, "Cypress thermometer now disconnected\n");
+}
+
+/* usb specific object needed to register this driver with the usb subsystem */
+static struct usb_driver cytherm_driver = {
+ .name = "cytherm",
+ .probe = cytherm_probe,
+ .disconnect = cytherm_disconnect,
+ .id_table = id_table,
+ .dev_groups = cytherm_groups,
+};
+
+module_usb_driver(cytherm_driver);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/misc/ehset.c b/drivers/usb/misc/ehset.c
new file mode 100644
index 000000000..36b6e9fa7
--- /dev/null
+++ b/drivers/usb/misc/ehset.c
@@ -0,0 +1,190 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2010-2013, The Linux Foundation. All rights reserved.
+ */
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <linux/usb/ch11.h>
+
+#define TEST_SE0_NAK_PID 0x0101
+#define TEST_J_PID 0x0102
+#define TEST_K_PID 0x0103
+#define TEST_PACKET_PID 0x0104
+#define TEST_HS_HOST_PORT_SUSPEND_RESUME 0x0106
+#define TEST_SINGLE_STEP_GET_DEV_DESC 0x0107
+#define TEST_SINGLE_STEP_SET_FEATURE 0x0108
+
+extern const struct usb_device_id *usb_device_match_id(struct usb_device *udev,
+ const struct usb_device_id *id);
+
+/*
+ * A list of USB hubs which requires to disable the power
+ * to the port before starting the testing procedures.
+ */
+static const struct usb_device_id ehset_hub_list[] = {
+ { USB_DEVICE(0x0424, 0x4502) },
+ { USB_DEVICE(0x0424, 0x4913) },
+ { USB_DEVICE(0x0451, 0x8027) },
+ { }
+};
+
+static int ehset_prepare_port_for_testing(struct usb_device *hub_udev, u16 portnum)
+{
+ int ret = 0;
+
+ /*
+ * The USB2.0 spec chapter 11.24.2.13 says that the USB port which is
+ * going under test needs to be put in suspend before sending the
+ * test command. Most hubs don't enforce this precondition, but there
+ * are some hubs which needs to disable the power to the port before
+ * starting the test.
+ */
+ if (usb_device_match_id(hub_udev, ehset_hub_list)) {
+ ret = usb_control_msg_send(hub_udev, 0, USB_REQ_CLEAR_FEATURE,
+ USB_RT_PORT, USB_PORT_FEAT_ENABLE,
+ portnum, NULL, 0, 1000, GFP_KERNEL);
+ /*
+ * Wait for the port to be disabled. It's an arbitrary value
+ * which worked every time.
+ */
+ msleep(100);
+ } else {
+ /*
+ * For the hubs which are compliant with the spec,
+ * put the port in SUSPEND.
+ */
+ ret = usb_control_msg_send(hub_udev, 0, USB_REQ_SET_FEATURE,
+ USB_RT_PORT, USB_PORT_FEAT_SUSPEND,
+ portnum, NULL, 0, 1000, GFP_KERNEL);
+ }
+ return ret;
+}
+
+static int ehset_probe(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ int ret = -EINVAL;
+ struct usb_device *dev = interface_to_usbdev(intf);
+ struct usb_device *hub_udev = dev->parent;
+ struct usb_device_descriptor buf;
+ u8 portnum = dev->portnum;
+ u16 test_pid = le16_to_cpu(dev->descriptor.idProduct);
+
+ switch (test_pid) {
+ case TEST_SE0_NAK_PID:
+ ret = ehset_prepare_port_for_testing(hub_udev, portnum);
+ if (ret < 0)
+ break;
+ ret = usb_control_msg_send(hub_udev, 0, USB_REQ_SET_FEATURE,
+ USB_RT_PORT, USB_PORT_FEAT_TEST,
+ (USB_TEST_SE0_NAK << 8) | portnum,
+ NULL, 0, 1000, GFP_KERNEL);
+ break;
+ case TEST_J_PID:
+ ret = ehset_prepare_port_for_testing(hub_udev, portnum);
+ if (ret < 0)
+ break;
+ ret = usb_control_msg_send(hub_udev, 0, USB_REQ_SET_FEATURE,
+ USB_RT_PORT, USB_PORT_FEAT_TEST,
+ (USB_TEST_J << 8) | portnum, NULL, 0,
+ 1000, GFP_KERNEL);
+ break;
+ case TEST_K_PID:
+ ret = ehset_prepare_port_for_testing(hub_udev, portnum);
+ if (ret < 0)
+ break;
+ ret = usb_control_msg_send(hub_udev, 0, USB_REQ_SET_FEATURE,
+ USB_RT_PORT, USB_PORT_FEAT_TEST,
+ (USB_TEST_K << 8) | portnum, NULL, 0,
+ 1000, GFP_KERNEL);
+ break;
+ case TEST_PACKET_PID:
+ ret = ehset_prepare_port_for_testing(hub_udev, portnum);
+ if (ret < 0)
+ break;
+ ret = usb_control_msg_send(hub_udev, 0, USB_REQ_SET_FEATURE,
+ USB_RT_PORT, USB_PORT_FEAT_TEST,
+ (USB_TEST_PACKET << 8) | portnum,
+ NULL, 0, 1000, GFP_KERNEL);
+ break;
+ case TEST_HS_HOST_PORT_SUSPEND_RESUME:
+ /* Test: wait for 15secs -> suspend -> 15secs delay -> resume */
+ msleep(15 * 1000);
+ ret = usb_control_msg_send(hub_udev, 0, USB_REQ_SET_FEATURE,
+ USB_RT_PORT, USB_PORT_FEAT_SUSPEND,
+ portnum, NULL, 0, 1000, GFP_KERNEL);
+ if (ret < 0)
+ break;
+
+ msleep(15 * 1000);
+ ret = usb_control_msg_send(hub_udev, 0, USB_REQ_CLEAR_FEATURE,
+ USB_RT_PORT, USB_PORT_FEAT_SUSPEND,
+ portnum, NULL, 0, 1000, GFP_KERNEL);
+ break;
+ case TEST_SINGLE_STEP_GET_DEV_DESC:
+ /* Test: wait for 15secs -> GetDescriptor request */
+ msleep(15 * 1000);
+
+ ret = usb_control_msg_recv(dev, 0, USB_REQ_GET_DESCRIPTOR,
+ USB_DIR_IN, USB_DT_DEVICE << 8, 0,
+ &buf, USB_DT_DEVICE_SIZE,
+ USB_CTRL_GET_TIMEOUT, GFP_KERNEL);
+ break;
+ case TEST_SINGLE_STEP_SET_FEATURE:
+ /*
+ * GetDescriptor SETUP request -> 15secs delay -> IN & STATUS
+ *
+ * Note, this test is only supported on root hubs since the
+ * SetPortFeature handling can only be done inside the HCD's
+ * hub_control callback function.
+ */
+ if (hub_udev != dev->bus->root_hub) {
+ dev_err(&intf->dev, "SINGLE_STEP_SET_FEATURE test only supported on root hub\n");
+ break;
+ }
+
+ ret = usb_control_msg_send(hub_udev, 0, USB_REQ_SET_FEATURE,
+ USB_RT_PORT, USB_PORT_FEAT_TEST,
+ (6 << 8) | portnum, NULL, 0,
+ 60 * 1000, GFP_KERNEL);
+
+ break;
+ default:
+ dev_err(&intf->dev, "%s: unsupported PID: 0x%x\n",
+ __func__, test_pid);
+ }
+
+ return ret;
+}
+
+static void ehset_disconnect(struct usb_interface *intf)
+{
+}
+
+static const struct usb_device_id ehset_id_table[] = {
+ { USB_DEVICE(0x1a0a, TEST_SE0_NAK_PID) },
+ { USB_DEVICE(0x1a0a, TEST_J_PID) },
+ { USB_DEVICE(0x1a0a, TEST_K_PID) },
+ { USB_DEVICE(0x1a0a, TEST_PACKET_PID) },
+ { USB_DEVICE(0x1a0a, TEST_HS_HOST_PORT_SUSPEND_RESUME) },
+ { USB_DEVICE(0x1a0a, TEST_SINGLE_STEP_GET_DEV_DESC) },
+ { USB_DEVICE(0x1a0a, TEST_SINGLE_STEP_SET_FEATURE) },
+ { } /* Terminating entry */
+};
+MODULE_DEVICE_TABLE(usb, ehset_id_table);
+
+static struct usb_driver ehset_driver = {
+ .name = "usb_ehset_test",
+ .probe = ehset_probe,
+ .disconnect = ehset_disconnect,
+ .id_table = ehset_id_table,
+};
+
+module_usb_driver(ehset_driver);
+
+MODULE_DESCRIPTION("USB Driver for EHSET Test Fixture");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/usb/misc/emi26.c b/drivers/usb/misc/emi26.c
new file mode 100644
index 000000000..24d841850
--- /dev/null
+++ b/drivers/usb/misc/emi26.c
@@ -0,0 +1,259 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Emagic EMI 2|6 usb audio interface firmware loader.
+ * Copyright (C) 2002
+ * Tapio Laxström (tapio.laxstrom@iptime.fi)
+ *
+ * emi26.c,v 1.13 2002/03/08 13:10:26 tapio Exp
+ */
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/delay.h>
+#include <linux/firmware.h>
+#include <linux/ihex.h>
+
+#define EMI26_VENDOR_ID 0x086a /* Emagic Soft-und Hardware GmBH */
+#define EMI26_PRODUCT_ID 0x0100 /* EMI 2|6 without firmware */
+#define EMI26B_PRODUCT_ID 0x0102 /* EMI 2|6 without firmware */
+
+#define ANCHOR_LOAD_INTERNAL 0xA0 /* Vendor specific request code for Anchor Upload/Download (This one is implemented in the core) */
+#define ANCHOR_LOAD_EXTERNAL 0xA3 /* This command is not implemented in the core. Requires firmware */
+#define ANCHOR_LOAD_FPGA 0xA5 /* This command is not implemented in the core. Requires firmware. Emagic extension */
+#define MAX_INTERNAL_ADDRESS 0x1B3F /* This is the highest internal RAM address for the AN2131Q */
+#define CPUCS_REG 0x7F92 /* EZ-USB Control and Status Register. Bit 0 controls 8051 reset */
+#define INTERNAL_RAM(address) (address <= MAX_INTERNAL_ADDRESS)
+
+static int emi26_writememory( struct usb_device *dev, int address,
+ const unsigned char *data, int length,
+ __u8 bRequest);
+static int emi26_set_reset(struct usb_device *dev, unsigned char reset_bit);
+static int emi26_load_firmware (struct usb_device *dev);
+static int emi26_probe(struct usb_interface *intf, const struct usb_device_id *id);
+static void emi26_disconnect(struct usb_interface *intf);
+
+/* thanks to drivers/usb/serial/keyspan_pda.c code */
+static int emi26_writememory (struct usb_device *dev, int address,
+ const unsigned char *data, int length,
+ __u8 request)
+{
+ int result;
+ unsigned char *buffer = kmemdup(data, length, GFP_KERNEL);
+
+ if (!buffer) {
+ dev_err(&dev->dev, "kmalloc(%d) failed.\n", length);
+ return -ENOMEM;
+ }
+ /* Note: usb_control_msg returns negative value on error or length of the
+ * data that was written! */
+ result = usb_control_msg (dev, usb_sndctrlpipe(dev, 0), request, 0x40, address, 0, buffer, length, 300);
+ kfree (buffer);
+ return result;
+}
+
+/* thanks to drivers/usb/serial/keyspan_pda.c code */
+static int emi26_set_reset (struct usb_device *dev, unsigned char reset_bit)
+{
+ int response;
+ dev_info(&dev->dev, "%s - %d\n", __func__, reset_bit);
+ /* printk(KERN_DEBUG "%s - %d", __func__, reset_bit); */
+ response = emi26_writememory (dev, CPUCS_REG, &reset_bit, 1, 0xa0);
+ if (response < 0) {
+ dev_err(&dev->dev, "set_reset (%d) failed\n", reset_bit);
+ }
+ return response;
+}
+
+#define FW_LOAD_SIZE 1023
+
+static int emi26_load_firmware (struct usb_device *dev)
+{
+ const struct firmware *loader_fw = NULL;
+ const struct firmware *bitstream_fw = NULL;
+ const struct firmware *firmware_fw = NULL;
+ const struct ihex_binrec *rec;
+ int err = -ENOMEM;
+ int i;
+ __u32 addr; /* Address to write */
+ __u8 *buf;
+
+ buf = kmalloc(FW_LOAD_SIZE, GFP_KERNEL);
+ if (!buf)
+ goto wraperr;
+
+ err = request_ihex_firmware(&loader_fw, "emi26/loader.fw", &dev->dev);
+ if (err)
+ goto nofw;
+
+ err = request_ihex_firmware(&bitstream_fw, "emi26/bitstream.fw",
+ &dev->dev);
+ if (err)
+ goto nofw;
+
+ err = request_ihex_firmware(&firmware_fw, "emi26/firmware.fw",
+ &dev->dev);
+ if (err) {
+ nofw:
+ dev_err(&dev->dev, "%s - request_firmware() failed\n",
+ __func__);
+ goto wraperr;
+ }
+
+ /* Assert reset (stop the CPU in the EMI) */
+ err = emi26_set_reset(dev,1);
+ if (err < 0)
+ goto wraperr;
+
+ rec = (const struct ihex_binrec *)loader_fw->data;
+ /* 1. We need to put the loader for the FPGA into the EZ-USB */
+ while (rec) {
+ err = emi26_writememory(dev, be32_to_cpu(rec->addr),
+ rec->data, be16_to_cpu(rec->len),
+ ANCHOR_LOAD_INTERNAL);
+ if (err < 0)
+ goto wraperr;
+ rec = ihex_next_binrec(rec);
+ }
+
+ /* De-assert reset (let the CPU run) */
+ err = emi26_set_reset(dev,0);
+ if (err < 0)
+ goto wraperr;
+ msleep(250); /* let device settle */
+
+ /* 2. We upload the FPGA firmware into the EMI
+ * Note: collect up to 1023 (yes!) bytes and send them with
+ * a single request. This is _much_ faster! */
+ rec = (const struct ihex_binrec *)bitstream_fw->data;
+ do {
+ i = 0;
+ addr = be32_to_cpu(rec->addr);
+
+ /* intel hex records are terminated with type 0 element */
+ while (rec && (i + be16_to_cpu(rec->len) < FW_LOAD_SIZE)) {
+ memcpy(buf + i, rec->data, be16_to_cpu(rec->len));
+ i += be16_to_cpu(rec->len);
+ rec = ihex_next_binrec(rec);
+ }
+ err = emi26_writememory(dev, addr, buf, i, ANCHOR_LOAD_FPGA);
+ if (err < 0)
+ goto wraperr;
+ } while (rec);
+
+ /* Assert reset (stop the CPU in the EMI) */
+ err = emi26_set_reset(dev,1);
+ if (err < 0)
+ goto wraperr;
+
+ /* 3. We need to put the loader for the firmware into the EZ-USB (again...) */
+ for (rec = (const struct ihex_binrec *)loader_fw->data;
+ rec; rec = ihex_next_binrec(rec)) {
+ err = emi26_writememory(dev, be32_to_cpu(rec->addr),
+ rec->data, be16_to_cpu(rec->len),
+ ANCHOR_LOAD_INTERNAL);
+ if (err < 0)
+ goto wraperr;
+ }
+ msleep(250); /* let device settle */
+
+ /* De-assert reset (let the CPU run) */
+ err = emi26_set_reset(dev,0);
+ if (err < 0)
+ goto wraperr;
+
+ /* 4. We put the part of the firmware that lies in the external RAM into the EZ-USB */
+
+ for (rec = (const struct ihex_binrec *)firmware_fw->data;
+ rec; rec = ihex_next_binrec(rec)) {
+ if (!INTERNAL_RAM(be32_to_cpu(rec->addr))) {
+ err = emi26_writememory(dev, be32_to_cpu(rec->addr),
+ rec->data, be16_to_cpu(rec->len),
+ ANCHOR_LOAD_EXTERNAL);
+ if (err < 0)
+ goto wraperr;
+ }
+ }
+
+ /* Assert reset (stop the CPU in the EMI) */
+ err = emi26_set_reset(dev,1);
+ if (err < 0)
+ goto wraperr;
+
+ for (rec = (const struct ihex_binrec *)firmware_fw->data;
+ rec; rec = ihex_next_binrec(rec)) {
+ if (INTERNAL_RAM(be32_to_cpu(rec->addr))) {
+ err = emi26_writememory(dev, be32_to_cpu(rec->addr),
+ rec->data, be16_to_cpu(rec->len),
+ ANCHOR_LOAD_INTERNAL);
+ if (err < 0)
+ goto wraperr;
+ }
+ }
+
+ /* De-assert reset (let the CPU run) */
+ err = emi26_set_reset(dev,0);
+ if (err < 0)
+ goto wraperr;
+ msleep(250); /* let device settle */
+
+ /* return 1 to fail the driver inialization
+ * and give real driver change to load */
+ err = 1;
+
+wraperr:
+ if (err < 0)
+ dev_err(&dev->dev,"%s - error loading firmware: error = %d\n",
+ __func__, err);
+
+ release_firmware(loader_fw);
+ release_firmware(bitstream_fw);
+ release_firmware(firmware_fw);
+
+ kfree(buf);
+ return err;
+}
+
+static const struct usb_device_id id_table[] = {
+ { USB_DEVICE(EMI26_VENDOR_ID, EMI26_PRODUCT_ID) },
+ { USB_DEVICE(EMI26_VENDOR_ID, EMI26B_PRODUCT_ID) },
+ { } /* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE (usb, id_table);
+
+static int emi26_probe(struct usb_interface *intf, const struct usb_device_id *id)
+{
+ struct usb_device *dev = interface_to_usbdev(intf);
+
+ dev_info(&intf->dev, "%s start\n", __func__);
+
+ emi26_load_firmware(dev);
+
+ /* do not return the driver context, let real audio driver do that */
+ return -EIO;
+}
+
+static void emi26_disconnect(struct usb_interface *intf)
+{
+}
+
+static struct usb_driver emi26_driver = {
+ .name = "emi26 - firmware loader",
+ .probe = emi26_probe,
+ .disconnect = emi26_disconnect,
+ .id_table = id_table,
+};
+
+module_usb_driver(emi26_driver);
+
+MODULE_AUTHOR("Tapio Laxström");
+MODULE_DESCRIPTION("Emagic EMI 2|6 firmware loader.");
+MODULE_LICENSE("GPL");
+
+MODULE_FIRMWARE("emi26/loader.fw");
+MODULE_FIRMWARE("emi26/bitstream.fw");
+MODULE_FIRMWARE("emi26/firmware.fw");
+/* vi:ai:syntax=c:sw=8:ts=8:tw=80
+ */
diff --git a/drivers/usb/misc/emi62.c b/drivers/usb/misc/emi62.c
new file mode 100644
index 000000000..3eea60437
--- /dev/null
+++ b/drivers/usb/misc/emi62.c
@@ -0,0 +1,272 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Emagic EMI 2|6 usb audio interface firmware loader.
+ * Copyright (C) 2002
+ * Tapio Laxström (tapio.laxstrom@iptime.fi)
+ */
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/delay.h>
+#include <linux/firmware.h>
+#include <linux/ihex.h>
+
+/* include firmware (variables)*/
+
+/* FIXME: This is quick and dirty solution! */
+#define SPDIF /* if you want SPDIF comment next line */
+//#undef SPDIF /* if you want MIDI uncomment this line */
+
+#ifdef SPDIF
+#define FIRMWARE_FW "emi62/spdif.fw"
+#else
+#define FIRMWARE_FW "emi62/midi.fw"
+#endif
+
+#define EMI62_VENDOR_ID 0x086a /* Emagic Soft-und Hardware GmBH */
+#define EMI62_PRODUCT_ID 0x0110 /* EMI 6|2m without firmware */
+
+#define ANCHOR_LOAD_INTERNAL 0xA0 /* Vendor specific request code for Anchor Upload/Download (This one is implemented in the core) */
+#define ANCHOR_LOAD_EXTERNAL 0xA3 /* This command is not implemented in the core. Requires firmware */
+#define ANCHOR_LOAD_FPGA 0xA5 /* This command is not implemented in the core. Requires firmware. Emagic extension */
+#define MAX_INTERNAL_ADDRESS 0x1B3F /* This is the highest internal RAM address for the AN2131Q */
+#define CPUCS_REG 0x7F92 /* EZ-USB Control and Status Register. Bit 0 controls 8051 reset */
+#define INTERNAL_RAM(address) (address <= MAX_INTERNAL_ADDRESS)
+
+static int emi62_writememory(struct usb_device *dev, int address,
+ const unsigned char *data, int length,
+ __u8 bRequest);
+static int emi62_set_reset(struct usb_device *dev, unsigned char reset_bit);
+static int emi62_load_firmware (struct usb_device *dev);
+static int emi62_probe(struct usb_interface *intf, const struct usb_device_id *id);
+static void emi62_disconnect(struct usb_interface *intf);
+
+/* thanks to drivers/usb/serial/keyspan_pda.c code */
+static int emi62_writememory(struct usb_device *dev, int address,
+ const unsigned char *data, int length,
+ __u8 request)
+{
+ int result;
+ unsigned char *buffer = kmemdup(data, length, GFP_KERNEL);
+
+ if (!buffer) {
+ dev_err(&dev->dev, "kmalloc(%d) failed.\n", length);
+ return -ENOMEM;
+ }
+ /* Note: usb_control_msg returns negative value on error or length of the
+ * data that was written! */
+ result = usb_control_msg (dev, usb_sndctrlpipe(dev, 0), request, 0x40, address, 0, buffer, length, 300);
+ kfree (buffer);
+ return result;
+}
+
+/* thanks to drivers/usb/serial/keyspan_pda.c code */
+static int emi62_set_reset (struct usb_device *dev, unsigned char reset_bit)
+{
+ int response;
+ dev_info(&dev->dev, "%s - %d\n", __func__, reset_bit);
+
+ response = emi62_writememory (dev, CPUCS_REG, &reset_bit, 1, 0xa0);
+ if (response < 0)
+ dev_err(&dev->dev, "set_reset (%d) failed\n", reset_bit);
+ return response;
+}
+
+#define FW_LOAD_SIZE 1023
+
+static int emi62_load_firmware (struct usb_device *dev)
+{
+ const struct firmware *loader_fw = NULL;
+ const struct firmware *bitstream_fw = NULL;
+ const struct firmware *firmware_fw = NULL;
+ const struct ihex_binrec *rec;
+ int err = -ENOMEM;
+ int i;
+ __u32 addr; /* Address to write */
+ __u8 *buf;
+
+ dev_dbg(&dev->dev, "load_firmware\n");
+ buf = kmalloc(FW_LOAD_SIZE, GFP_KERNEL);
+ if (!buf)
+ goto wraperr;
+
+ err = request_ihex_firmware(&loader_fw, "emi62/loader.fw", &dev->dev);
+ if (err)
+ goto nofw;
+
+ err = request_ihex_firmware(&bitstream_fw, "emi62/bitstream.fw",
+ &dev->dev);
+ if (err)
+ goto nofw;
+
+ err = request_ihex_firmware(&firmware_fw, FIRMWARE_FW, &dev->dev);
+ if (err) {
+ nofw:
+ goto wraperr;
+ }
+
+ /* Assert reset (stop the CPU in the EMI) */
+ err = emi62_set_reset(dev,1);
+ if (err < 0)
+ goto wraperr;
+
+ rec = (const struct ihex_binrec *)loader_fw->data;
+
+ /* 1. We need to put the loader for the FPGA into the EZ-USB */
+ while (rec) {
+ err = emi62_writememory(dev, be32_to_cpu(rec->addr),
+ rec->data, be16_to_cpu(rec->len),
+ ANCHOR_LOAD_INTERNAL);
+ if (err < 0)
+ goto wraperr;
+ rec = ihex_next_binrec(rec);
+ }
+
+ /* De-assert reset (let the CPU run) */
+ err = emi62_set_reset(dev,0);
+ if (err < 0)
+ goto wraperr;
+ msleep(250); /* let device settle */
+
+ /* 2. We upload the FPGA firmware into the EMI
+ * Note: collect up to 1023 (yes!) bytes and send them with
+ * a single request. This is _much_ faster! */
+ rec = (const struct ihex_binrec *)bitstream_fw->data;
+ do {
+ i = 0;
+ addr = be32_to_cpu(rec->addr);
+
+ /* intel hex records are terminated with type 0 element */
+ while (rec && (i + be16_to_cpu(rec->len) < FW_LOAD_SIZE)) {
+ memcpy(buf + i, rec->data, be16_to_cpu(rec->len));
+ i += be16_to_cpu(rec->len);
+ rec = ihex_next_binrec(rec);
+ }
+ err = emi62_writememory(dev, addr, buf, i, ANCHOR_LOAD_FPGA);
+ if (err < 0)
+ goto wraperr;
+ } while (rec);
+
+ /* Assert reset (stop the CPU in the EMI) */
+ err = emi62_set_reset(dev,1);
+ if (err < 0)
+ goto wraperr;
+
+ /* 3. We need to put the loader for the firmware into the EZ-USB (again...) */
+ for (rec = (const struct ihex_binrec *)loader_fw->data;
+ rec; rec = ihex_next_binrec(rec)) {
+ err = emi62_writememory(dev, be32_to_cpu(rec->addr),
+ rec->data, be16_to_cpu(rec->len),
+ ANCHOR_LOAD_INTERNAL);
+ if (err < 0)
+ goto wraperr;
+ }
+
+ /* De-assert reset (let the CPU run) */
+ err = emi62_set_reset(dev,0);
+ if (err < 0)
+ goto wraperr;
+ msleep(250); /* let device settle */
+
+ /* 4. We put the part of the firmware that lies in the external RAM into the EZ-USB */
+
+ for (rec = (const struct ihex_binrec *)firmware_fw->data;
+ rec; rec = ihex_next_binrec(rec)) {
+ if (!INTERNAL_RAM(be32_to_cpu(rec->addr))) {
+ err = emi62_writememory(dev, be32_to_cpu(rec->addr),
+ rec->data, be16_to_cpu(rec->len),
+ ANCHOR_LOAD_EXTERNAL);
+ if (err < 0)
+ goto wraperr;
+ }
+ }
+
+ /* Assert reset (stop the CPU in the EMI) */
+ err = emi62_set_reset(dev,1);
+ if (err < 0)
+ goto wraperr;
+
+ for (rec = (const struct ihex_binrec *)firmware_fw->data;
+ rec; rec = ihex_next_binrec(rec)) {
+ if (INTERNAL_RAM(be32_to_cpu(rec->addr))) {
+ err = emi62_writememory(dev, be32_to_cpu(rec->addr),
+ rec->data, be16_to_cpu(rec->len),
+ ANCHOR_LOAD_EXTERNAL);
+ if (err < 0)
+ goto wraperr;
+ }
+ }
+
+ /* De-assert reset (let the CPU run) */
+ err = emi62_set_reset(dev,0);
+ if (err < 0)
+ goto wraperr;
+ msleep(250); /* let device settle */
+
+ release_firmware(loader_fw);
+ release_firmware(bitstream_fw);
+ release_firmware(firmware_fw);
+
+ kfree(buf);
+
+ /* return 1 to fail the driver inialization
+ * and give real driver change to load */
+ return 1;
+
+wraperr:
+ if (err < 0)
+ dev_err(&dev->dev,"%s - error loading firmware: error = %d\n",
+ __func__, err);
+ release_firmware(loader_fw);
+ release_firmware(bitstream_fw);
+ release_firmware(firmware_fw);
+
+ kfree(buf);
+ dev_err(&dev->dev, "Error\n");
+ return err;
+}
+
+static const struct usb_device_id id_table[] = {
+ { USB_DEVICE(EMI62_VENDOR_ID, EMI62_PRODUCT_ID) },
+ { } /* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE (usb, id_table);
+
+static int emi62_probe(struct usb_interface *intf, const struct usb_device_id *id)
+{
+ struct usb_device *dev = interface_to_usbdev(intf);
+ dev_dbg(&intf->dev, "emi62_probe\n");
+
+ dev_info(&intf->dev, "%s start\n", __func__);
+
+ emi62_load_firmware(dev);
+
+ /* do not return the driver context, let real audio driver do that */
+ return -EIO;
+}
+
+static void emi62_disconnect(struct usb_interface *intf)
+{
+}
+
+static struct usb_driver emi62_driver = {
+ .name = "emi62 - firmware loader",
+ .probe = emi62_probe,
+ .disconnect = emi62_disconnect,
+ .id_table = id_table,
+};
+
+module_usb_driver(emi62_driver);
+
+MODULE_AUTHOR("Tapio Laxström");
+MODULE_DESCRIPTION("Emagic EMI 6|2m firmware loader.");
+MODULE_LICENSE("GPL");
+
+MODULE_FIRMWARE("emi62/loader.fw");
+MODULE_FIRMWARE("emi62/bitstream.fw");
+MODULE_FIRMWARE(FIRMWARE_FW);
+/* vi:ai:syntax=c:sw=8:ts=8:tw=80
+ */
diff --git a/drivers/usb/misc/ezusb.c b/drivers/usb/misc/ezusb.c
new file mode 100644
index 000000000..78aaee56c
--- /dev/null
+++ b/drivers/usb/misc/ezusb.c
@@ -0,0 +1,151 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * EZ-USB specific functions used by some of the USB to Serial drivers.
+ *
+ * Copyright (C) 1999 - 2002 Greg Kroah-Hartman (greg@kroah.com)
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/firmware.h>
+#include <linux/ihex.h>
+#include <linux/usb/ezusb.h>
+
+struct ezusb_fx_type {
+ /* EZ-USB Control and Status Register. Bit 0 controls 8051 reset */
+ unsigned short cpucs_reg;
+ unsigned short max_internal_adress;
+};
+
+static const struct ezusb_fx_type ezusb_fx1 = {
+ .cpucs_reg = 0x7F92,
+ .max_internal_adress = 0x1B3F,
+};
+
+/* Commands for writing to memory */
+#define WRITE_INT_RAM 0xA0
+#define WRITE_EXT_RAM 0xA3
+
+static int ezusb_writememory(struct usb_device *dev, int address,
+ unsigned char *data, int length, __u8 request)
+{
+ if (!dev)
+ return -ENODEV;
+
+ return usb_control_msg_send(dev, 0, request,
+ USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
+ address, 0, data, length, 3000, GFP_KERNEL);
+}
+
+static int ezusb_set_reset(struct usb_device *dev, unsigned short cpucs_reg,
+ unsigned char reset_bit)
+{
+ int response = ezusb_writememory(dev, cpucs_reg, &reset_bit, 1, WRITE_INT_RAM);
+ if (response < 0)
+ dev_err(&dev->dev, "%s-%d failed: %d\n",
+ __func__, reset_bit, response);
+ return response;
+}
+
+int ezusb_fx1_set_reset(struct usb_device *dev, unsigned char reset_bit)
+{
+ return ezusb_set_reset(dev, ezusb_fx1.cpucs_reg, reset_bit);
+}
+EXPORT_SYMBOL_GPL(ezusb_fx1_set_reset);
+
+static int ezusb_ihex_firmware_download(struct usb_device *dev,
+ struct ezusb_fx_type fx,
+ const char *firmware_path)
+{
+ int ret = -ENOENT;
+ const struct firmware *firmware = NULL;
+ const struct ihex_binrec *record;
+
+ if (request_ihex_firmware(&firmware, firmware_path,
+ &dev->dev)) {
+ dev_err(&dev->dev,
+ "%s - request \"%s\" failed\n",
+ __func__, firmware_path);
+ goto out;
+ }
+
+ ret = ezusb_set_reset(dev, fx.cpucs_reg, 0);
+ if (ret < 0)
+ goto out;
+
+ record = (const struct ihex_binrec *)firmware->data;
+ for (; record; record = ihex_next_binrec(record)) {
+ if (be32_to_cpu(record->addr) > fx.max_internal_adress) {
+ ret = ezusb_writememory(dev, be32_to_cpu(record->addr),
+ (unsigned char *)record->data,
+ be16_to_cpu(record->len), WRITE_EXT_RAM);
+ if (ret < 0) {
+ dev_err(&dev->dev, "%s - ezusb_writememory "
+ "failed writing internal memory "
+ "(%d %04X %p %d)\n", __func__, ret,
+ be32_to_cpu(record->addr), record->data,
+ be16_to_cpu(record->len));
+ goto out;
+ }
+ }
+ }
+
+ ret = ezusb_set_reset(dev, fx.cpucs_reg, 1);
+ if (ret < 0)
+ goto out;
+ record = (const struct ihex_binrec *)firmware->data;
+ for (; record; record = ihex_next_binrec(record)) {
+ if (be32_to_cpu(record->addr) <= fx.max_internal_adress) {
+ ret = ezusb_writememory(dev, be32_to_cpu(record->addr),
+ (unsigned char *)record->data,
+ be16_to_cpu(record->len), WRITE_INT_RAM);
+ if (ret < 0) {
+ dev_err(&dev->dev, "%s - ezusb_writememory "
+ "failed writing external memory "
+ "(%d %04X %p %d)\n", __func__, ret,
+ be32_to_cpu(record->addr), record->data,
+ be16_to_cpu(record->len));
+ goto out;
+ }
+ }
+ }
+ ret = ezusb_set_reset(dev, fx.cpucs_reg, 0);
+out:
+ release_firmware(firmware);
+ return ret;
+}
+
+int ezusb_fx1_ihex_firmware_download(struct usb_device *dev,
+ const char *firmware_path)
+{
+ return ezusb_ihex_firmware_download(dev, ezusb_fx1, firmware_path);
+}
+EXPORT_SYMBOL_GPL(ezusb_fx1_ihex_firmware_download);
+
+#if 0
+/*
+ * Once someone one needs these fx2 functions, uncomment them
+ * and add them to ezusb.h and all should be good.
+ */
+static struct ezusb_fx_type ezusb_fx2 = {
+ .cpucs_reg = 0xE600,
+ .max_internal_adress = 0x3FFF,
+};
+
+int ezusb_fx2_set_reset(struct usb_device *dev, unsigned char reset_bit)
+{
+ return ezusb_set_reset(dev, ezusb_fx2.cpucs_reg, reset_bit);
+}
+EXPORT_SYMBOL_GPL(ezusb_fx2_set_reset);
+
+int ezusb_fx2_ihex_firmware_download(struct usb_device *dev,
+ const char *firmware_path)
+{
+ return ezusb_ihex_firmware_download(dev, ezusb_fx2, firmware_path);
+}
+EXPORT_SYMBOL_GPL(ezusb_fx2_ihex_firmware_download);
+#endif
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/misc/ftdi-elan.c b/drivers/usb/misc/ftdi-elan.c
new file mode 100644
index 000000000..b2f980409
--- /dev/null
+++ b/drivers/usb/misc/ftdi-elan.c
@@ -0,0 +1,2784 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * 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 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.
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/list.h>
+#include <linux/ioctl.h>
+#include <linux/pci_ids.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/kref.h>
+#include <linux/mutex.h>
+#include <linux/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)
+static bool distrust_firmware = 1;
+module_param(distrust_firmware, bool, 0);
+MODULE_PARM_DESC(distrust_firmware,
+ "true to distrust firmware power/overcurrent setup");
+extern struct platform_driver u132_platform_driver;
+/*
+ * ftdi_module_lock exists to protect access to global variables
+ *
+ */
+static struct mutex 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"
+#include <asm/io.h>
+#include <linux/usb/hcd.h>
+
+/* FIXME ohci.h is ONLY for internal use by the OHCI driver.
+ * If you're going to try stuff like this, you need to split
+ * out shareable stuff (register declarations?) into its own
+ * file, maybe name <linux/usb/ohci.h>
+ */
+
+#include "../host/ohci.h"
+/* 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 const 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 mutex 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 delayed_work status_work;
+ struct delayed_work command_work;
+ struct delayed_work 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 received;
+ 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;
+ mutex_lock(&ftdi_module_lock);
+ list_del_init(&ftdi->ftdi_list);
+ ftdi_instances -= 1;
+ mutex_unlock(&ftdi_module_lock);
+ kfree(ftdi->bulk_in_buffer);
+ ftdi->bulk_in_buffer = NULL;
+ kfree(ftdi);
+}
+
+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 (!schedule_delayed_work(&ftdi->status_work, delta))
+ kref_put(&ftdi->kref, ftdi_elan_delete);
+}
+
+static void ftdi_status_queue_work(struct usb_ftdi *ftdi, unsigned int delta)
+{
+ if (schedule_delayed_work(&ftdi->status_work, delta))
+ kref_get(&ftdi->kref);
+}
+
+static void ftdi_status_cancel_work(struct usb_ftdi *ftdi)
+{
+ if (cancel_delayed_work_sync(&ftdi->status_work))
+ kref_put(&ftdi->kref, ftdi_elan_delete);
+}
+
+static void ftdi_command_requeue_work(struct usb_ftdi *ftdi, unsigned int delta)
+{
+ if (!schedule_delayed_work(&ftdi->command_work, delta))
+ kref_put(&ftdi->kref, ftdi_elan_delete);
+}
+
+static void ftdi_command_queue_work(struct usb_ftdi *ftdi, unsigned int delta)
+{
+ if (schedule_delayed_work(&ftdi->command_work, delta))
+ kref_get(&ftdi->kref);
+}
+
+static void ftdi_command_cancel_work(struct usb_ftdi *ftdi)
+{
+ if (cancel_delayed_work_sync(&ftdi->command_work))
+ kref_put(&ftdi->kref, ftdi_elan_delete);
+}
+
+static void ftdi_response_requeue_work(struct usb_ftdi *ftdi,
+ unsigned int delta)
+{
+ if (!schedule_delayed_work(&ftdi->respond_work, delta))
+ kref_put(&ftdi->kref, ftdi_elan_delete);
+}
+
+static void ftdi_respond_queue_work(struct usb_ftdi *ftdi, unsigned int delta)
+{
+ if (schedule_delayed_work(&ftdi->respond_work, delta))
+ kref_get(&ftdi->kref);
+}
+
+static void ftdi_response_cancel_work(struct usb_ftdi *ftdi)
+{
+ if (cancel_delayed_work_sync(&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);
+static 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)
+{
+ 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);
+
+ return platform_device_register(&ftdi->platform_dev);
+}
+
+static void ftdi_elan_abandon_completions(struct usb_ftdi *ftdi)
+{
+ mutex_lock(&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);
+ }
+ mutex_unlock(&ftdi->u132_lock);
+}
+
+static void ftdi_elan_abandon_targets(struct usb_ftdi *ftdi)
+{
+ int ed_number = 4;
+ mutex_lock(&ftdi->u132_lock);
+ while (ed_number-- > 0) {
+ struct u132_target *target = &ftdi->target[ed_number];
+ if (target->active == 1) {
+ target->condition_code = TD_DEVNOTRESP;
+ mutex_unlock(&ftdi->u132_lock);
+ ftdi_elan_do_callback(ftdi, target, NULL, 0);
+ mutex_lock(&ftdi->u132_lock);
+ }
+ }
+ ftdi->received = 0;
+ ftdi->expected = 4;
+ ftdi->ed_found = 0;
+ mutex_unlock(&ftdi->u132_lock);
+}
+
+static void ftdi_elan_flush_targets(struct usb_ftdi *ftdi)
+{
+ int ed_number = 4;
+ mutex_lock(&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 {
+ mutex_unlock(&ftdi->u132_lock);
+ msleep(100);
+ mutex_lock(&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 {
+ mutex_unlock(&ftdi->u132_lock);
+ msleep(100);
+ mutex_lock(&ftdi->u132_lock);
+ goto wait_2;
+ }
+ }
+ }
+ ftdi->received = 0;
+ ftdi->expected = 4;
+ ftdi->ed_found = 0;
+ mutex_unlock(&ftdi->u132_lock);
+}
+
+static void ftdi_elan_cancel_targets(struct usb_ftdi *ftdi)
+{
+ int ed_number = 4;
+ mutex_lock(&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 {
+ mutex_unlock(&ftdi->u132_lock);
+ msleep(100);
+ mutex_lock(&ftdi->u132_lock);
+ goto wait;
+ }
+ }
+ }
+ ftdi->received = 0;
+ ftdi->expected = 4;
+ ftdi->ed_found = 0;
+ mutex_unlock(&ftdi->u132_lock);
+}
+
+static void ftdi_elan_kick_command_queue(struct usb_ftdi *ftdi)
+{
+ ftdi_command_queue_work(ftdi, 0);
+}
+
+static void ftdi_elan_command_work(struct work_struct *work)
+{
+ struct usb_ftdi *ftdi =
+ container_of(work, struct usb_ftdi, command_work.work);
+
+ 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);
+}
+
+static void ftdi_elan_respond_work(struct work_struct *work)
+{
+ struct usb_ftdi *ftdi =
+ container_of(work, struct usb_ftdi, respond_work.work);
+ 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 == -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(struct work_struct *work)
+{
+ struct usb_ftdi *ftdi =
+ container_of(work, struct usb_ftdi, status_work.work);
+ 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 = 1 *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 confirmed platform_dev.dev.parent=%p platform_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;
+ struct usb_interface *interface;
+
+ subminor = iminor(inode);
+ interface = usb_find_interface(&ftdi_elan_driver, subminor);
+
+ if (!interface) {
+ pr_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 = 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;
+}
+
+
+/*
+ *
+ * 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 - 1;
+ int bytes_read = 0;
+ int retry_on_empty = 10;
+ int retry_on_timeout = 5;
+ struct usb_ftdi *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, 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 usb_ftdi *ftdi = urb->context;
+ int status = urb->status;
+
+ if (status && !(status == -ENOENT || status == -ECONNRESET ||
+ status == -ESHUTDOWN)) {
+ dev_err(&ftdi->udev->dev,
+ "urb=%p write bulk status received: %d\n", urb, status);
+ }
+ usb_free_coherent(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)
+ return -ENOMEM;
+ buf = usb_alloc_coherent(ftdi->udev, total_size, GFP_KERNEL,
+ &urb->transfer_dma);
+ if (!buf) {
+ dev_err(&ftdi->udev->dev, "could not get a buffer to write %d commands 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) - 1) / 3;
+ diag[0] = 0;
+ while (s-- > 0 && m-- > 0) {
+ if (s > 0 || m == 0) {
+ d += sprintf(d, " %02X", *c++);
+ } else
+ d += sprintf(d, " ..");
+ }
+ }
+ retval = usb_submit_urb(urb, GFP_KERNEL);
+ if (retval) {
+ dev_err(&ftdi->udev->dev, "failed %d to submit urb %p to write %d commands totaling %d bytes to the Uxxx\n",
+ retval, urb, command_size, total_size);
+ usb_free_coherent(ftdi->udev, total_size, buf, urb->transfer_dma);
+ usb_free_urb(urb);
+ return retval;
+ }
+ usb_free_urb(urb); /* release our reference to this urb,
+ the USB core will eventually free it entirely */
+ ftdi->command_head += command_size;
+ ftdi_elan_kick_respond_queue(ftdi);
+ return 0;
+}
+
+static void ftdi_elan_do_callback(struct usb_ftdi *ftdi,
+ struct u132_target *target, u8 *buffer, int length)
+{
+ struct urb *urb = target->urb;
+ int halted = target->halted;
+ int skipped = target->skipped;
+ int actual = target->actual;
+ int non_null = target->non_null;
+ int toggle_bits = target->toggle_bits;
+ int error_count = target->error_count;
+ int condition_code = target->condition_code;
+ int repeat_number = target->repeat_number;
+ void (*callback) (void *, struct urb *, u8 *, int, int, int, int, int,
+ int, int, int, int) = target->callback;
+ target->active -= 1;
+ target->callback = NULL;
+ (*callback) (target->endp, urb, buffer, length, toggle_bits,
+ error_count, condition_code, repeat_number, halted, skipped,
+ actual, non_null);
+}
+
+static char *have_ed_set_response(struct usb_ftdi *ftdi,
+ struct u132_target *target, u16 ed_length, int ed_number, int ed_type,
+ char *b)
+{
+ int payload = (ed_length >> 0) & 0x07FF;
+ mutex_lock(&ftdi->u132_lock);
+ target->actual = 0;
+ target->non_null = (ed_length >> 15) & 0x0001;
+ target->repeat_number = (ed_length >> 11) & 0x000F;
+ if (ed_type == 0x02 || ed_type == 0x03) {
+ if (payload == 0 || target->abandoning > 0) {
+ target->abandoning = 0;
+ mutex_unlock(&ftdi->u132_lock);
+ ftdi_elan_do_callback(ftdi, target, 4 + ftdi->response,
+ payload);
+ ftdi->received = 0;
+ ftdi->expected = 4;
+ ftdi->ed_found = 0;
+ return ftdi->response;
+ } else {
+ ftdi->expected = 4 + payload;
+ ftdi->ed_found = 1;
+ mutex_unlock(&ftdi->u132_lock);
+ return b;
+ }
+ } else {
+ target->abandoning = 0;
+ mutex_unlock(&ftdi->u132_lock);
+ ftdi_elan_do_callback(ftdi, target, 4 + ftdi->response,
+ payload);
+ ftdi->received = 0;
+ ftdi->expected = 4;
+ ftdi->ed_found = 0;
+ return ftdi->response;
+ }
+}
+
+static char *have_ed_get_response(struct usb_ftdi *ftdi,
+ struct u132_target *target, u16 ed_length, int ed_number, int ed_type,
+ char *b)
+{
+ mutex_lock(&ftdi->u132_lock);
+ target->condition_code = TD_DEVNOTRESP;
+ target->actual = (ed_length >> 0) & 0x01FF;
+ target->non_null = (ed_length >> 15) & 0x0001;
+ target->repeat_number = (ed_length >> 11) & 0x000F;
+ mutex_unlock(&ftdi->u132_lock);
+ if (target->active)
+ ftdi_elan_do_callback(ftdi, target, NULL, 0);
+ target->abandoning = 0;
+ ftdi->received = 0;
+ ftdi->expected = 4;
+ ftdi->ed_found = 0;
+ return ftdi->response;
+}
+
+
+/*
+ * The engine tries to empty the FTDI fifo
+ *
+ * all responses found in the fifo data are dispatched thus
+ * the response buffer can only ever hold a maximum sized
+ * response from the Uxxx.
+ *
+ */
+static int ftdi_elan_respond_engine(struct usb_ftdi *ftdi)
+{
+ u8 *b = ftdi->response + ftdi->received;
+ int bytes_read = 0;
+ int retry_on_empty = 1;
+ int retry_on_timeout = 3;
+read:{
+ 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, 500);
+ char diag[30 *3 + 4];
+ char *d = diag;
+ int m = packet_bytes;
+ u8 *c = ftdi->bulk_in_buffer;
+ int s = (sizeof(diag) - 1) / 3;
+ diag[0] = 0;
+ while (s-- > 0 && m-- > 0) {
+ if (s > 0 || m == 0) {
+ d += sprintf(d, " %02X", *c++);
+ } else
+ d += sprintf(d, " ..");
+ }
+ 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) {
+ dev_err(&ftdi->udev->dev, "TIMED OUT with packet_bytes = %d with total %d bytes%s\n",
+ packet_bytes, bytes_read, diag);
+ goto more;
+ } else if (bytes_read > 0) {
+ dev_err(&ftdi->udev->dev, "ONLY %d bytes%s\n",
+ bytes_read, diag);
+ return -ENOMEM;
+ } else {
+ dev_err(&ftdi->udev->dev, "TIMED OUT with packet_bytes = %d with total %d bytes%s\n",
+ packet_bytes, bytes_read, diag);
+ return -ENOMEM;
+ }
+ } else if (retval == -EILSEQ) {
+ dev_err(&ftdi->udev->dev, "error = %d with packet_bytes = %d with total %d bytes%s\n",
+ retval, packet_bytes, bytes_read, diag);
+ return retval;
+ } else if (retval) {
+ dev_err(&ftdi->udev->dev, "error = %d with packet_bytes = %d with total %d bytes%s\n",
+ retval, packet_bytes, bytes_read, diag);
+ return retval;
+ } else {
+ if (retry_on_empty-- > 0) {
+ goto more;
+ } else
+ return 0;
+ }
+ }
+more:{
+ goto read;
+ }
+have:if (ftdi->bulk_in_left > 0) {
+ u8 c = ftdi->bulk_in_buffer[++ftdi->bulk_in_last];
+ bytes_read += 1;
+ ftdi->bulk_in_left -= 1;
+ if (ftdi->received == 0 && c == 0xFF) {
+ goto have;
+ } else
+ *b++ = c;
+ if (++ftdi->received < ftdi->expected) {
+ goto have;
+ } else if (ftdi->ed_found) {
+ int ed_number = (ftdi->response[0] >> 5) & 0x03;
+ u16 ed_length = (ftdi->response[2] << 8) |
+ ftdi->response[1];
+ struct u132_target *target = &ftdi->target[ed_number];
+ int payload = (ed_length >> 0) & 0x07FF;
+ char diag[30 *3 + 4];
+ char *d = diag;
+ int m = payload;
+ u8 *c = 4 + ftdi->response;
+ int s = (sizeof(diag) - 1) / 3;
+ diag[0] = 0;
+ while (s-- > 0 && m-- > 0) {
+ if (s > 0 || m == 0) {
+ d += sprintf(d, " %02X", *c++);
+ } else
+ d += sprintf(d, " ..");
+ }
+ ftdi_elan_do_callback(ftdi, target, 4 + ftdi->response,
+ payload);
+ ftdi->received = 0;
+ ftdi->expected = 4;
+ ftdi->ed_found = 0;
+ b = ftdi->response;
+ goto have;
+ } else if (ftdi->expected == 8) {
+ u8 buscmd;
+ int respond_head = ftdi->respond_head++;
+ struct u132_respond *respond = &ftdi->respond[
+ RESPOND_MASK & respond_head];
+ u32 data = ftdi->response[7];
+ data <<= 8;
+ data |= ftdi->response[6];
+ data <<= 8;
+ data |= ftdi->response[5];
+ data <<= 8;
+ data |= ftdi->response[4];
+ *respond->value = data;
+ *respond->result = 0;
+ complete(&respond->wait_completion);
+ ftdi->received = 0;
+ ftdi->expected = 4;
+ ftdi->ed_found = 0;
+ b = ftdi->response;
+ buscmd = (ftdi->response[0] >> 0) & 0x0F;
+ if (buscmd == 0x00) {
+ } else if (buscmd == 0x02) {
+ } else if (buscmd == 0x06) {
+ } else if (buscmd == 0x0A) {
+ } else
+ dev_err(&ftdi->udev->dev, "Uxxx unknown(%0X) value = %08X\n",
+ buscmd, data);
+ goto have;
+ } else {
+ if ((ftdi->response[0] & 0x80) == 0x00) {
+ ftdi->expected = 8;
+ goto have;
+ } else {
+ int ed_number = (ftdi->response[0] >> 5) & 0x03;
+ int ed_type = (ftdi->response[0] >> 0) & 0x03;
+ u16 ed_length = (ftdi->response[2] << 8) |
+ ftdi->response[1];
+ struct u132_target *target = &ftdi->target[
+ ed_number];
+ target->halted = (ftdi->response[0] >> 3) &
+ 0x01;
+ target->skipped = (ftdi->response[0] >> 2) &
+ 0x01;
+ target->toggle_bits = (ftdi->response[3] >> 6)
+ & 0x03;
+ target->error_count = (ftdi->response[3] >> 4)
+ & 0x03;
+ target->condition_code = (ftdi->response[
+ 3] >> 0) & 0x0F;
+ if ((ftdi->response[0] & 0x10) == 0x00) {
+ b = have_ed_set_response(ftdi, target,
+ ed_length, ed_number, ed_type,
+ b);
+ goto have;
+ } else {
+ b = have_ed_get_response(ftdi, target,
+ ed_length, ed_number, ed_type,
+ b);
+ goto have;
+ }
+ }
+ }
+ } else
+ goto more;
+}
+
+
+/*
+ * create a urb, and a buffer for it, and copy the data to the urb
+ *
+ */
+static ssize_t ftdi_elan_write(struct file *file,
+ const char __user *user_buffer, size_t count,
+ loff_t *ppos)
+{
+ int retval = 0;
+ struct urb *urb;
+ char *buf;
+ struct usb_ftdi *ftdi = file->private_data;
+
+ if (ftdi->disconnected > 0) {
+ return -ENODEV;
+ }
+ if (count == 0) {
+ goto exit;
+ }
+ urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!urb) {
+ retval = -ENOMEM;
+ goto error_1;
+ }
+ buf = usb_alloc_coherent(ftdi->udev, count, GFP_KERNEL,
+ &urb->transfer_dma);
+ if (!buf) {
+ retval = -ENOMEM;
+ goto error_2;
+ }
+ if (copy_from_user(buf, user_buffer, count)) {
+ retval = -EFAULT;
+ goto error_3;
+ }
+ usb_fill_bulk_urb(urb, ftdi->udev, usb_sndbulkpipe(ftdi->udev,
+ ftdi->bulk_out_endpointAddr), buf, count,
+ ftdi_elan_write_bulk_callback, ftdi);
+ urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+ retval = usb_submit_urb(urb, GFP_KERNEL);
+ if (retval) {
+ dev_err(&ftdi->udev->dev,
+ "failed submitting write urb, error %d\n", retval);
+ goto error_3;
+ }
+ usb_free_urb(urb);
+
+exit:
+ return count;
+error_3:
+ usb_free_coherent(ftdi->udev, count, buf, urb->transfer_dma);
+error_2:
+ usb_free_urb(urb);
+error_1:
+ return retval;
+}
+
+static const struct file_operations ftdi_elan_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .read = ftdi_elan_read,
+ .write = ftdi_elan_write,
+ .open = ftdi_elan_open,
+ .release = ftdi_elan_release,
+};
+
+/*
+ * usb class driver info in order to get a minor number from the usb core,
+ * and to have the device registered with the driver core
+ */
+static struct usb_class_driver ftdi_elan_jtag_class = {
+ .name = "ftdi-%d-jtag",
+ .fops = &ftdi_elan_fops,
+ .minor_base = USB_FTDI_ELAN_MINOR_BASE,
+};
+
+/*
+ * the following definitions are for the
+ * ELAN FPGA state machgine processor that
+ * lies on the other side of the FTDI chip
+ */
+#define cPCIu132rd 0x0
+#define cPCIu132wr 0x1
+#define cPCIiord 0x2
+#define cPCIiowr 0x3
+#define cPCImemrd 0x6
+#define cPCImemwr 0x7
+#define cPCIcfgrd 0xA
+#define cPCIcfgwr 0xB
+#define cPCInull 0xF
+#define cU132cmd_status 0x0
+#define cU132flash 0x1
+#define cPIDsetup 0x0
+#define cPIDout 0x1
+#define cPIDin 0x2
+#define cPIDinonce 0x3
+#define cCCnoerror 0x0
+#define cCCcrc 0x1
+#define cCCbitstuff 0x2
+#define cCCtoggle 0x3
+#define cCCstall 0x4
+#define cCCnoresp 0x5
+#define cCCbadpid1 0x6
+#define cCCbadpid2 0x7
+#define cCCdataoverrun 0x8
+#define cCCdataunderrun 0x9
+#define cCCbuffoverrun 0xC
+#define cCCbuffunderrun 0xD
+#define cCCnotaccessed 0xF
+static int ftdi_elan_write_reg(struct usb_ftdi *ftdi, u32 data)
+{
+wait:if (ftdi->disconnected > 0) {
+ return -ENODEV;
+ } else {
+ int command_size;
+ mutex_lock(&ftdi->u132_lock);
+ 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 = 0x00 | cPCIu132wr;
+ command->length = 0x04;
+ command->address = 0x00;
+ command->width = 0x00;
+ command->follows = 4;
+ command->value = data;
+ command->buffer = &command->value;
+ ftdi->command_next += 1;
+ ftdi_elan_kick_command_queue(ftdi);
+ mutex_unlock(&ftdi->u132_lock);
+ return 0;
+ } else {
+ mutex_unlock(&ftdi->u132_lock);
+ msleep(100);
+ goto wait;
+ }
+ }
+}
+
+static int ftdi_elan_write_config(struct usb_ftdi *ftdi, int config_offset,
+ u8 width, u32 data)
+{
+ u8 addressofs = config_offset / 4;
+wait:if (ftdi->disconnected > 0) {
+ return -ENODEV;
+ } else {
+ int command_size;
+ mutex_lock(&ftdi->u132_lock);
+ 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 = 0x00 | (cPCIcfgwr & 0x0F);
+ command->length = 0x04;
+ command->address = addressofs;
+ command->width = 0x00 | (width & 0x0F);
+ command->follows = 4;
+ command->value = data;
+ command->buffer = &command->value;
+ ftdi->command_next += 1;
+ ftdi_elan_kick_command_queue(ftdi);
+ mutex_unlock(&ftdi->u132_lock);
+ return 0;
+ } else {
+ mutex_unlock(&ftdi->u132_lock);
+ msleep(100);
+ goto wait;
+ }
+ }
+}
+
+static int ftdi_elan_write_pcimem(struct usb_ftdi *ftdi, int mem_offset,
+ u8 width, u32 data)
+{
+ u8 addressofs = mem_offset / 4;
+wait:if (ftdi->disconnected > 0) {
+ return -ENODEV;
+ } else {
+ int command_size;
+ mutex_lock(&ftdi->u132_lock);
+ 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 = 0x00 | (cPCImemwr & 0x0F);
+ command->length = 0x04;
+ command->address = addressofs;
+ command->width = 0x00 | (width & 0x0F);
+ command->follows = 4;
+ command->value = data;
+ command->buffer = &command->value;
+ ftdi->command_next += 1;
+ ftdi_elan_kick_command_queue(ftdi);
+ mutex_unlock(&ftdi->u132_lock);
+ return 0;
+ } else {
+ mutex_unlock(&ftdi->u132_lock);
+ msleep(100);
+ goto wait;
+ }
+ }
+}
+
+int usb_ftdi_elan_write_pcimem(struct platform_device *pdev, int mem_offset,
+ u8 width, u32 data)
+{
+ struct usb_ftdi *ftdi = platform_device_to_usb_ftdi(pdev);
+ return ftdi_elan_write_pcimem(ftdi, mem_offset, width, data);
+}
+
+
+EXPORT_SYMBOL_GPL(usb_ftdi_elan_write_pcimem);
+static int ftdi_elan_read_reg(struct usb_ftdi *ftdi, u32 *data)
+{
+wait:if (ftdi->disconnected > 0) {
+ return -ENODEV;
+ } else {
+ int command_size;
+ int respond_size;
+ mutex_lock(&ftdi->u132_lock);
+ command_size = ftdi->command_next - ftdi->command_head;
+ respond_size = ftdi->respond_next - ftdi->respond_head;
+ if (command_size < COMMAND_SIZE && respond_size < RESPOND_SIZE)
+ {
+ struct u132_command *command = &ftdi->command[
+ COMMAND_MASK & ftdi->command_next];
+ struct u132_respond *respond = &ftdi->respond[
+ RESPOND_MASK & ftdi->respond_next];
+ int result = -ENODEV;
+ respond->result = &result;
+ respond->header = command->header = 0x00 | cPCIu132rd;
+ command->length = 0x04;
+ respond->address = command->address = cU132cmd_status;
+ command->width = 0x00;
+ command->follows = 0;
+ command->value = 0;
+ command->buffer = NULL;
+ respond->value = data;
+ init_completion(&respond->wait_completion);
+ ftdi->command_next += 1;
+ ftdi->respond_next += 1;
+ ftdi_elan_kick_command_queue(ftdi);
+ mutex_unlock(&ftdi->u132_lock);
+ wait_for_completion(&respond->wait_completion);
+ return result;
+ } else {
+ mutex_unlock(&ftdi->u132_lock);
+ msleep(100);
+ goto wait;
+ }
+ }
+}
+
+static int ftdi_elan_read_config(struct usb_ftdi *ftdi, int config_offset,
+ u8 width, u32 *data)
+{
+ u8 addressofs = config_offset / 4;
+wait:if (ftdi->disconnected > 0) {
+ return -ENODEV;
+ } else {
+ int command_size;
+ int respond_size;
+ mutex_lock(&ftdi->u132_lock);
+ command_size = ftdi->command_next - ftdi->command_head;
+ respond_size = ftdi->respond_next - ftdi->respond_head;
+ if (command_size < COMMAND_SIZE && respond_size < RESPOND_SIZE)
+ {
+ struct u132_command *command = &ftdi->command[
+ COMMAND_MASK & ftdi->command_next];
+ struct u132_respond *respond = &ftdi->respond[
+ RESPOND_MASK & ftdi->respond_next];
+ int result = -ENODEV;
+ respond->result = &result;
+ respond->header = command->header = 0x00 | (cPCIcfgrd &
+ 0x0F);
+ command->length = 0x04;
+ respond->address = command->address = addressofs;
+ command->width = 0x00 | (width & 0x0F);
+ command->follows = 0;
+ command->value = 0;
+ command->buffer = NULL;
+ respond->value = data;
+ init_completion(&respond->wait_completion);
+ ftdi->command_next += 1;
+ ftdi->respond_next += 1;
+ ftdi_elan_kick_command_queue(ftdi);
+ mutex_unlock(&ftdi->u132_lock);
+ wait_for_completion(&respond->wait_completion);
+ return result;
+ } else {
+ mutex_unlock(&ftdi->u132_lock);
+ msleep(100);
+ goto wait;
+ }
+ }
+}
+
+static int ftdi_elan_read_pcimem(struct usb_ftdi *ftdi, int mem_offset,
+ u8 width, u32 *data)
+{
+ u8 addressofs = mem_offset / 4;
+wait:if (ftdi->disconnected > 0) {
+ return -ENODEV;
+ } else {
+ int command_size;
+ int respond_size;
+ mutex_lock(&ftdi->u132_lock);
+ command_size = ftdi->command_next - ftdi->command_head;
+ respond_size = ftdi->respond_next - ftdi->respond_head;
+ if (command_size < COMMAND_SIZE && respond_size < RESPOND_SIZE)
+ {
+ struct u132_command *command = &ftdi->command[
+ COMMAND_MASK & ftdi->command_next];
+ struct u132_respond *respond = &ftdi->respond[
+ RESPOND_MASK & ftdi->respond_next];
+ int result = -ENODEV;
+ respond->result = &result;
+ respond->header = command->header = 0x00 | (cPCImemrd &
+ 0x0F);
+ command->length = 0x04;
+ respond->address = command->address = addressofs;
+ command->width = 0x00 | (width & 0x0F);
+ command->follows = 0;
+ command->value = 0;
+ command->buffer = NULL;
+ respond->value = data;
+ init_completion(&respond->wait_completion);
+ ftdi->command_next += 1;
+ ftdi->respond_next += 1;
+ ftdi_elan_kick_command_queue(ftdi);
+ mutex_unlock(&ftdi->u132_lock);
+ wait_for_completion(&respond->wait_completion);
+ return result;
+ } else {
+ mutex_unlock(&ftdi->u132_lock);
+ msleep(100);
+ goto wait;
+ }
+ }
+}
+
+int usb_ftdi_elan_read_pcimem(struct platform_device *pdev, int mem_offset,
+ u8 width, u32 *data)
+{
+ struct usb_ftdi *ftdi = platform_device_to_usb_ftdi(pdev);
+ if (ftdi->initialized == 0) {
+ return -ENODEV;
+ } else
+ return ftdi_elan_read_pcimem(ftdi, mem_offset, width, data);
+}
+
+
+EXPORT_SYMBOL_GPL(usb_ftdi_elan_read_pcimem);
+static int ftdi_elan_edset_setup(struct usb_ftdi *ftdi, u8 ed_number,
+ void *endp, struct urb *urb, u8 address, u8 ep_number, u8 toggle_bits,
+ 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))
+{
+ u8 ed = ed_number - 1;
+wait:if (ftdi->disconnected > 0) {
+ return -ENODEV;
+ } else if (ftdi->initialized == 0) {
+ return -ENODEV;
+ } else {
+ int command_size;
+ mutex_lock(&ftdi->u132_lock);
+ command_size = ftdi->command_next - ftdi->command_head;
+ if (command_size < COMMAND_SIZE) {
+ struct u132_target *target = &ftdi->target[ed];
+ struct u132_command *command = &ftdi->command[
+ COMMAND_MASK & ftdi->command_next];
+ command->header = 0x80 | (ed << 5);
+ command->length = 0x8007;
+ command->address = (toggle_bits << 6) | (ep_number << 2)
+ | (address << 0);
+ command->width = usb_maxpacket(urb->dev, urb->pipe);
+ command->follows = 8;
+ command->value = 0;
+ command->buffer = urb->setup_packet;
+ target->callback = callback;
+ target->endp = endp;
+ target->urb = urb;
+ target->active = 1;
+ ftdi->command_next += 1;
+ ftdi_elan_kick_command_queue(ftdi);
+ mutex_unlock(&ftdi->u132_lock);
+ return 0;
+ } else {
+ mutex_unlock(&ftdi->u132_lock);
+ msleep(100);
+ goto wait;
+ }
+ }
+}
+
+int usb_ftdi_elan_edset_setup(struct platform_device *pdev, u8 ed_number,
+ void *endp, struct urb *urb, u8 address, u8 ep_number, u8 toggle_bits,
+ 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))
+{
+ struct usb_ftdi *ftdi = platform_device_to_usb_ftdi(pdev);
+ return ftdi_elan_edset_setup(ftdi, ed_number, endp, urb, address,
+ ep_number, toggle_bits, callback);
+}
+
+
+EXPORT_SYMBOL_GPL(usb_ftdi_elan_edset_setup);
+static int ftdi_elan_edset_input(struct usb_ftdi *ftdi, u8 ed_number,
+ void *endp, struct urb *urb, u8 address, u8 ep_number, u8 toggle_bits,
+ 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))
+{
+ u8 ed = ed_number - 1;
+wait:if (ftdi->disconnected > 0) {
+ return -ENODEV;
+ } else if (ftdi->initialized == 0) {
+ return -ENODEV;
+ } else {
+ int command_size;
+ mutex_lock(&ftdi->u132_lock);
+ command_size = ftdi->command_next - ftdi->command_head;
+ if (command_size < COMMAND_SIZE) {
+ struct u132_target *target = &ftdi->target[ed];
+ struct u132_command *command = &ftdi->command[
+ COMMAND_MASK & ftdi->command_next];
+ u32 remaining_length = urb->transfer_buffer_length -
+ urb->actual_length;
+ command->header = 0x82 | (ed << 5);
+ if (remaining_length == 0) {
+ command->length = 0x0000;
+ } else if (remaining_length > 1024) {
+ command->length = 0x8000 | 1023;
+ } else
+ command->length = 0x8000 | (remaining_length -
+ 1);
+ command->address = (toggle_bits << 6) | (ep_number << 2)
+ | (address << 0);
+ command->width = usb_maxpacket(urb->dev, urb->pipe);
+ command->follows = 0;
+ command->value = 0;
+ command->buffer = NULL;
+ target->callback = callback;
+ target->endp = endp;
+ target->urb = urb;
+ target->active = 1;
+ ftdi->command_next += 1;
+ ftdi_elan_kick_command_queue(ftdi);
+ mutex_unlock(&ftdi->u132_lock);
+ return 0;
+ } else {
+ mutex_unlock(&ftdi->u132_lock);
+ msleep(100);
+ goto wait;
+ }
+ }
+}
+
+int usb_ftdi_elan_edset_input(struct platform_device *pdev, u8 ed_number,
+ void *endp, struct urb *urb, u8 address, u8 ep_number, u8 toggle_bits,
+ 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))
+{
+ struct usb_ftdi *ftdi = platform_device_to_usb_ftdi(pdev);
+ return ftdi_elan_edset_input(ftdi, ed_number, endp, urb, address,
+ ep_number, toggle_bits, callback);
+}
+
+
+EXPORT_SYMBOL_GPL(usb_ftdi_elan_edset_input);
+static int ftdi_elan_edset_empty(struct usb_ftdi *ftdi, u8 ed_number,
+ void *endp, struct urb *urb, u8 address, u8 ep_number, u8 toggle_bits,
+ 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))
+{
+ u8 ed = ed_number - 1;
+wait:if (ftdi->disconnected > 0) {
+ return -ENODEV;
+ } else if (ftdi->initialized == 0) {
+ return -ENODEV;
+ } else {
+ int command_size;
+ mutex_lock(&ftdi->u132_lock);
+ command_size = ftdi->command_next - ftdi->command_head;
+ if (command_size < COMMAND_SIZE) {
+ struct u132_target *target = &ftdi->target[ed];
+ struct u132_command *command = &ftdi->command[
+ COMMAND_MASK & ftdi->command_next];
+ command->header = 0x81 | (ed << 5);
+ command->length = 0x0000;
+ command->address = (toggle_bits << 6) | (ep_number << 2)
+ | (address << 0);
+ command->width = usb_maxpacket(urb->dev, urb->pipe);
+ command->follows = 0;
+ command->value = 0;
+ command->buffer = NULL;
+ target->callback = callback;
+ target->endp = endp;
+ target->urb = urb;
+ target->active = 1;
+ ftdi->command_next += 1;
+ ftdi_elan_kick_command_queue(ftdi);
+ mutex_unlock(&ftdi->u132_lock);
+ return 0;
+ } else {
+ mutex_unlock(&ftdi->u132_lock);
+ msleep(100);
+ goto wait;
+ }
+ }
+}
+
+int usb_ftdi_elan_edset_empty(struct platform_device *pdev, u8 ed_number,
+ void *endp, struct urb *urb, u8 address, u8 ep_number, u8 toggle_bits,
+ 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))
+{
+ struct usb_ftdi *ftdi = platform_device_to_usb_ftdi(pdev);
+ return ftdi_elan_edset_empty(ftdi, ed_number, endp, urb, address,
+ ep_number, toggle_bits, callback);
+}
+
+
+EXPORT_SYMBOL_GPL(usb_ftdi_elan_edset_empty);
+static int ftdi_elan_edset_output(struct usb_ftdi *ftdi, u8 ed_number,
+ void *endp, struct urb *urb, u8 address, u8 ep_number, u8 toggle_bits,
+ 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))
+{
+ u8 ed = ed_number - 1;
+wait:if (ftdi->disconnected > 0) {
+ return -ENODEV;
+ } else if (ftdi->initialized == 0) {
+ return -ENODEV;
+ } else {
+ int command_size;
+ mutex_lock(&ftdi->u132_lock);
+ command_size = ftdi->command_next - ftdi->command_head;
+ if (command_size < COMMAND_SIZE) {
+ u8 *b;
+ u16 urb_size;
+ int i = 0;
+ char data[30 *3 + 4];
+ char *d = data;
+ int m = (sizeof(data) - 1) / 3 - 1;
+ int l = 0;
+ struct u132_target *target = &ftdi->target[ed];
+ struct u132_command *command = &ftdi->command[
+ COMMAND_MASK & ftdi->command_next];
+ command->header = 0x81 | (ed << 5);
+ command->address = (toggle_bits << 6) | (ep_number << 2)
+ | (address << 0);
+ command->width = usb_maxpacket(urb->dev, urb->pipe);
+ command->follows = min_t(u32, 1024,
+ urb->transfer_buffer_length -
+ urb->actual_length);
+ command->value = 0;
+ command->buffer = urb->transfer_buffer +
+ urb->actual_length;
+ command->length = 0x8000 | (command->follows - 1);
+ b = command->buffer;
+ urb_size = command->follows;
+ data[0] = 0;
+ while (urb_size-- > 0) {
+ if (i > m) {
+ } else if (i++ < m) {
+ int w = sprintf(d, " %02X", *b++);
+ d += w;
+ l += w;
+ } else
+ d += sprintf(d, " ..");
+ }
+ target->callback = callback;
+ target->endp = endp;
+ target->urb = urb;
+ target->active = 1;
+ ftdi->command_next += 1;
+ ftdi_elan_kick_command_queue(ftdi);
+ mutex_unlock(&ftdi->u132_lock);
+ return 0;
+ } else {
+ mutex_unlock(&ftdi->u132_lock);
+ msleep(100);
+ goto wait;
+ }
+ }
+}
+
+int usb_ftdi_elan_edset_output(struct platform_device *pdev, u8 ed_number,
+ void *endp, struct urb *urb, u8 address, u8 ep_number, u8 toggle_bits,
+ 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))
+{
+ struct usb_ftdi *ftdi = platform_device_to_usb_ftdi(pdev);
+ return ftdi_elan_edset_output(ftdi, ed_number, endp, urb, address,
+ ep_number, toggle_bits, callback);
+}
+
+
+EXPORT_SYMBOL_GPL(usb_ftdi_elan_edset_output);
+static int ftdi_elan_edset_single(struct usb_ftdi *ftdi, u8 ed_number,
+ void *endp, struct urb *urb, u8 address, u8 ep_number, u8 toggle_bits,
+ 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))
+{
+ u8 ed = ed_number - 1;
+wait:if (ftdi->disconnected > 0) {
+ return -ENODEV;
+ } else if (ftdi->initialized == 0) {
+ return -ENODEV;
+ } else {
+ int command_size;
+ mutex_lock(&ftdi->u132_lock);
+ command_size = ftdi->command_next - ftdi->command_head;
+ if (command_size < COMMAND_SIZE) {
+ u32 remaining_length = urb->transfer_buffer_length -
+ urb->actual_length;
+ struct u132_target *target = &ftdi->target[ed];
+ struct u132_command *command = &ftdi->command[
+ COMMAND_MASK & ftdi->command_next];
+ command->header = 0x83 | (ed << 5);
+ if (remaining_length == 0) {
+ command->length = 0x0000;
+ } else if (remaining_length > 1024) {
+ command->length = 0x8000 | 1023;
+ } else
+ command->length = 0x8000 | (remaining_length -
+ 1);
+ command->address = (toggle_bits << 6) | (ep_number << 2)
+ | (address << 0);
+ command->width = usb_maxpacket(urb->dev, urb->pipe);
+ command->follows = 0;
+ command->value = 0;
+ command->buffer = NULL;
+ target->callback = callback;
+ target->endp = endp;
+ target->urb = urb;
+ target->active = 1;
+ ftdi->command_next += 1;
+ ftdi_elan_kick_command_queue(ftdi);
+ mutex_unlock(&ftdi->u132_lock);
+ return 0;
+ } else {
+ mutex_unlock(&ftdi->u132_lock);
+ msleep(100);
+ goto wait;
+ }
+ }
+}
+
+int usb_ftdi_elan_edset_single(struct platform_device *pdev, u8 ed_number,
+ void *endp, struct urb *urb, u8 address, u8 ep_number, u8 toggle_bits,
+ 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))
+{
+ struct usb_ftdi *ftdi = platform_device_to_usb_ftdi(pdev);
+ return ftdi_elan_edset_single(ftdi, ed_number, endp, urb, address,
+ ep_number, toggle_bits, callback);
+}
+
+
+EXPORT_SYMBOL_GPL(usb_ftdi_elan_edset_single);
+static int ftdi_elan_edset_flush(struct usb_ftdi *ftdi, u8 ed_number,
+ void *endp)
+{
+ u8 ed = ed_number - 1;
+ if (ftdi->disconnected > 0) {
+ return -ENODEV;
+ } else if (ftdi->initialized == 0) {
+ return -ENODEV;
+ } else {
+ struct u132_target *target = &ftdi->target[ed];
+ mutex_lock(&ftdi->u132_lock);
+ if (target->abandoning > 0) {
+ mutex_unlock(&ftdi->u132_lock);
+ return 0;
+ } else {
+ 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 << 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 {
+ mutex_unlock(&ftdi->u132_lock);
+ msleep(100);
+ mutex_lock(&ftdi->u132_lock);
+ goto wait_1;
+ }
+ }
+ mutex_unlock(&ftdi->u132_lock);
+ return 0;
+ }
+ }
+}
+
+int usb_ftdi_elan_edset_flush(struct platform_device *pdev, u8 ed_number,
+ void *endp)
+{
+ struct usb_ftdi *ftdi = platform_device_to_usb_ftdi(pdev);
+ return ftdi_elan_edset_flush(ftdi, ed_number, endp);
+}
+
+
+EXPORT_SYMBOL_GPL(usb_ftdi_elan_edset_flush);
+static int ftdi_elan_flush_input_fifo(struct usb_ftdi *ftdi)
+{
+ int retry_on_empty = 10;
+ int retry_on_timeout = 5;
+ int retry_on_status = 20;
+more:{
+ 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, 100);
+ if (packet_bytes > 2) {
+ char diag[30 *3 + 4];
+ char *d = diag;
+ int m = (sizeof(diag) - 1) / 3 - 1;
+ char *b = ftdi->bulk_in_buffer;
+ int bytes_read = 0;
+ diag[0] = 0;
+ while (packet_bytes-- > 0) {
+ char c = *b++;
+ if (bytes_read < m) {
+ d += sprintf(d, " %02X",
+ 0x000000FF & c);
+ } else if (bytes_read > m) {
+ } else
+ d += sprintf(d, " ..");
+ bytes_read += 1;
+ continue;
+ }
+ goto more;
+ } else if (packet_bytes > 1) {
+ char s1 = ftdi->bulk_in_buffer[0];
+ char s2 = ftdi->bulk_in_buffer[1];
+ if (s1 == 0x31 && s2 == 0x60) {
+ return 0;
+ } else if (retry_on_status-- > 0) {
+ goto more;
+ } else {
+ dev_err(&ftdi->udev->dev, "STATUS ERROR retry limit reached\n");
+ return -EFAULT;
+ }
+ } else if (packet_bytes > 0) {
+ char b1 = ftdi->bulk_in_buffer[0];
+ dev_err(&ftdi->udev->dev, "only one byte flushed from FTDI = %02X\n",
+ b1);
+ if (retry_on_status-- > 0) {
+ goto more;
+ } else {
+ dev_err(&ftdi->udev->dev, "STATUS ERROR retry limit reached\n");
+ return -EFAULT;
+ }
+ } else if (retval == -ETIMEDOUT) {
+ if (retry_on_timeout-- > 0) {
+ goto more;
+ } else {
+ dev_err(&ftdi->udev->dev, "TIMED OUT retry limit reached\n");
+ return -ENOMEM;
+ }
+ } else if (retval == 0) {
+ if (retry_on_empty-- > 0) {
+ goto more;
+ } else {
+ dev_err(&ftdi->udev->dev, "empty packet retry limit reached\n");
+ return -ENOMEM;
+ }
+ } else {
+ dev_err(&ftdi->udev->dev, "error = %d\n", retval);
+ return retval;
+ }
+ }
+ return -1;
+}
+
+
+/*
+ * send the long flush sequence
+ *
+ */
+static int ftdi_elan_synchronize_flush(struct usb_ftdi *ftdi)
+{
+ int retval;
+ struct urb *urb;
+ char *buf;
+ int I = 257;
+ int i = 0;
+ urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!urb)
+ return -ENOMEM;
+ buf = usb_alloc_coherent(ftdi->udev, I, GFP_KERNEL, &urb->transfer_dma);
+ if (!buf) {
+ dev_err(&ftdi->udev->dev, "could not get a buffer for flush sequence\n");
+ usb_free_urb(urb);
+ return -ENOMEM;
+ }
+ while (I-- > 0)
+ buf[i++] = 0x55;
+ usb_fill_bulk_urb(urb, ftdi->udev, usb_sndbulkpipe(ftdi->udev,
+ ftdi->bulk_out_endpointAddr), buf, i,
+ ftdi_elan_write_bulk_callback, ftdi);
+ urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+ retval = usb_submit_urb(urb, GFP_KERNEL);
+ if (retval) {
+ dev_err(&ftdi->udev->dev, "failed to submit urb containing the flush sequence\n");
+ usb_free_coherent(ftdi->udev, i, buf, urb->transfer_dma);
+ usb_free_urb(urb);
+ return -ENOMEM;
+ }
+ usb_free_urb(urb);
+ return 0;
+}
+
+
+/*
+ * send the reset sequence
+ *
+ */
+static int ftdi_elan_synchronize_reset(struct usb_ftdi *ftdi)
+{
+ int retval;
+ struct urb *urb;
+ char *buf;
+ int I = 4;
+ int i = 0;
+ urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!urb)
+ return -ENOMEM;
+ buf = usb_alloc_coherent(ftdi->udev, I, GFP_KERNEL, &urb->transfer_dma);
+ if (!buf) {
+ dev_err(&ftdi->udev->dev, "could not get a buffer for the reset sequence\n");
+ usb_free_urb(urb);
+ return -ENOMEM;
+ }
+ buf[i++] = 0x55;
+ buf[i++] = 0xAA;
+ buf[i++] = 0x5A;
+ buf[i++] = 0xA5;
+ usb_fill_bulk_urb(urb, ftdi->udev, usb_sndbulkpipe(ftdi->udev,
+ ftdi->bulk_out_endpointAddr), buf, i,
+ ftdi_elan_write_bulk_callback, ftdi);
+ urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+ retval = usb_submit_urb(urb, GFP_KERNEL);
+ if (retval) {
+ dev_err(&ftdi->udev->dev, "failed to submit urb containing the reset sequence\n");
+ usb_free_coherent(ftdi->udev, i, buf, urb->transfer_dma);
+ usb_free_urb(urb);
+ return -ENOMEM;
+ }
+ usb_free_urb(urb);
+ return 0;
+}
+
+static int ftdi_elan_synchronize(struct usb_ftdi *ftdi)
+{
+ int retval;
+ int long_stop = 10;
+ int retry_on_timeout = 5;
+ int retry_on_empty = 10;
+ int err_count = 0;
+ retval = ftdi_elan_flush_input_fifo(ftdi);
+ if (retval)
+ return retval;
+ ftdi->bulk_in_left = 0;
+ ftdi->bulk_in_last = -1;
+ while (long_stop-- > 0) {
+ int read_stop;
+ int read_stuck;
+ retval = ftdi_elan_synchronize_flush(ftdi);
+ if (retval)
+ return retval;
+ retval = ftdi_elan_flush_input_fifo(ftdi);
+ if (retval)
+ return retval;
+ reset:retval = ftdi_elan_synchronize_reset(ftdi);
+ if (retval)
+ return retval;
+ read_stop = 100;
+ read_stuck = 10;
+ read:{
+ int packet_bytes = 0;
+ retval = usb_bulk_msg(ftdi->udev,
+ usb_rcvbulkpipe(ftdi->udev,
+ ftdi->bulk_in_endpointAddr),
+ ftdi->bulk_in_buffer, ftdi->bulk_in_size,
+ &packet_bytes, 500);
+ if (packet_bytes > 2) {
+ char diag[30 *3 + 4];
+ char *d = diag;
+ int m = (sizeof(diag) - 1) / 3 - 1;
+ char *b = ftdi->bulk_in_buffer;
+ int bytes_read = 0;
+ unsigned char c = 0;
+ diag[0] = 0;
+ while (packet_bytes-- > 0) {
+ c = *b++;
+ if (bytes_read < m) {
+ d += sprintf(d, " %02X", c);
+ } else if (bytes_read > m) {
+ } else
+ d += sprintf(d, " ..");
+ bytes_read += 1;
+ continue;
+ }
+ if (c == 0x7E) {
+ return 0;
+ } else {
+ if (c == 0x55) {
+ goto read;
+ } else if (read_stop-- > 0) {
+ goto read;
+ } else {
+ dev_err(&ftdi->udev->dev, "retry limit reached\n");
+ continue;
+ }
+ }
+ } else if (packet_bytes > 1) {
+ unsigned char s1 = ftdi->bulk_in_buffer[0];
+ unsigned char s2 = ftdi->bulk_in_buffer[1];
+ if (s1 == 0x31 && s2 == 0x00) {
+ if (read_stuck-- > 0) {
+ goto read;
+ } else
+ goto reset;
+ } else {
+ if (read_stop-- > 0) {
+ goto read;
+ } else {
+ dev_err(&ftdi->udev->dev, "retry limit reached\n");
+ continue;
+ }
+ }
+ } else if (packet_bytes > 0) {
+ if (read_stop-- > 0) {
+ goto read;
+ } else {
+ dev_err(&ftdi->udev->dev, "retry limit reached\n");
+ continue;
+ }
+ } else if (retval == -ETIMEDOUT) {
+ if (retry_on_timeout-- > 0) {
+ goto read;
+ } else {
+ dev_err(&ftdi->udev->dev, "TIMED OUT retry limit reached\n");
+ continue;
+ }
+ } else if (retval == 0) {
+ if (retry_on_empty-- > 0) {
+ goto read;
+ } else {
+ dev_err(&ftdi->udev->dev, "empty packet retry limit reached\n");
+ continue;
+ }
+ } else {
+ err_count += 1;
+ dev_err(&ftdi->udev->dev, "error = %d\n",
+ retval);
+ if (read_stop-- > 0) {
+ goto read;
+ } else {
+ dev_err(&ftdi->udev->dev, "retry limit reached\n");
+ continue;
+ }
+ }
+ }
+ }
+ dev_err(&ftdi->udev->dev, "failed to synchronize\n");
+ return -EFAULT;
+}
+
+static int ftdi_elan_stuck_waiting(struct usb_ftdi *ftdi)
+{
+ int retry_on_empty = 10;
+ int retry_on_timeout = 5;
+ int retry_on_status = 50;
+more:{
+ 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, 1000);
+ if (packet_bytes > 2) {
+ char diag[30 *3 + 4];
+ char *d = diag;
+ int m = (sizeof(diag) - 1) / 3 - 1;
+ char *b = ftdi->bulk_in_buffer;
+ int bytes_read = 0;
+ diag[0] = 0;
+ while (packet_bytes-- > 0) {
+ char c = *b++;
+ if (bytes_read < m) {
+ d += sprintf(d, " %02X",
+ 0x000000FF & c);
+ } else if (bytes_read > m) {
+ } else
+ d += sprintf(d, " ..");
+ bytes_read += 1;
+ }
+ goto more;
+ } else if (packet_bytes > 1) {
+ char s1 = ftdi->bulk_in_buffer[0];
+ char s2 = ftdi->bulk_in_buffer[1];
+ if (s1 == 0x31 && s2 == 0x60) {
+ return 0;
+ } else if (retry_on_status-- > 0) {
+ msleep(5);
+ goto more;
+ } else
+ return -EFAULT;
+ } else if (packet_bytes > 0) {
+ char b1 = ftdi->bulk_in_buffer[0];
+ dev_err(&ftdi->udev->dev, "only one byte flushed from FTDI = %02X\n", b1);
+ if (retry_on_status-- > 0) {
+ msleep(5);
+ goto more;
+ } else {
+ dev_err(&ftdi->udev->dev, "STATUS ERROR retry limit reached\n");
+ return -EFAULT;
+ }
+ } else if (retval == -ETIMEDOUT) {
+ if (retry_on_timeout-- > 0) {
+ goto more;
+ } else {
+ dev_err(&ftdi->udev->dev, "TIMED OUT retry limit reached\n");
+ return -ENOMEM;
+ }
+ } else if (retval == 0) {
+ if (retry_on_empty-- > 0) {
+ goto more;
+ } else {
+ dev_err(&ftdi->udev->dev, "empty packet retry limit reached\n");
+ return -ENOMEM;
+ }
+ } else {
+ dev_err(&ftdi->udev->dev, "error = %d\n", retval);
+ return -ENOMEM;
+ }
+ }
+ return -1;
+}
+
+static int ftdi_elan_checkingPCI(struct usb_ftdi *ftdi)
+{
+ int UxxxStatus = ftdi_elan_read_reg(ftdi, &ftdi->controlreg);
+ if (UxxxStatus)
+ return UxxxStatus;
+ if (ftdi->controlreg & 0x00400000) {
+ if (ftdi->card_ejected) {
+ } else {
+ ftdi->card_ejected = 1;
+ dev_err(&ftdi->udev->dev, "CARD EJECTED - controlreg = %08X\n",
+ ftdi->controlreg);
+ }
+ return -ENODEV;
+ } else {
+ u8 fn = ftdi->function - 1;
+ int activePCIfn = fn << 8;
+ u32 pcidata;
+ u32 pciVID;
+ u32 pciPID;
+ int reg = 0;
+ UxxxStatus = ftdi_elan_read_config(ftdi, activePCIfn | reg, 0,
+ &pcidata);
+ if (UxxxStatus)
+ return UxxxStatus;
+ pciVID = pcidata & 0xFFFF;
+ pciPID = (pcidata >> 16) & 0xFFFF;
+ if (pciVID == ftdi->platform_data.vendor && pciPID ==
+ ftdi->platform_data.device) {
+ return 0;
+ } else {
+ dev_err(&ftdi->udev->dev, "vendor=%04X pciVID=%04X device=%04X pciPID=%04X\n",
+ ftdi->platform_data.vendor, pciVID,
+ ftdi->platform_data.device, pciPID);
+ return -ENODEV;
+ }
+ }
+}
+
+
+#define ftdi_read_pcimem(ftdi, member, data) ftdi_elan_read_pcimem(ftdi, \
+ offsetof(struct ohci_regs, member), 0, data);
+#define ftdi_write_pcimem(ftdi, member, data) ftdi_elan_write_pcimem(ftdi, \
+ offsetof(struct ohci_regs, member), 0, data);
+
+#define OHCI_CONTROL_INIT OHCI_CTRL_CBSR
+#define OHCI_INTR_INIT (OHCI_INTR_MIE | OHCI_INTR_UE | OHCI_INTR_RD | \
+ OHCI_INTR_WDH)
+static int ftdi_elan_check_controller(struct usb_ftdi *ftdi, int quirk)
+{
+ int devices = 0;
+ int retval;
+ u32 hc_control;
+ int num_ports;
+ u32 control;
+ u32 rh_a = -1;
+ u32 status;
+ u32 fminterval;
+ u32 hc_fminterval;
+ u32 periodicstart;
+ u32 cmdstatus;
+ u32 roothub_a;
+ int mask = OHCI_INTR_INIT;
+ int sleep_time = 0;
+ int reset_timeout = 30; /* ... allow extra time */
+ int temp;
+ retval = ftdi_write_pcimem(ftdi, intrdisable, OHCI_INTR_MIE);
+ if (retval)
+ return retval;
+ retval = ftdi_read_pcimem(ftdi, control, &control);
+ if (retval)
+ return retval;
+ retval = ftdi_read_pcimem(ftdi, roothub.a, &rh_a);
+ if (retval)
+ return retval;
+ num_ports = rh_a & RH_A_NDP;
+ retval = ftdi_read_pcimem(ftdi, fminterval, &hc_fminterval);
+ if (retval)
+ return retval;
+ hc_fminterval &= 0x3fff;
+ if (hc_fminterval != FI) {
+ }
+ hc_fminterval |= FSMP(hc_fminterval) << 16;
+ retval = ftdi_read_pcimem(ftdi, control, &hc_control);
+ if (retval)
+ return retval;
+ switch (hc_control & OHCI_CTRL_HCFS) {
+ case OHCI_USB_OPER:
+ sleep_time = 0;
+ break;
+ case OHCI_USB_SUSPEND:
+ case OHCI_USB_RESUME:
+ hc_control &= OHCI_CTRL_RWC;
+ hc_control |= OHCI_USB_RESUME;
+ sleep_time = 10;
+ break;
+ default:
+ hc_control &= OHCI_CTRL_RWC;
+ hc_control |= OHCI_USB_RESET;
+ sleep_time = 50;
+ break;
+ }
+ retval = ftdi_write_pcimem(ftdi, control, hc_control);
+ if (retval)
+ return retval;
+ retval = ftdi_read_pcimem(ftdi, control, &control);
+ if (retval)
+ return retval;
+ msleep(sleep_time);
+ retval = ftdi_read_pcimem(ftdi, roothub.a, &roothub_a);
+ if (retval)
+ return retval;
+ if (!(roothub_a & RH_A_NPS)) { /* power down each port */
+ for (temp = 0; temp < num_ports; temp++) {
+ retval = ftdi_write_pcimem(ftdi,
+ roothub.portstatus[temp], RH_PS_LSDA);
+ if (retval)
+ return retval;
+ }
+ }
+ retval = ftdi_read_pcimem(ftdi, control, &control);
+ if (retval)
+ return retval;
+retry:retval = ftdi_read_pcimem(ftdi, cmdstatus, &status);
+ if (retval)
+ return retval;
+ retval = ftdi_write_pcimem(ftdi, cmdstatus, OHCI_HCR);
+ if (retval)
+ return retval;
+extra:{
+ retval = ftdi_read_pcimem(ftdi, cmdstatus, &status);
+ if (retval)
+ return retval;
+ if (0 != (status & OHCI_HCR)) {
+ if (--reset_timeout == 0) {
+ dev_err(&ftdi->udev->dev, "USB HC reset timed out!\n");
+ return -ENODEV;
+ } else {
+ msleep(5);
+ goto extra;
+ }
+ }
+ }
+ if (quirk & OHCI_QUIRK_INITRESET) {
+ retval = ftdi_write_pcimem(ftdi, control, hc_control);
+ if (retval)
+ return retval;
+ retval = ftdi_read_pcimem(ftdi, control, &control);
+ if (retval)
+ return retval;
+ }
+ retval = ftdi_write_pcimem(ftdi, ed_controlhead, 0x00000000);
+ if (retval)
+ return retval;
+ retval = ftdi_write_pcimem(ftdi, ed_bulkhead, 0x11000000);
+ if (retval)
+ return retval;
+ retval = ftdi_write_pcimem(ftdi, hcca, 0x00000000);
+ if (retval)
+ return retval;
+ retval = ftdi_read_pcimem(ftdi, fminterval, &fminterval);
+ if (retval)
+ return retval;
+ retval = ftdi_write_pcimem(ftdi, fminterval,
+ ((fminterval & FIT) ^ FIT) | hc_fminterval);
+ if (retval)
+ return retval;
+ retval = ftdi_write_pcimem(ftdi, periodicstart,
+ ((9 *hc_fminterval) / 10) & 0x3fff);
+ if (retval)
+ return retval;
+ retval = ftdi_read_pcimem(ftdi, fminterval, &fminterval);
+ if (retval)
+ return retval;
+ retval = ftdi_read_pcimem(ftdi, periodicstart, &periodicstart);
+ if (retval)
+ return retval;
+ if (0 == (fminterval & 0x3fff0000) || 0 == periodicstart) {
+ if (!(quirk & OHCI_QUIRK_INITRESET)) {
+ quirk |= OHCI_QUIRK_INITRESET;
+ goto retry;
+ } else
+ dev_err(&ftdi->udev->dev, "init err(%08x %04x)\n",
+ fminterval, periodicstart);
+ } /* start controller operations */
+ hc_control &= OHCI_CTRL_RWC;
+ hc_control |= OHCI_CONTROL_INIT | OHCI_CTRL_BLE | OHCI_USB_OPER;
+ retval = ftdi_write_pcimem(ftdi, control, hc_control);
+ if (retval)
+ return retval;
+ retval = ftdi_write_pcimem(ftdi, cmdstatus, OHCI_BLF);
+ if (retval)
+ return retval;
+ retval = ftdi_read_pcimem(ftdi, cmdstatus, &cmdstatus);
+ if (retval)
+ return retval;
+ retval = ftdi_read_pcimem(ftdi, control, &control);
+ if (retval)
+ return retval;
+ retval = ftdi_write_pcimem(ftdi, roothub.status, RH_HS_DRWE);
+ if (retval)
+ return retval;
+ retval = ftdi_write_pcimem(ftdi, intrstatus, mask);
+ if (retval)
+ return retval;
+ retval = ftdi_write_pcimem(ftdi, intrdisable,
+ OHCI_INTR_MIE | OHCI_INTR_OC | OHCI_INTR_RHSC | OHCI_INTR_FNO |
+ OHCI_INTR_UE | OHCI_INTR_RD | OHCI_INTR_SF | OHCI_INTR_WDH |
+ OHCI_INTR_SO);
+ if (retval)
+ return retval; /* handle root hub init quirks ... */
+ retval = ftdi_read_pcimem(ftdi, roothub.a, &roothub_a);
+ if (retval)
+ return retval;
+ roothub_a &= ~(RH_A_PSM | RH_A_OCPM);
+ if (quirk & OHCI_QUIRK_SUPERIO) {
+ roothub_a |= RH_A_NOCP;
+ roothub_a &= ~(RH_A_POTPGT | RH_A_NPS);
+ retval = ftdi_write_pcimem(ftdi, roothub.a, roothub_a);
+ if (retval)
+ return retval;
+ } else if ((quirk & OHCI_QUIRK_AMD756) || distrust_firmware) {
+ roothub_a |= RH_A_NPS;
+ retval = ftdi_write_pcimem(ftdi, roothub.a, roothub_a);
+ if (retval)
+ return retval;
+ }
+ retval = ftdi_write_pcimem(ftdi, roothub.status, RH_HS_LPSC);
+ if (retval)
+ return retval;
+ retval = ftdi_write_pcimem(ftdi, roothub.b,
+ (roothub_a & RH_A_NPS) ? 0 : RH_B_PPCM);
+ if (retval)
+ return retval;
+ retval = ftdi_read_pcimem(ftdi, control, &control);
+ if (retval)
+ return retval;
+ mdelay((roothub_a >> 23) & 0x1fe);
+ for (temp = 0; temp < num_ports; temp++) {
+ u32 portstatus;
+ retval = ftdi_read_pcimem(ftdi, roothub.portstatus[temp],
+ &portstatus);
+ if (retval)
+ return retval;
+ if (1 & portstatus)
+ devices += 1;
+ }
+ return devices;
+}
+
+static int ftdi_elan_setup_controller(struct usb_ftdi *ftdi, int fn)
+{
+ u32 latence_timer;
+ int UxxxStatus;
+ u32 pcidata;
+ int reg = 0;
+ int activePCIfn = fn << 8;
+ UxxxStatus = ftdi_elan_write_reg(ftdi, 0x0000025FL | 0x2800);
+ if (UxxxStatus)
+ return UxxxStatus;
+ reg = 16;
+ UxxxStatus = ftdi_elan_write_config(ftdi, activePCIfn | reg, 0,
+ 0xFFFFFFFF);
+ if (UxxxStatus)
+ return UxxxStatus;
+ UxxxStatus = ftdi_elan_read_config(ftdi, activePCIfn | reg, 0,
+ &pcidata);
+ if (UxxxStatus)
+ return UxxxStatus;
+ UxxxStatus = ftdi_elan_write_config(ftdi, activePCIfn | reg, 0,
+ 0xF0000000);
+ if (UxxxStatus)
+ return UxxxStatus;
+ UxxxStatus = ftdi_elan_read_config(ftdi, activePCIfn | reg, 0,
+ &pcidata);
+ if (UxxxStatus)
+ return UxxxStatus;
+ reg = 12;
+ UxxxStatus = ftdi_elan_read_config(ftdi, activePCIfn | reg, 0,
+ &latence_timer);
+ if (UxxxStatus)
+ return UxxxStatus;
+ latence_timer &= 0xFFFF00FF;
+ latence_timer |= 0x00001600;
+ UxxxStatus = ftdi_elan_write_config(ftdi, activePCIfn | reg, 0x00,
+ latence_timer);
+ if (UxxxStatus)
+ return UxxxStatus;
+ UxxxStatus = ftdi_elan_read_config(ftdi, activePCIfn | reg, 0,
+ &pcidata);
+ if (UxxxStatus)
+ return UxxxStatus;
+ reg = 4;
+ UxxxStatus = ftdi_elan_write_config(ftdi, activePCIfn | reg, 0x00,
+ 0x06);
+ if (UxxxStatus)
+ return UxxxStatus;
+ UxxxStatus = ftdi_elan_read_config(ftdi, activePCIfn | reg, 0,
+ &pcidata);
+ if (UxxxStatus)
+ return UxxxStatus;
+ for (reg = 0; reg <= 0x54; reg += 4) {
+ UxxxStatus = ftdi_elan_read_pcimem(ftdi, reg, 0, &pcidata);
+ if (UxxxStatus)
+ return UxxxStatus;
+ }
+ return 0;
+}
+
+static int ftdi_elan_close_controller(struct usb_ftdi *ftdi, int fn)
+{
+ u32 latence_timer;
+ int UxxxStatus;
+ u32 pcidata;
+ int reg = 0;
+ int activePCIfn = fn << 8;
+ UxxxStatus = ftdi_elan_write_reg(ftdi, 0x0000025FL | 0x2800);
+ if (UxxxStatus)
+ return UxxxStatus;
+ reg = 16;
+ UxxxStatus = ftdi_elan_write_config(ftdi, activePCIfn | reg, 0,
+ 0xFFFFFFFF);
+ if (UxxxStatus)
+ return UxxxStatus;
+ UxxxStatus = ftdi_elan_read_config(ftdi, activePCIfn | reg, 0,
+ &pcidata);
+ if (UxxxStatus)
+ return UxxxStatus;
+ UxxxStatus = ftdi_elan_write_config(ftdi, activePCIfn | reg, 0,
+ 0x00000000);
+ if (UxxxStatus)
+ return UxxxStatus;
+ UxxxStatus = ftdi_elan_read_config(ftdi, activePCIfn | reg, 0,
+ &pcidata);
+ if (UxxxStatus)
+ return UxxxStatus;
+ reg = 12;
+ UxxxStatus = ftdi_elan_read_config(ftdi, activePCIfn | reg, 0,
+ &latence_timer);
+ if (UxxxStatus)
+ return UxxxStatus;
+ latence_timer &= 0xFFFF00FF;
+ latence_timer |= 0x00001600;
+ UxxxStatus = ftdi_elan_write_config(ftdi, activePCIfn | reg, 0x00,
+ latence_timer);
+ if (UxxxStatus)
+ return UxxxStatus;
+ UxxxStatus = ftdi_elan_read_config(ftdi, activePCIfn | reg, 0,
+ &pcidata);
+ if (UxxxStatus)
+ return UxxxStatus;
+ reg = 4;
+ UxxxStatus = ftdi_elan_write_config(ftdi, activePCIfn | reg, 0x00,
+ 0x00);
+ if (UxxxStatus)
+ return UxxxStatus;
+ return ftdi_elan_read_config(ftdi, activePCIfn | reg, 0, &pcidata);
+}
+
+static int ftdi_elan_found_controller(struct usb_ftdi *ftdi, int fn, int quirk)
+{
+ int result;
+ int UxxxStatus;
+ UxxxStatus = ftdi_elan_setup_controller(ftdi, fn);
+ if (UxxxStatus)
+ return UxxxStatus;
+ result = ftdi_elan_check_controller(ftdi, quirk);
+ UxxxStatus = ftdi_elan_close_controller(ftdi, fn);
+ if (UxxxStatus)
+ return UxxxStatus;
+ return result;
+}
+
+static int ftdi_elan_enumeratePCI(struct usb_ftdi *ftdi)
+{
+ u32 controlreg;
+ u8 sensebits;
+ int UxxxStatus;
+ UxxxStatus = ftdi_elan_read_reg(ftdi, &controlreg);
+ if (UxxxStatus)
+ return UxxxStatus;
+ UxxxStatus = ftdi_elan_write_reg(ftdi, 0x00000000L);
+ if (UxxxStatus)
+ return UxxxStatus;
+ msleep(750);
+ UxxxStatus = ftdi_elan_write_reg(ftdi, 0x00000200L | 0x100);
+ if (UxxxStatus)
+ return UxxxStatus;
+ UxxxStatus = ftdi_elan_write_reg(ftdi, 0x00000200L | 0x500);
+ if (UxxxStatus)
+ return UxxxStatus;
+ UxxxStatus = ftdi_elan_read_reg(ftdi, &controlreg);
+ if (UxxxStatus)
+ return UxxxStatus;
+ UxxxStatus = ftdi_elan_write_reg(ftdi, 0x0000020CL | 0x000);
+ if (UxxxStatus)
+ return UxxxStatus;
+ UxxxStatus = ftdi_elan_write_reg(ftdi, 0x0000020DL | 0x000);
+ if (UxxxStatus)
+ return UxxxStatus;
+ msleep(250);
+ UxxxStatus = ftdi_elan_write_reg(ftdi, 0x0000020FL | 0x000);
+ if (UxxxStatus)
+ return UxxxStatus;
+ UxxxStatus = ftdi_elan_read_reg(ftdi, &controlreg);
+ if (UxxxStatus)
+ return UxxxStatus;
+ UxxxStatus = ftdi_elan_write_reg(ftdi, 0x0000025FL | 0x800);
+ if (UxxxStatus)
+ return UxxxStatus;
+ UxxxStatus = ftdi_elan_read_reg(ftdi, &controlreg);
+ if (UxxxStatus)
+ return UxxxStatus;
+ UxxxStatus = ftdi_elan_read_reg(ftdi, &controlreg);
+ if (UxxxStatus)
+ return UxxxStatus;
+ msleep(1000);
+ sensebits = (controlreg >> 16) & 0x000F;
+ if (0x0D == sensebits)
+ return 0;
+ else
+ return - ENXIO;
+}
+
+static int ftdi_elan_setupOHCI(struct usb_ftdi *ftdi)
+{
+ int UxxxStatus;
+ u32 pcidata;
+ int reg = 0;
+ u8 fn;
+ int activePCIfn = 0;
+ int max_devices = 0;
+ int controllers = 0;
+ int unrecognized = 0;
+ ftdi->function = 0;
+ for (fn = 0; (fn < 4); fn++) {
+ u32 pciVID = 0;
+ u32 pciPID = 0;
+ int devices = 0;
+ activePCIfn = fn << 8;
+ UxxxStatus = ftdi_elan_read_config(ftdi, activePCIfn | reg, 0,
+ &pcidata);
+ if (UxxxStatus)
+ return UxxxStatus;
+ pciVID = pcidata & 0xFFFF;
+ pciPID = (pcidata >> 16) & 0xFFFF;
+ if ((pciVID == PCI_VENDOR_ID_OPTI) && (pciPID == 0xc861)) {
+ devices = ftdi_elan_found_controller(ftdi, fn, 0);
+ controllers += 1;
+ } else if ((pciVID == PCI_VENDOR_ID_NEC) && (pciPID == 0x0035))
+ {
+ devices = ftdi_elan_found_controller(ftdi, fn, 0);
+ controllers += 1;
+ } else if ((pciVID == PCI_VENDOR_ID_AL) && (pciPID == 0x5237)) {
+ devices = ftdi_elan_found_controller(ftdi, fn, 0);
+ controllers += 1;
+ } else if ((pciVID == PCI_VENDOR_ID_ATT) && (pciPID == 0x5802))
+ {
+ devices = ftdi_elan_found_controller(ftdi, fn, 0);
+ controllers += 1;
+ } else if (pciVID == PCI_VENDOR_ID_AMD && pciPID == 0x740c) {
+ devices = ftdi_elan_found_controller(ftdi, fn,
+ OHCI_QUIRK_AMD756);
+ controllers += 1;
+ } else if (pciVID == PCI_VENDOR_ID_COMPAQ && pciPID == 0xa0f8) {
+ devices = ftdi_elan_found_controller(ftdi, fn,
+ OHCI_QUIRK_ZFMICRO);
+ controllers += 1;
+ } else if (0 == pcidata) {
+ } else
+ unrecognized += 1;
+ if (devices > max_devices) {
+ max_devices = devices;
+ ftdi->function = fn + 1;
+ ftdi->platform_data.vendor = pciVID;
+ ftdi->platform_data.device = pciPID;
+ }
+ }
+ if (ftdi->function > 0) {
+ return ftdi_elan_setup_controller(ftdi, ftdi->function - 1);
+ } else if (controllers > 0) {
+ return -ENXIO;
+ } else if (unrecognized > 0) {
+ return -ENXIO;
+ } else {
+ ftdi->enumerated = 0;
+ return -ENXIO;
+ }
+}
+
+
+/*
+ * we use only the first bulk-in and bulk-out endpoints
+ */
+static int ftdi_elan_probe(struct usb_interface *interface,
+ const struct usb_device_id *id)
+{
+ struct usb_host_interface *iface_desc;
+ struct usb_endpoint_descriptor *bulk_in, *bulk_out;
+ int retval;
+ struct usb_ftdi *ftdi;
+
+ ftdi = kzalloc(sizeof(struct usb_ftdi), GFP_KERNEL);
+ if (!ftdi)
+ return -ENOMEM;
+
+ mutex_lock(&ftdi_module_lock);
+ list_add_tail(&ftdi->ftdi_list, &ftdi_static_list);
+ ftdi->sequence_num = ++ftdi_instances;
+ mutex_unlock(&ftdi_module_lock);
+ ftdi_elan_init_kref(ftdi);
+ sema_init(&ftdi->sw_lock, 1);
+ ftdi->udev = usb_get_dev(interface_to_usbdev(interface));
+ ftdi->interface = interface;
+ mutex_init(&ftdi->u132_lock);
+ ftdi->expected = 4;
+
+ iface_desc = interface->cur_altsetting;
+ retval = usb_find_common_endpoints(iface_desc,
+ &bulk_in, &bulk_out, NULL, NULL);
+ if (retval) {
+ dev_err(&ftdi->udev->dev, "Could not find both bulk-in and bulk-out endpoints\n");
+ goto error;
+ }
+
+ ftdi->bulk_in_size = usb_endpoint_maxp(bulk_in);
+ ftdi->bulk_in_endpointAddr = bulk_in->bEndpointAddress;
+ ftdi->bulk_in_buffer = kmalloc(ftdi->bulk_in_size, GFP_KERNEL);
+ if (!ftdi->bulk_in_buffer) {
+ retval = -ENOMEM;
+ goto error;
+ }
+
+ ftdi->bulk_out_endpointAddr = bulk_out->bEndpointAddress;
+
+ dev_info(&ftdi->udev->dev, "interface %d has I=%02X O=%02X\n",
+ iface_desc->desc.bInterfaceNumber, ftdi->bulk_in_endpointAddr,
+ ftdi->bulk_out_endpointAddr);
+ usb_set_intfdata(interface, ftdi);
+ if (iface_desc->desc.bInterfaceNumber == 0 &&
+ ftdi->bulk_in_endpointAddr == 0x81 &&
+ ftdi->bulk_out_endpointAddr == 0x02) {
+ retval = usb_register_dev(interface, &ftdi_elan_jtag_class);
+ if (retval) {
+ dev_err(&ftdi->udev->dev, "Not able to get a minor for this device\n");
+ usb_set_intfdata(interface, NULL);
+ retval = -ENOMEM;
+ goto error;
+ } else {
+ ftdi->class = &ftdi_elan_jtag_class;
+ dev_info(&ftdi->udev->dev, "USB FDTI=%p JTAG interface %d now attached to ftdi%d\n",
+ ftdi, iface_desc->desc.bInterfaceNumber,
+ interface->minor);
+ return 0;
+ }
+ } else if (iface_desc->desc.bInterfaceNumber == 1 &&
+ ftdi->bulk_in_endpointAddr == 0x83 &&
+ ftdi->bulk_out_endpointAddr == 0x04) {
+ ftdi->class = NULL;
+ dev_info(&ftdi->udev->dev, "USB FDTI=%p ELAN interface %d now activated\n",
+ ftdi, iface_desc->desc.bInterfaceNumber);
+ INIT_DELAYED_WORK(&ftdi->status_work, ftdi_elan_status_work);
+ INIT_DELAYED_WORK(&ftdi->command_work, ftdi_elan_command_work);
+ INIT_DELAYED_WORK(&ftdi->respond_work, ftdi_elan_respond_work);
+ ftdi_status_queue_work(ftdi, msecs_to_jiffies(3 *1000));
+ return 0;
+ } else {
+ dev_err(&ftdi->udev->dev,
+ "Could not find ELAN's U132 device\n");
+ retval = -ENODEV;
+ goto error;
+ }
+error:if (ftdi) {
+ ftdi_elan_put_kref(ftdi);
+ }
+ return retval;
+}
+
+static void ftdi_elan_disconnect(struct usb_interface *interface)
+{
+ struct usb_ftdi *ftdi = usb_get_intfdata(interface);
+ ftdi->disconnected += 1;
+ if (ftdi->class) {
+ int minor = interface->minor;
+ struct usb_class_driver *class = ftdi->class;
+ usb_set_intfdata(interface, NULL);
+ usb_deregister_dev(interface, class);
+ dev_info(&ftdi->udev->dev, "USB FTDI U132 jtag interface on minor %d now disconnected\n",
+ minor);
+ } else {
+ ftdi_status_cancel_work(ftdi);
+ ftdi_command_cancel_work(ftdi);
+ ftdi_response_cancel_work(ftdi);
+ ftdi_elan_abandon_completions(ftdi);
+ ftdi_elan_abandon_targets(ftdi);
+ if (ftdi->registered) {
+ platform_device_unregister(&ftdi->platform_dev);
+ ftdi->synchronized = 0;
+ ftdi->enumerated = 0;
+ ftdi->initialized = 0;
+ ftdi->registered = 0;
+ }
+ ftdi->disconnected += 1;
+ usb_set_intfdata(interface, NULL);
+ dev_info(&ftdi->udev->dev, "USB FTDI U132 host controller interface now disconnected\n");
+ }
+ ftdi_elan_put_kref(ftdi);
+}
+
+static struct usb_driver ftdi_elan_driver = {
+ .name = "ftdi-elan",
+ .probe = ftdi_elan_probe,
+ .disconnect = ftdi_elan_disconnect,
+ .id_table = ftdi_elan_table,
+};
+static int __init ftdi_elan_init(void)
+{
+ int result;
+ pr_info("driver %s\n", ftdi_elan_driver.name);
+ mutex_init(&ftdi_module_lock);
+ INIT_LIST_HEAD(&ftdi_static_list);
+ result = usb_register(&ftdi_elan_driver);
+ if (result) {
+ pr_err("usb_register failed. Error number %d\n", result);
+ }
+ return result;
+
+}
+
+static void __exit ftdi_elan_exit(void)
+{
+ struct usb_ftdi *ftdi;
+ struct usb_ftdi *temp;
+ usb_deregister(&ftdi_elan_driver);
+ pr_info("ftdi_u132 driver deregistered\n");
+ list_for_each_entry_safe(ftdi, temp, &ftdi_static_list, ftdi_list) {
+ ftdi_status_cancel_work(ftdi);
+ ftdi_command_cancel_work(ftdi);
+ ftdi_response_cancel_work(ftdi);
+ }
+}
+
+
+module_init(ftdi_elan_init);
+module_exit(ftdi_elan_exit);
diff --git a/drivers/usb/misc/idmouse.c b/drivers/usb/misc/idmouse.c
new file mode 100644
index 000000000..ea39243ef
--- /dev/null
+++ b/drivers/usb/misc/idmouse.c
@@ -0,0 +1,407 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* Siemens ID Mouse driver v0.6
+
+ Copyright (C) 2004-5 by Florian 'Floe' Echtler <echtler@fs.tum.de>
+ and Andreas 'ad' Deresch <aderesch@fs.tum.de>
+
+ Derived from the USB Skeleton driver 1.1,
+ Copyright (C) 2003 Greg Kroah-Hartman (greg@kroah.com)
+
+ Additional information provided by Martin Reising
+ <Martin.Reising@natural-computing.de>
+
+*/
+
+#include <linux/kernel.h>
+#include <linux/sched/signal.h>
+#include <linux/errno.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/completion.h>
+#include <linux/mutex.h>
+#include <linux/uaccess.h>
+#include <linux/usb.h>
+
+/* image constants */
+#define WIDTH 225
+#define HEIGHT 289
+#define HEADER "P5 225 289 255 "
+#define IMGSIZE ((WIDTH * HEIGHT) + sizeof(HEADER)-1)
+
+#define DRIVER_SHORT "idmouse"
+#define DRIVER_AUTHOR "Florian 'Floe' Echtler <echtler@fs.tum.de>"
+#define DRIVER_DESC "Siemens ID Mouse FingerTIP Sensor Driver"
+
+/* minor number for misc USB devices */
+#define USB_IDMOUSE_MINOR_BASE 132
+
+/* vendor and device IDs */
+#define ID_SIEMENS 0x0681
+#define ID_IDMOUSE 0x0005
+#define ID_CHERRY 0x0010
+
+/* device ID table */
+static const struct usb_device_id idmouse_table[] = {
+ {USB_DEVICE(ID_SIEMENS, ID_IDMOUSE)}, /* Siemens ID Mouse (Professional) */
+ {USB_DEVICE(ID_SIEMENS, ID_CHERRY )}, /* Cherry FingerTIP ID Board */
+ {} /* terminating null entry */
+};
+
+/* sensor commands */
+#define FTIP_RESET 0x20
+#define FTIP_ACQUIRE 0x21
+#define FTIP_RELEASE 0x22
+#define FTIP_BLINK 0x23 /* LSB of value = blink pulse width */
+#define FTIP_SCROLL 0x24
+
+#define ftip_command(dev, command, value, index) \
+ usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, 0), command, \
+ USB_TYPE_VENDOR | USB_RECIP_ENDPOINT | USB_DIR_OUT, value, index, NULL, 0, 1000)
+
+MODULE_DEVICE_TABLE(usb, idmouse_table);
+
+/* structure to hold all of our device specific stuff */
+struct usb_idmouse {
+
+ struct usb_device *udev; /* save off the usb device pointer */
+ struct usb_interface *interface; /* the interface for this device */
+
+ unsigned char *bulk_in_buffer; /* the buffer to receive data */
+ size_t bulk_in_size; /* the maximum bulk packet size */
+ size_t orig_bi_size; /* same as above, but reported by the device */
+ __u8 bulk_in_endpointAddr; /* the address of the bulk in endpoint */
+
+ int open; /* if the port is open or not */
+ int present; /* if the device is not disconnected */
+ struct mutex lock; /* locks this structure */
+
+};
+
+/* local function prototypes */
+static ssize_t idmouse_read(struct file *file, char __user *buffer,
+ size_t count, loff_t * ppos);
+
+static int idmouse_open(struct inode *inode, struct file *file);
+static int idmouse_release(struct inode *inode, struct file *file);
+
+static int idmouse_probe(struct usb_interface *interface,
+ const struct usb_device_id *id);
+
+static void idmouse_disconnect(struct usb_interface *interface);
+static int idmouse_suspend(struct usb_interface *intf, pm_message_t message);
+static int idmouse_resume(struct usb_interface *intf);
+
+/* file operation pointers */
+static const struct file_operations idmouse_fops = {
+ .owner = THIS_MODULE,
+ .read = idmouse_read,
+ .open = idmouse_open,
+ .release = idmouse_release,
+ .llseek = default_llseek,
+};
+
+/* class driver information */
+static struct usb_class_driver idmouse_class = {
+ .name = "idmouse%d",
+ .fops = &idmouse_fops,
+ .minor_base = USB_IDMOUSE_MINOR_BASE,
+};
+
+/* usb specific object needed to register this driver with the usb subsystem */
+static struct usb_driver idmouse_driver = {
+ .name = DRIVER_SHORT,
+ .probe = idmouse_probe,
+ .disconnect = idmouse_disconnect,
+ .suspend = idmouse_suspend,
+ .resume = idmouse_resume,
+ .reset_resume = idmouse_resume,
+ .id_table = idmouse_table,
+ .supports_autosuspend = 1,
+};
+
+static int idmouse_create_image(struct usb_idmouse *dev)
+{
+ int bytes_read;
+ int bulk_read;
+ int result;
+
+ memcpy(dev->bulk_in_buffer, HEADER, sizeof(HEADER)-1);
+ bytes_read = sizeof(HEADER)-1;
+
+ /* reset the device and set a fast blink rate */
+ result = ftip_command(dev, FTIP_RELEASE, 0, 0);
+ if (result < 0)
+ goto reset;
+ result = ftip_command(dev, FTIP_BLINK, 1, 0);
+ if (result < 0)
+ goto reset;
+
+ /* initialize the sensor - sending this command twice */
+ /* significantly reduces the rate of failed reads */
+ result = ftip_command(dev, FTIP_ACQUIRE, 0, 0);
+ if (result < 0)
+ goto reset;
+ result = ftip_command(dev, FTIP_ACQUIRE, 0, 0);
+ if (result < 0)
+ goto reset;
+
+ /* start the readout - sending this command twice */
+ /* presumably enables the high dynamic range mode */
+ result = ftip_command(dev, FTIP_RESET, 0, 0);
+ if (result < 0)
+ goto reset;
+ result = ftip_command(dev, FTIP_RESET, 0, 0);
+ if (result < 0)
+ goto reset;
+
+ /* loop over a blocking bulk read to get data from the device */
+ while (bytes_read < IMGSIZE) {
+ result = usb_bulk_msg(dev->udev,
+ usb_rcvbulkpipe(dev->udev, dev->bulk_in_endpointAddr),
+ dev->bulk_in_buffer + bytes_read,
+ dev->bulk_in_size, &bulk_read, 5000);
+ if (result < 0) {
+ /* Maybe this error was caused by the increased packet size? */
+ /* Reset to the original value and tell userspace to retry. */
+ if (dev->bulk_in_size != dev->orig_bi_size) {
+ dev->bulk_in_size = dev->orig_bi_size;
+ result = -EAGAIN;
+ }
+ break;
+ }
+ if (signal_pending(current)) {
+ result = -EINTR;
+ break;
+ }
+ bytes_read += bulk_read;
+ }
+
+ /* check for valid image */
+ /* right border should be black (0x00) */
+ for (bytes_read = sizeof(HEADER)-1 + WIDTH-1; bytes_read < IMGSIZE; bytes_read += WIDTH)
+ if (dev->bulk_in_buffer[bytes_read] != 0x00)
+ return -EAGAIN;
+
+ /* lower border should be white (0xFF) */
+ for (bytes_read = IMGSIZE-WIDTH; bytes_read < IMGSIZE-1; bytes_read++)
+ if (dev->bulk_in_buffer[bytes_read] != 0xFF)
+ return -EAGAIN;
+
+ /* reset the device */
+reset:
+ ftip_command(dev, FTIP_RELEASE, 0, 0);
+
+ /* should be IMGSIZE == 65040 */
+ dev_dbg(&dev->interface->dev, "read %d bytes fingerprint data\n",
+ bytes_read);
+ return result;
+}
+
+/* PM operations are nops as this driver does IO only during open() */
+static int idmouse_suspend(struct usb_interface *intf, pm_message_t message)
+{
+ return 0;
+}
+
+static int idmouse_resume(struct usb_interface *intf)
+{
+ return 0;
+}
+
+static inline void idmouse_delete(struct usb_idmouse *dev)
+{
+ kfree(dev->bulk_in_buffer);
+ kfree(dev);
+}
+
+static int idmouse_open(struct inode *inode, struct file *file)
+{
+ struct usb_idmouse *dev;
+ struct usb_interface *interface;
+ int result;
+
+ /* get the interface from minor number and driver information */
+ interface = usb_find_interface(&idmouse_driver, iminor(inode));
+ if (!interface)
+ return -ENODEV;
+
+ /* get the device information block from the interface */
+ dev = usb_get_intfdata(interface);
+ if (!dev)
+ return -ENODEV;
+
+ /* lock this device */
+ mutex_lock(&dev->lock);
+
+ /* check if already open */
+ if (dev->open) {
+
+ /* already open, so fail */
+ result = -EBUSY;
+
+ } else {
+
+ /* create a new image and check for success */
+ result = usb_autopm_get_interface(interface);
+ if (result)
+ goto error;
+ result = idmouse_create_image(dev);
+ usb_autopm_put_interface(interface);
+ if (result)
+ goto error;
+
+ /* increment our usage count for the driver */
+ ++dev->open;
+
+ /* save our object in the file's private structure */
+ file->private_data = dev;
+
+ }
+
+error:
+
+ /* unlock this device */
+ mutex_unlock(&dev->lock);
+ return result;
+}
+
+static int idmouse_release(struct inode *inode, struct file *file)
+{
+ struct usb_idmouse *dev;
+
+ dev = file->private_data;
+
+ if (dev == NULL)
+ return -ENODEV;
+
+ /* lock our device */
+ mutex_lock(&dev->lock);
+
+ --dev->open;
+
+ if (!dev->present) {
+ /* the device was unplugged before the file was released */
+ mutex_unlock(&dev->lock);
+ idmouse_delete(dev);
+ } else {
+ mutex_unlock(&dev->lock);
+ }
+ return 0;
+}
+
+static ssize_t idmouse_read(struct file *file, char __user *buffer, size_t count,
+ loff_t * ppos)
+{
+ struct usb_idmouse *dev = file->private_data;
+ int result;
+
+ /* lock this object */
+ mutex_lock(&dev->lock);
+
+ /* verify that the device wasn't unplugged */
+ if (!dev->present) {
+ mutex_unlock(&dev->lock);
+ return -ENODEV;
+ }
+
+ result = simple_read_from_buffer(buffer, count, ppos,
+ dev->bulk_in_buffer, IMGSIZE);
+ /* unlock the device */
+ mutex_unlock(&dev->lock);
+ return result;
+}
+
+static int idmouse_probe(struct usb_interface *interface,
+ const struct usb_device_id *id)
+{
+ struct usb_device *udev = interface_to_usbdev(interface);
+ struct usb_idmouse *dev;
+ struct usb_host_interface *iface_desc;
+ struct usb_endpoint_descriptor *endpoint;
+ int result;
+
+ /* check if we have gotten the data or the hid interface */
+ iface_desc = interface->cur_altsetting;
+ if (iface_desc->desc.bInterfaceClass != 0x0A)
+ return -ENODEV;
+
+ if (iface_desc->desc.bNumEndpoints < 1)
+ return -ENODEV;
+
+ /* allocate memory for our device state and initialize it */
+ dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+ if (dev == NULL)
+ return -ENOMEM;
+
+ mutex_init(&dev->lock);
+ dev->udev = udev;
+ dev->interface = interface;
+
+ /* set up the endpoint information - use only the first bulk-in endpoint */
+ result = usb_find_bulk_in_endpoint(iface_desc, &endpoint);
+ if (result) {
+ dev_err(&interface->dev, "Unable to find bulk-in endpoint.\n");
+ idmouse_delete(dev);
+ return result;
+ }
+
+ dev->orig_bi_size = usb_endpoint_maxp(endpoint);
+ dev->bulk_in_size = 0x200; /* works _much_ faster */
+ dev->bulk_in_endpointAddr = endpoint->bEndpointAddress;
+ dev->bulk_in_buffer = kmalloc(IMGSIZE + dev->bulk_in_size, GFP_KERNEL);
+ if (!dev->bulk_in_buffer) {
+ idmouse_delete(dev);
+ return -ENOMEM;
+ }
+
+ /* allow device read, write and ioctl */
+ dev->present = 1;
+
+ /* we can register the device now, as it is ready */
+ usb_set_intfdata(interface, dev);
+ result = usb_register_dev(interface, &idmouse_class);
+ if (result) {
+ /* something prevented us from registering this device */
+ dev_err(&interface->dev, "Unable to allocate minor number.\n");
+ idmouse_delete(dev);
+ return result;
+ }
+
+ /* be noisy */
+ dev_info(&interface->dev,"%s now attached\n",DRIVER_DESC);
+
+ return 0;
+}
+
+static void idmouse_disconnect(struct usb_interface *interface)
+{
+ struct usb_idmouse *dev = usb_get_intfdata(interface);
+
+ /* give back our minor */
+ usb_deregister_dev(interface, &idmouse_class);
+
+ /* lock the device */
+ mutex_lock(&dev->lock);
+
+ /* prevent device read, write and ioctl */
+ dev->present = 0;
+
+ /* if the device is opened, idmouse_release will clean this up */
+ if (!dev->open) {
+ mutex_unlock(&dev->lock);
+ idmouse_delete(dev);
+ } else {
+ /* unlock */
+ mutex_unlock(&dev->lock);
+ }
+
+ dev_info(&interface->dev, "disconnected\n");
+}
+
+module_usb_driver(idmouse_driver);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/usb/misc/iowarrior.c b/drivers/usb/misc/iowarrior.c
new file mode 100644
index 000000000..b421f1326
--- /dev/null
+++ b/drivers/usb/misc/iowarrior.c
@@ -0,0 +1,926 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Native support for the I/O-Warrior USB devices
+ *
+ * Copyright (c) 2003-2005, 2020 Code Mercenaries GmbH
+ * written by Christian Lucht <lucht@codemercs.com> and
+ * Christoph Jung <jung@codemercs.com>
+ *
+ * based on
+
+ * usb-skeleton.c by Greg Kroah-Hartman <greg@kroah.com>
+ * brlvger.c by Stephane Dalton <sdalton@videotron.ca>
+ * and Stephane Doyon <s.doyon@videotron.ca>
+ *
+ * Released under the GPLv2.
+ */
+
+#include <linux/module.h>
+#include <linux/usb.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include <linux/mutex.h>
+#include <linux/poll.h>
+#include <linux/usb/iowarrior.h>
+
+#define DRIVER_AUTHOR "Christian Lucht <lucht@codemercs.com>"
+#define DRIVER_DESC "USB IO-Warrior driver"
+
+#define USB_VENDOR_ID_CODEMERCS 1984
+/* low speed iowarrior */
+#define USB_DEVICE_ID_CODEMERCS_IOW40 0x1500
+#define USB_DEVICE_ID_CODEMERCS_IOW24 0x1501
+#define USB_DEVICE_ID_CODEMERCS_IOWPV1 0x1511
+#define USB_DEVICE_ID_CODEMERCS_IOWPV2 0x1512
+/* full speed iowarrior */
+#define USB_DEVICE_ID_CODEMERCS_IOW56 0x1503
+/* fuller speed iowarrior */
+#define USB_DEVICE_ID_CODEMERCS_IOW28 0x1504
+#define USB_DEVICE_ID_CODEMERCS_IOW28L 0x1505
+#define USB_DEVICE_ID_CODEMERCS_IOW100 0x1506
+
+/* OEMed devices */
+#define USB_DEVICE_ID_CODEMERCS_IOW24SAG 0x158a
+#define USB_DEVICE_ID_CODEMERCS_IOW56AM 0x158b
+
+/* Get a minor range for your devices from the usb maintainer */
+#ifdef CONFIG_USB_DYNAMIC_MINORS
+#define IOWARRIOR_MINOR_BASE 0
+#else
+#define IOWARRIOR_MINOR_BASE 208 // SKELETON_MINOR_BASE 192 + 16, not official yet
+#endif
+
+/* interrupt input queue size */
+#define MAX_INTERRUPT_BUFFER 16
+/*
+ maximum number of urbs that are submitted for writes at the same time,
+ this applies to the IOWarrior56 only!
+ IOWarrior24 and IOWarrior40 use synchronous usb_control_msg calls.
+*/
+#define MAX_WRITES_IN_FLIGHT 4
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
+
+static struct usb_driver iowarrior_driver;
+
+/*--------------*/
+/* data */
+/*--------------*/
+
+/* Structure to hold all of our device specific stuff */
+struct iowarrior {
+ struct mutex mutex; /* locks this structure */
+ struct usb_device *udev; /* save off the usb device pointer */
+ struct usb_interface *interface; /* the interface for this device */
+ unsigned char minor; /* the starting minor number for this device */
+ struct usb_endpoint_descriptor *int_out_endpoint; /* endpoint for reading (needed for IOW56 only) */
+ struct usb_endpoint_descriptor *int_in_endpoint; /* endpoint for reading */
+ struct urb *int_in_urb; /* the urb for reading data */
+ unsigned char *int_in_buffer; /* buffer for data to be read */
+ unsigned char serial_number; /* to detect lost packages */
+ unsigned char *read_queue; /* size is MAX_INTERRUPT_BUFFER * packet size */
+ wait_queue_head_t read_wait;
+ wait_queue_head_t write_wait; /* wait-queue for writing to the device */
+ atomic_t write_busy; /* number of write-urbs submitted */
+ atomic_t read_idx;
+ atomic_t intr_idx;
+ atomic_t overflow_flag; /* signals an index 'rollover' */
+ int present; /* this is 1 as long as the device is connected */
+ int opened; /* this is 1 if the device is currently open */
+ char chip_serial[9]; /* the serial number string of the chip connected */
+ int report_size; /* number of bytes in a report */
+ u16 product_id;
+ struct usb_anchor submitted;
+};
+
+/*--------------*/
+/* globals */
+/*--------------*/
+
+#define USB_REQ_GET_REPORT 0x01
+//#if 0
+static int usb_get_report(struct usb_device *dev,
+ struct usb_host_interface *inter, unsigned char type,
+ unsigned char id, void *buf, int size)
+{
+ return usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
+ USB_REQ_GET_REPORT,
+ USB_DIR_IN | USB_TYPE_CLASS |
+ USB_RECIP_INTERFACE, (type << 8) + id,
+ inter->desc.bInterfaceNumber, buf, size,
+ USB_CTRL_GET_TIMEOUT);
+}
+//#endif
+
+#define USB_REQ_SET_REPORT 0x09
+
+static int usb_set_report(struct usb_interface *intf, unsigned char type,
+ unsigned char id, void *buf, int size)
+{
+ return usb_control_msg(interface_to_usbdev(intf),
+ usb_sndctrlpipe(interface_to_usbdev(intf), 0),
+ USB_REQ_SET_REPORT,
+ USB_TYPE_CLASS | USB_RECIP_INTERFACE,
+ (type << 8) + id,
+ intf->cur_altsetting->desc.bInterfaceNumber, buf,
+ size, 1000);
+}
+
+/*---------------------*/
+/* driver registration */
+/*---------------------*/
+/* table of devices that work with this driver */
+static const struct usb_device_id iowarrior_ids[] = {
+ {USB_DEVICE(USB_VENDOR_ID_CODEMERCS, USB_DEVICE_ID_CODEMERCS_IOW40)},
+ {USB_DEVICE(USB_VENDOR_ID_CODEMERCS, USB_DEVICE_ID_CODEMERCS_IOW24)},
+ {USB_DEVICE(USB_VENDOR_ID_CODEMERCS, USB_DEVICE_ID_CODEMERCS_IOWPV1)},
+ {USB_DEVICE(USB_VENDOR_ID_CODEMERCS, USB_DEVICE_ID_CODEMERCS_IOWPV2)},
+ {USB_DEVICE(USB_VENDOR_ID_CODEMERCS, USB_DEVICE_ID_CODEMERCS_IOW56)},
+ {USB_DEVICE(USB_VENDOR_ID_CODEMERCS, USB_DEVICE_ID_CODEMERCS_IOW24SAG)},
+ {USB_DEVICE(USB_VENDOR_ID_CODEMERCS, USB_DEVICE_ID_CODEMERCS_IOW56AM)},
+ {USB_DEVICE(USB_VENDOR_ID_CODEMERCS, USB_DEVICE_ID_CODEMERCS_IOW28)},
+ {USB_DEVICE(USB_VENDOR_ID_CODEMERCS, USB_DEVICE_ID_CODEMERCS_IOW28L)},
+ {USB_DEVICE(USB_VENDOR_ID_CODEMERCS, USB_DEVICE_ID_CODEMERCS_IOW100)},
+ {} /* Terminating entry */
+};
+MODULE_DEVICE_TABLE(usb, iowarrior_ids);
+
+/*
+ * USB callback handler for reading data
+ */
+static void iowarrior_callback(struct urb *urb)
+{
+ struct iowarrior *dev = urb->context;
+ int intr_idx;
+ int read_idx;
+ int aux_idx;
+ int offset;
+ int status = urb->status;
+ int retval;
+
+ switch (status) {
+ case 0:
+ /* success */
+ break;
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ return;
+ default:
+ goto exit;
+ }
+
+ intr_idx = atomic_read(&dev->intr_idx);
+ /* aux_idx become previous intr_idx */
+ aux_idx = (intr_idx == 0) ? (MAX_INTERRUPT_BUFFER - 1) : (intr_idx - 1);
+ read_idx = atomic_read(&dev->read_idx);
+
+ /* queue is not empty and it's interface 0 */
+ if ((intr_idx != read_idx)
+ && (dev->interface->cur_altsetting->desc.bInterfaceNumber == 0)) {
+ /* + 1 for serial number */
+ offset = aux_idx * (dev->report_size + 1);
+ if (!memcmp
+ (dev->read_queue + offset, urb->transfer_buffer,
+ dev->report_size)) {
+ /* equal values on interface 0 will be ignored */
+ goto exit;
+ }
+ }
+
+ /* aux_idx become next intr_idx */
+ aux_idx = (intr_idx == (MAX_INTERRUPT_BUFFER - 1)) ? 0 : (intr_idx + 1);
+ if (read_idx == aux_idx) {
+ /* queue full, dropping oldest input */
+ read_idx = (++read_idx == MAX_INTERRUPT_BUFFER) ? 0 : read_idx;
+ atomic_set(&dev->read_idx, read_idx);
+ atomic_set(&dev->overflow_flag, 1);
+ }
+
+ /* +1 for serial number */
+ offset = intr_idx * (dev->report_size + 1);
+ memcpy(dev->read_queue + offset, urb->transfer_buffer,
+ dev->report_size);
+ *(dev->read_queue + offset + (dev->report_size)) = dev->serial_number++;
+
+ atomic_set(&dev->intr_idx, aux_idx);
+ /* tell the blocking read about the new data */
+ wake_up_interruptible(&dev->read_wait);
+
+exit:
+ retval = usb_submit_urb(urb, GFP_ATOMIC);
+ if (retval)
+ dev_err(&dev->interface->dev, "%s - usb_submit_urb failed with result %d\n",
+ __func__, retval);
+
+}
+
+/*
+ * USB Callback handler for write-ops
+ */
+static void iowarrior_write_callback(struct urb *urb)
+{
+ struct iowarrior *dev;
+ int status = urb->status;
+
+ dev = urb->context;
+ /* sync/async unlink faults aren't errors */
+ if (status &&
+ !(status == -ENOENT ||
+ status == -ECONNRESET || status == -ESHUTDOWN)) {
+ dev_dbg(&dev->interface->dev,
+ "nonzero write bulk status received: %d\n", status);
+ }
+ /* free up our allocated buffer */
+ usb_free_coherent(urb->dev, urb->transfer_buffer_length,
+ urb->transfer_buffer, urb->transfer_dma);
+ /* tell a waiting writer the interrupt-out-pipe is available again */
+ atomic_dec(&dev->write_busy);
+ wake_up_interruptible(&dev->write_wait);
+}
+
+/*
+ * iowarrior_delete
+ */
+static inline void iowarrior_delete(struct iowarrior *dev)
+{
+ dev_dbg(&dev->interface->dev, "minor %d\n", dev->minor);
+ kfree(dev->int_in_buffer);
+ usb_free_urb(dev->int_in_urb);
+ kfree(dev->read_queue);
+ usb_put_intf(dev->interface);
+ kfree(dev);
+}
+
+/*---------------------*/
+/* fops implementation */
+/*---------------------*/
+
+static int read_index(struct iowarrior *dev)
+{
+ int intr_idx, read_idx;
+
+ read_idx = atomic_read(&dev->read_idx);
+ intr_idx = atomic_read(&dev->intr_idx);
+
+ return (read_idx == intr_idx ? -1 : read_idx);
+}
+
+/*
+ * iowarrior_read
+ */
+static ssize_t iowarrior_read(struct file *file, char __user *buffer,
+ size_t count, loff_t *ppos)
+{
+ struct iowarrior *dev;
+ int read_idx;
+ int offset;
+
+ dev = file->private_data;
+
+ /* verify that the device wasn't unplugged */
+ if (!dev || !dev->present)
+ return -ENODEV;
+
+ dev_dbg(&dev->interface->dev, "minor %d, count = %zd\n",
+ dev->minor, count);
+
+ /* read count must be packet size (+ time stamp) */
+ if ((count != dev->report_size)
+ && (count != (dev->report_size + 1)))
+ return -EINVAL;
+
+ /* repeat until no buffer overrun in callback handler occur */
+ do {
+ atomic_set(&dev->overflow_flag, 0);
+ if ((read_idx = read_index(dev)) == -1) {
+ /* queue empty */
+ if (file->f_flags & O_NONBLOCK)
+ return -EAGAIN;
+ else {
+ //next line will return when there is either new data, or the device is unplugged
+ int r = wait_event_interruptible(dev->read_wait,
+ (!dev->present
+ || (read_idx =
+ read_index
+ (dev)) !=
+ -1));
+ if (r) {
+ //we were interrupted by a signal
+ return -ERESTART;
+ }
+ if (!dev->present) {
+ //The device was unplugged
+ return -ENODEV;
+ }
+ if (read_idx == -1) {
+ // Can this happen ???
+ return 0;
+ }
+ }
+ }
+
+ offset = read_idx * (dev->report_size + 1);
+ if (copy_to_user(buffer, dev->read_queue + offset, count)) {
+ return -EFAULT;
+ }
+ } while (atomic_read(&dev->overflow_flag));
+
+ read_idx = ++read_idx == MAX_INTERRUPT_BUFFER ? 0 : read_idx;
+ atomic_set(&dev->read_idx, read_idx);
+ return count;
+}
+
+/*
+ * iowarrior_write
+ */
+static ssize_t iowarrior_write(struct file *file,
+ const char __user *user_buffer,
+ size_t count, loff_t *ppos)
+{
+ struct iowarrior *dev;
+ int retval = 0;
+ char *buf = NULL; /* for IOW24 and IOW56 we need a buffer */
+ struct urb *int_out_urb = NULL;
+
+ dev = file->private_data;
+
+ mutex_lock(&dev->mutex);
+ /* verify that the device wasn't unplugged */
+ if (!dev->present) {
+ retval = -ENODEV;
+ goto exit;
+ }
+ dev_dbg(&dev->interface->dev, "minor %d, count = %zd\n",
+ dev->minor, count);
+ /* if count is 0 we're already done */
+ if (count == 0) {
+ retval = 0;
+ goto exit;
+ }
+ /* We only accept full reports */
+ if (count != dev->report_size) {
+ retval = -EINVAL;
+ goto exit;
+ }
+ switch (dev->product_id) {
+ case USB_DEVICE_ID_CODEMERCS_IOW24:
+ case USB_DEVICE_ID_CODEMERCS_IOW24SAG:
+ case USB_DEVICE_ID_CODEMERCS_IOWPV1:
+ case USB_DEVICE_ID_CODEMERCS_IOWPV2:
+ case USB_DEVICE_ID_CODEMERCS_IOW40:
+ /* IOW24 and IOW40 use a synchronous call */
+ buf = memdup_user(user_buffer, count);
+ if (IS_ERR(buf)) {
+ retval = PTR_ERR(buf);
+ goto exit;
+ }
+ retval = usb_set_report(dev->interface, 2, 0, buf, count);
+ kfree(buf);
+ goto exit;
+ case USB_DEVICE_ID_CODEMERCS_IOW56:
+ case USB_DEVICE_ID_CODEMERCS_IOW56AM:
+ case USB_DEVICE_ID_CODEMERCS_IOW28:
+ case USB_DEVICE_ID_CODEMERCS_IOW28L:
+ case USB_DEVICE_ID_CODEMERCS_IOW100:
+ /* The IOW56 uses asynchronous IO and more urbs */
+ if (atomic_read(&dev->write_busy) == MAX_WRITES_IN_FLIGHT) {
+ /* Wait until we are below the limit for submitted urbs */
+ if (file->f_flags & O_NONBLOCK) {
+ retval = -EAGAIN;
+ goto exit;
+ } else {
+ retval = wait_event_interruptible(dev->write_wait,
+ (!dev->present || (atomic_read (&dev-> write_busy) < MAX_WRITES_IN_FLIGHT)));
+ if (retval) {
+ /* we were interrupted by a signal */
+ retval = -ERESTART;
+ goto exit;
+ }
+ if (!dev->present) {
+ /* The device was unplugged */
+ retval = -ENODEV;
+ goto exit;
+ }
+ if (!dev->opened) {
+ /* We were closed while waiting for an URB */
+ retval = -ENODEV;
+ goto exit;
+ }
+ }
+ }
+ atomic_inc(&dev->write_busy);
+ int_out_urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!int_out_urb) {
+ retval = -ENOMEM;
+ goto error_no_urb;
+ }
+ buf = usb_alloc_coherent(dev->udev, dev->report_size,
+ GFP_KERNEL, &int_out_urb->transfer_dma);
+ if (!buf) {
+ retval = -ENOMEM;
+ dev_dbg(&dev->interface->dev,
+ "Unable to allocate buffer\n");
+ goto error_no_buffer;
+ }
+ usb_fill_int_urb(int_out_urb, dev->udev,
+ usb_sndintpipe(dev->udev,
+ dev->int_out_endpoint->bEndpointAddress),
+ buf, dev->report_size,
+ iowarrior_write_callback, dev,
+ dev->int_out_endpoint->bInterval);
+ int_out_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+ if (copy_from_user(buf, user_buffer, count)) {
+ retval = -EFAULT;
+ goto error;
+ }
+ usb_anchor_urb(int_out_urb, &dev->submitted);
+ retval = usb_submit_urb(int_out_urb, GFP_KERNEL);
+ if (retval) {
+ dev_dbg(&dev->interface->dev,
+ "submit error %d for urb nr.%d\n",
+ retval, atomic_read(&dev->write_busy));
+ usb_unanchor_urb(int_out_urb);
+ goto error;
+ }
+ /* submit was ok */
+ retval = count;
+ usb_free_urb(int_out_urb);
+ goto exit;
+ default:
+ /* what do we have here ? An unsupported Product-ID ? */
+ dev_err(&dev->interface->dev, "%s - not supported for product=0x%x\n",
+ __func__, dev->product_id);
+ retval = -EFAULT;
+ goto exit;
+ }
+error:
+ usb_free_coherent(dev->udev, dev->report_size, buf,
+ int_out_urb->transfer_dma);
+error_no_buffer:
+ usb_free_urb(int_out_urb);
+error_no_urb:
+ atomic_dec(&dev->write_busy);
+ wake_up_interruptible(&dev->write_wait);
+exit:
+ mutex_unlock(&dev->mutex);
+ return retval;
+}
+
+/*
+ * iowarrior_ioctl
+ */
+static long iowarrior_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ struct iowarrior *dev = NULL;
+ __u8 *buffer;
+ __u8 __user *user_buffer;
+ int retval;
+ int io_res; /* checks for bytes read/written and copy_to/from_user results */
+
+ dev = file->private_data;
+ if (!dev)
+ return -ENODEV;
+
+ buffer = kzalloc(dev->report_size, GFP_KERNEL);
+ if (!buffer)
+ return -ENOMEM;
+
+ mutex_lock(&dev->mutex);
+
+ /* verify that the device wasn't unplugged */
+ if (!dev->present) {
+ retval = -ENODEV;
+ goto error_out;
+ }
+
+ dev_dbg(&dev->interface->dev, "minor %d, cmd 0x%.4x, arg %ld\n",
+ dev->minor, cmd, arg);
+
+ retval = 0;
+ io_res = 0;
+ switch (cmd) {
+ case IOW_WRITE:
+ if (dev->product_id == USB_DEVICE_ID_CODEMERCS_IOW24 ||
+ dev->product_id == USB_DEVICE_ID_CODEMERCS_IOW24SAG ||
+ dev->product_id == USB_DEVICE_ID_CODEMERCS_IOWPV1 ||
+ dev->product_id == USB_DEVICE_ID_CODEMERCS_IOWPV2 ||
+ dev->product_id == USB_DEVICE_ID_CODEMERCS_IOW40) {
+ user_buffer = (__u8 __user *)arg;
+ io_res = copy_from_user(buffer, user_buffer,
+ dev->report_size);
+ if (io_res) {
+ retval = -EFAULT;
+ } else {
+ io_res = usb_set_report(dev->interface, 2, 0,
+ buffer,
+ dev->report_size);
+ if (io_res < 0)
+ retval = io_res;
+ }
+ } else {
+ retval = -EINVAL;
+ dev_err(&dev->interface->dev,
+ "ioctl 'IOW_WRITE' is not supported for product=0x%x.\n",
+ dev->product_id);
+ }
+ break;
+ case IOW_READ:
+ user_buffer = (__u8 __user *)arg;
+ io_res = usb_get_report(dev->udev,
+ dev->interface->cur_altsetting, 1, 0,
+ buffer, dev->report_size);
+ if (io_res < 0)
+ retval = io_res;
+ else {
+ io_res = copy_to_user(user_buffer, buffer, dev->report_size);
+ if (io_res)
+ retval = -EFAULT;
+ }
+ break;
+ case IOW_GETINFO:
+ {
+ /* Report available information for the device */
+ struct iowarrior_info info;
+ /* needed for power consumption */
+ struct usb_config_descriptor *cfg_descriptor = &dev->udev->actconfig->desc;
+
+ memset(&info, 0, sizeof(info));
+ /* directly from the descriptor */
+ info.vendor = le16_to_cpu(dev->udev->descriptor.idVendor);
+ info.product = dev->product_id;
+ info.revision = le16_to_cpu(dev->udev->descriptor.bcdDevice);
+
+ /* 0==UNKNOWN, 1==LOW(usb1.1) ,2=FULL(usb1.1), 3=HIGH(usb2.0) */
+ info.speed = dev->udev->speed;
+ info.if_num = dev->interface->cur_altsetting->desc.bInterfaceNumber;
+ info.report_size = dev->report_size;
+
+ /* serial number string has been read earlier 8 chars or empty string */
+ memcpy(info.serial, dev->chip_serial,
+ sizeof(dev->chip_serial));
+ if (cfg_descriptor == NULL) {
+ info.power = -1; /* no information available */
+ } else {
+ /* the MaxPower is stored in units of 2mA to make it fit into a byte-value */
+ info.power = cfg_descriptor->bMaxPower * 2;
+ }
+ io_res = copy_to_user((struct iowarrior_info __user *)arg, &info,
+ sizeof(struct iowarrior_info));
+ if (io_res)
+ retval = -EFAULT;
+ break;
+ }
+ default:
+ /* return that we did not understand this ioctl call */
+ retval = -ENOTTY;
+ break;
+ }
+error_out:
+ /* unlock the device */
+ mutex_unlock(&dev->mutex);
+ kfree(buffer);
+ return retval;
+}
+
+/*
+ * iowarrior_open
+ */
+static int iowarrior_open(struct inode *inode, struct file *file)
+{
+ struct iowarrior *dev = NULL;
+ struct usb_interface *interface;
+ int subminor;
+ int retval = 0;
+
+ subminor = iminor(inode);
+
+ interface = usb_find_interface(&iowarrior_driver, subminor);
+ if (!interface) {
+ pr_err("%s - error, can't find device for minor %d\n",
+ __func__, subminor);
+ return -ENODEV;
+ }
+
+ dev = usb_get_intfdata(interface);
+ if (!dev)
+ return -ENODEV;
+
+ mutex_lock(&dev->mutex);
+
+ /* Only one process can open each device, no sharing. */
+ if (dev->opened) {
+ retval = -EBUSY;
+ goto out;
+ }
+
+ /* setup interrupt handler for receiving values */
+ if ((retval = usb_submit_urb(dev->int_in_urb, GFP_KERNEL)) < 0) {
+ dev_err(&interface->dev, "Error %d while submitting URB\n", retval);
+ retval = -EFAULT;
+ goto out;
+ }
+ /* increment our usage count for the driver */
+ ++dev->opened;
+ /* save our object in the file's private structure */
+ file->private_data = dev;
+ retval = 0;
+
+out:
+ mutex_unlock(&dev->mutex);
+ return retval;
+}
+
+/*
+ * iowarrior_release
+ */
+static int iowarrior_release(struct inode *inode, struct file *file)
+{
+ struct iowarrior *dev;
+ int retval = 0;
+
+ dev = file->private_data;
+ if (!dev)
+ return -ENODEV;
+
+ dev_dbg(&dev->interface->dev, "minor %d\n", dev->minor);
+
+ /* lock our device */
+ mutex_lock(&dev->mutex);
+
+ if (dev->opened <= 0) {
+ retval = -ENODEV; /* close called more than once */
+ mutex_unlock(&dev->mutex);
+ } else {
+ dev->opened = 0; /* we're closing now */
+ retval = 0;
+ if (dev->present) {
+ /*
+ The device is still connected so we only shutdown
+ pending read-/write-ops.
+ */
+ usb_kill_urb(dev->int_in_urb);
+ wake_up_interruptible(&dev->read_wait);
+ wake_up_interruptible(&dev->write_wait);
+ mutex_unlock(&dev->mutex);
+ } else {
+ /* The device was unplugged, cleanup resources */
+ mutex_unlock(&dev->mutex);
+ iowarrior_delete(dev);
+ }
+ }
+ return retval;
+}
+
+static __poll_t iowarrior_poll(struct file *file, poll_table * wait)
+{
+ struct iowarrior *dev = file->private_data;
+ __poll_t mask = 0;
+
+ if (!dev->present)
+ return EPOLLERR | EPOLLHUP;
+
+ poll_wait(file, &dev->read_wait, wait);
+ poll_wait(file, &dev->write_wait, wait);
+
+ if (!dev->present)
+ return EPOLLERR | EPOLLHUP;
+
+ if (read_index(dev) != -1)
+ mask |= EPOLLIN | EPOLLRDNORM;
+
+ if (atomic_read(&dev->write_busy) < MAX_WRITES_IN_FLIGHT)
+ mask |= EPOLLOUT | EPOLLWRNORM;
+ return mask;
+}
+
+/*
+ * File operations needed when we register this driver.
+ * This assumes that this driver NEEDS file operations,
+ * of course, which means that the driver is expected
+ * to have a node in the /dev directory. If the USB
+ * device were for a network interface then the driver
+ * would use "struct net_driver" instead, and a serial
+ * device would use "struct tty_driver".
+ */
+static const struct file_operations iowarrior_fops = {
+ .owner = THIS_MODULE,
+ .write = iowarrior_write,
+ .read = iowarrior_read,
+ .unlocked_ioctl = iowarrior_ioctl,
+ .open = iowarrior_open,
+ .release = iowarrior_release,
+ .poll = iowarrior_poll,
+ .llseek = noop_llseek,
+};
+
+static char *iowarrior_devnode(struct device *dev, umode_t *mode)
+{
+ return kasprintf(GFP_KERNEL, "usb/%s", dev_name(dev));
+}
+
+/*
+ * usb class driver info in order to get a minor number from the usb core,
+ * and to have the device registered with devfs and the driver core
+ */
+static struct usb_class_driver iowarrior_class = {
+ .name = "iowarrior%d",
+ .devnode = iowarrior_devnode,
+ .fops = &iowarrior_fops,
+ .minor_base = IOWARRIOR_MINOR_BASE,
+};
+
+/*---------------------------------*/
+/* probe and disconnect functions */
+/*---------------------------------*/
+/*
+ * iowarrior_probe
+ *
+ * Called by the usb core when a new device is connected that it thinks
+ * this driver might be interested in.
+ */
+static int iowarrior_probe(struct usb_interface *interface,
+ const struct usb_device_id *id)
+{
+ struct usb_device *udev = interface_to_usbdev(interface);
+ struct iowarrior *dev = NULL;
+ struct usb_host_interface *iface_desc;
+ int retval = -ENOMEM;
+ int res;
+
+ /* allocate memory for our device state and initialize it */
+ dev = kzalloc(sizeof(struct iowarrior), GFP_KERNEL);
+ if (!dev)
+ return retval;
+
+ mutex_init(&dev->mutex);
+
+ atomic_set(&dev->intr_idx, 0);
+ atomic_set(&dev->read_idx, 0);
+ atomic_set(&dev->overflow_flag, 0);
+ init_waitqueue_head(&dev->read_wait);
+ atomic_set(&dev->write_busy, 0);
+ init_waitqueue_head(&dev->write_wait);
+
+ dev->udev = udev;
+ dev->interface = usb_get_intf(interface);
+
+ iface_desc = interface->cur_altsetting;
+ dev->product_id = le16_to_cpu(udev->descriptor.idProduct);
+
+ init_usb_anchor(&dev->submitted);
+
+ res = usb_find_last_int_in_endpoint(iface_desc, &dev->int_in_endpoint);
+ if (res) {
+ dev_err(&interface->dev, "no interrupt-in endpoint found\n");
+ retval = res;
+ goto error;
+ }
+
+ if ((dev->product_id == USB_DEVICE_ID_CODEMERCS_IOW56) ||
+ (dev->product_id == USB_DEVICE_ID_CODEMERCS_IOW56AM) ||
+ (dev->product_id == USB_DEVICE_ID_CODEMERCS_IOW28) ||
+ (dev->product_id == USB_DEVICE_ID_CODEMERCS_IOW28L) ||
+ (dev->product_id == USB_DEVICE_ID_CODEMERCS_IOW100)) {
+ res = usb_find_last_int_out_endpoint(iface_desc,
+ &dev->int_out_endpoint);
+ if (res) {
+ dev_err(&interface->dev, "no interrupt-out endpoint found\n");
+ retval = res;
+ goto error;
+ }
+ }
+
+ /* we have to check the report_size often, so remember it in the endianness suitable for our machine */
+ dev->report_size = usb_endpoint_maxp(dev->int_in_endpoint);
+
+ /*
+ * Some devices need the report size to be different than the
+ * endpoint size.
+ */
+ if (dev->interface->cur_altsetting->desc.bInterfaceNumber == 0) {
+ switch (dev->product_id) {
+ case USB_DEVICE_ID_CODEMERCS_IOW56:
+ case USB_DEVICE_ID_CODEMERCS_IOW56AM:
+ dev->report_size = 7;
+ break;
+
+ case USB_DEVICE_ID_CODEMERCS_IOW28:
+ case USB_DEVICE_ID_CODEMERCS_IOW28L:
+ dev->report_size = 4;
+ break;
+
+ case USB_DEVICE_ID_CODEMERCS_IOW100:
+ dev->report_size = 12;
+ break;
+ }
+ }
+
+ /* create the urb and buffer for reading */
+ dev->int_in_urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!dev->int_in_urb)
+ goto error;
+ dev->int_in_buffer = kmalloc(dev->report_size, GFP_KERNEL);
+ if (!dev->int_in_buffer)
+ goto error;
+ usb_fill_int_urb(dev->int_in_urb, dev->udev,
+ usb_rcvintpipe(dev->udev,
+ dev->int_in_endpoint->bEndpointAddress),
+ dev->int_in_buffer, dev->report_size,
+ iowarrior_callback, dev,
+ dev->int_in_endpoint->bInterval);
+ /* create an internal buffer for interrupt data from the device */
+ dev->read_queue =
+ kmalloc_array(dev->report_size + 1, MAX_INTERRUPT_BUFFER,
+ GFP_KERNEL);
+ if (!dev->read_queue)
+ goto error;
+ /* Get the serial-number of the chip */
+ memset(dev->chip_serial, 0x00, sizeof(dev->chip_serial));
+ usb_string(udev, udev->descriptor.iSerialNumber, dev->chip_serial,
+ sizeof(dev->chip_serial));
+ if (strlen(dev->chip_serial) != 8)
+ memset(dev->chip_serial, 0x00, sizeof(dev->chip_serial));
+
+ /* Set the idle timeout to 0, if this is interface 0 */
+ if (dev->interface->cur_altsetting->desc.bInterfaceNumber == 0) {
+ usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+ 0x0A,
+ USB_TYPE_CLASS | USB_RECIP_INTERFACE, 0,
+ 0, NULL, 0, USB_CTRL_SET_TIMEOUT);
+ }
+ /* allow device read and ioctl */
+ dev->present = 1;
+
+ /* we can register the device now, as it is ready */
+ usb_set_intfdata(interface, dev);
+
+ retval = usb_register_dev(interface, &iowarrior_class);
+ if (retval) {
+ /* something prevented us from registering this driver */
+ dev_err(&interface->dev, "Not able to get a minor for this device.\n");
+ goto error;
+ }
+
+ dev->minor = interface->minor;
+
+ /* let the user know what node this device is now attached to */
+ dev_info(&interface->dev, "IOWarrior product=0x%x, serial=%s interface=%d "
+ "now attached to iowarrior%d\n", dev->product_id, dev->chip_serial,
+ iface_desc->desc.bInterfaceNumber, dev->minor - IOWARRIOR_MINOR_BASE);
+ return retval;
+
+error:
+ iowarrior_delete(dev);
+ return retval;
+}
+
+/*
+ * iowarrior_disconnect
+ *
+ * Called by the usb core when the device is removed from the system.
+ */
+static void iowarrior_disconnect(struct usb_interface *interface)
+{
+ struct iowarrior *dev = usb_get_intfdata(interface);
+ int minor = dev->minor;
+
+ usb_deregister_dev(interface, &iowarrior_class);
+
+ mutex_lock(&dev->mutex);
+
+ /* prevent device read, write and ioctl */
+ dev->present = 0;
+
+ if (dev->opened) {
+ /* There is a process that holds a filedescriptor to the device ,
+ so we only shutdown read-/write-ops going on.
+ Deleting the device is postponed until close() was called.
+ */
+ usb_kill_urb(dev->int_in_urb);
+ usb_kill_anchored_urbs(&dev->submitted);
+ wake_up_interruptible(&dev->read_wait);
+ wake_up_interruptible(&dev->write_wait);
+ mutex_unlock(&dev->mutex);
+ } else {
+ /* no process is using the device, cleanup now */
+ mutex_unlock(&dev->mutex);
+ iowarrior_delete(dev);
+ }
+
+ dev_info(&interface->dev, "I/O-Warror #%d now disconnected\n",
+ minor - IOWARRIOR_MINOR_BASE);
+}
+
+/* usb specific object needed to register this driver with the usb subsystem */
+static struct usb_driver iowarrior_driver = {
+ .name = "iowarrior",
+ .probe = iowarrior_probe,
+ .disconnect = iowarrior_disconnect,
+ .id_table = iowarrior_ids,
+};
+
+module_usb_driver(iowarrior_driver);
diff --git a/drivers/usb/misc/isight_firmware.c b/drivers/usb/misc/isight_firmware.c
new file mode 100644
index 000000000..4d30095d6
--- /dev/null
+++ b/drivers/usb/misc/isight_firmware.c
@@ -0,0 +1,131 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Driver for loading USB isight firmware
+ *
+ * Copyright (C) 2008 Matthew Garrett <mjg@redhat.com>
+ *
+ * The USB isight cameras in recent Apples are roughly compatible with the USB
+ * video class specification, and can be driven by uvcvideo. However, they
+ * need firmware to be loaded beforehand. After firmware loading, the device
+ * detaches from the USB bus and reattaches with a new device ID. It can then
+ * be claimed by the uvc driver.
+ *
+ * The firmware is non-free and must be extracted by the user. Tools to do this
+ * are available at http://bersace03.free.fr/ift/
+ *
+ * The isight firmware loading was reverse engineered by Johannes Berg
+ * <johannes@sipsolutions.de>, and this driver is based on code by Ronald
+ * Bultje <rbultje@ronald.bitfreak.net>
+ */
+
+#include <linux/usb.h>
+#include <linux/firmware.h>
+#include <linux/errno.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+
+static const struct usb_device_id id_table[] = {
+ {USB_DEVICE(0x05ac, 0x8300)},
+ {},
+};
+
+MODULE_DEVICE_TABLE(usb, id_table);
+
+static int isight_firmware_load(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ struct usb_device *dev = interface_to_usbdev(intf);
+ int llen, len, req, ret = 0;
+ const struct firmware *firmware;
+ unsigned char *buf = kmalloc(50, GFP_KERNEL);
+ unsigned char data[4];
+ const u8 *ptr;
+
+ if (!buf)
+ return -ENOMEM;
+
+ if (request_firmware(&firmware, "isight.fw", &dev->dev) != 0) {
+ printk(KERN_ERR "Unable to load isight firmware\n");
+ ret = -ENODEV;
+ goto out;
+ }
+
+ ptr = firmware->data;
+
+ buf[0] = 0x01;
+ if (usb_control_msg
+ (dev, usb_sndctrlpipe(dev, 0), 0xa0, 0x40, 0xe600, 0, buf, 1,
+ 300) != 1) {
+ printk(KERN_ERR
+ "Failed to initialise isight firmware loader\n");
+ ret = -ENODEV;
+ goto out;
+ }
+
+ while (ptr+4 <= firmware->data+firmware->size) {
+ memcpy(data, ptr, 4);
+ len = (data[0] << 8 | data[1]);
+ req = (data[2] << 8 | data[3]);
+ ptr += 4;
+
+ if (len == 0x8001)
+ break; /* success */
+ else if (len == 0)
+ continue;
+
+ for (; len > 0; req += 50) {
+ llen = min(len, 50);
+ len -= llen;
+ if (ptr+llen > firmware->data+firmware->size) {
+ printk(KERN_ERR
+ "Malformed isight firmware");
+ ret = -ENODEV;
+ goto out;
+ }
+ memcpy(buf, ptr, llen);
+
+ ptr += llen;
+
+ if (usb_control_msg
+ (dev, usb_sndctrlpipe(dev, 0), 0xa0, 0x40, req, 0,
+ buf, llen, 300) != llen) {
+ printk(KERN_ERR
+ "Failed to load isight firmware\n");
+ ret = -ENODEV;
+ goto out;
+ }
+
+ }
+ }
+
+ buf[0] = 0x00;
+ if (usb_control_msg
+ (dev, usb_sndctrlpipe(dev, 0), 0xa0, 0x40, 0xe600, 0, buf, 1,
+ 300) != 1) {
+ printk(KERN_ERR "isight firmware loading completion failed\n");
+ ret = -ENODEV;
+ }
+
+out:
+ kfree(buf);
+ release_firmware(firmware);
+ return ret;
+}
+
+MODULE_FIRMWARE("isight.fw");
+
+static void isight_firmware_disconnect(struct usb_interface *intf)
+{
+}
+
+static struct usb_driver isight_firmware_driver = {
+ .name = "isight_firmware",
+ .probe = isight_firmware_load,
+ .disconnect = isight_firmware_disconnect,
+ .id_table = id_table,
+};
+
+module_usb_driver(isight_firmware_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Matthew Garrett <mjg@redhat.com>");
diff --git a/drivers/usb/misc/ldusb.c b/drivers/usb/misc/ldusb.c
new file mode 100644
index 000000000..7cbef74df
--- /dev/null
+++ b/drivers/usb/misc/ldusb.c
@@ -0,0 +1,797 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Generic USB driver for report based interrupt in/out devices
+ * like LD Didactic's USB devices. LD Didactic's USB devices are
+ * HID devices which do not use HID report definitons (they use
+ * raw interrupt in and our reports only for communication).
+ *
+ * This driver uses a ring buffer for time critical reading of
+ * interrupt in reports and provides read and write methods for
+ * raw interrupt reports (similar to the Windows HID driver).
+ * Devices based on the book USB COMPLETE by Jan Axelson may need
+ * such a compatibility to the Windows HID driver.
+ *
+ * Copyright (C) 2005 Michael Hund <mhund@ld-didactic.de>
+ *
+ * Derived from Lego USB Tower driver
+ * Copyright (C) 2003 David Glance <advidgsf@sourceforge.net>
+ * 2001-2004 Juergen Stuber <starblue@users.sourceforge.net>
+ */
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+
+#include <linux/uaccess.h>
+#include <linux/input.h>
+#include <linux/usb.h>
+#include <linux/poll.h>
+
+/* Define these values to match your devices */
+#define USB_VENDOR_ID_LD 0x0f11 /* USB Vendor ID of LD Didactic GmbH */
+#define USB_DEVICE_ID_LD_CASSY 0x1000 /* USB Product ID of CASSY-S modules with 8 bytes endpoint size */
+#define USB_DEVICE_ID_LD_CASSY2 0x1001 /* USB Product ID of CASSY-S modules with 64 bytes endpoint size */
+#define USB_DEVICE_ID_LD_POCKETCASSY 0x1010 /* USB Product ID of Pocket-CASSY */
+#define USB_DEVICE_ID_LD_POCKETCASSY2 0x1011 /* USB Product ID of Pocket-CASSY 2 (reserved) */
+#define USB_DEVICE_ID_LD_MOBILECASSY 0x1020 /* USB Product ID of Mobile-CASSY */
+#define USB_DEVICE_ID_LD_MOBILECASSY2 0x1021 /* USB Product ID of Mobile-CASSY 2 (reserved) */
+#define USB_DEVICE_ID_LD_MICROCASSYVOLTAGE 0x1031 /* USB Product ID of Micro-CASSY Voltage */
+#define USB_DEVICE_ID_LD_MICROCASSYCURRENT 0x1032 /* USB Product ID of Micro-CASSY Current */
+#define USB_DEVICE_ID_LD_MICROCASSYTIME 0x1033 /* USB Product ID of Micro-CASSY Time (reserved) */
+#define USB_DEVICE_ID_LD_MICROCASSYTEMPERATURE 0x1035 /* USB Product ID of Micro-CASSY Temperature */
+#define USB_DEVICE_ID_LD_MICROCASSYPH 0x1038 /* USB Product ID of Micro-CASSY pH */
+#define USB_DEVICE_ID_LD_POWERANALYSERCASSY 0x1040 /* USB Product ID of Power Analyser CASSY */
+#define USB_DEVICE_ID_LD_CONVERTERCONTROLLERCASSY 0x1042 /* USB Product ID of Converter Controller CASSY */
+#define USB_DEVICE_ID_LD_MACHINETESTCASSY 0x1043 /* USB Product ID of Machine Test CASSY */
+#define USB_DEVICE_ID_LD_JWM 0x1080 /* USB Product ID of Joule and Wattmeter */
+#define USB_DEVICE_ID_LD_DMMP 0x1081 /* USB Product ID of Digital Multimeter P (reserved) */
+#define USB_DEVICE_ID_LD_UMIP 0x1090 /* USB Product ID of UMI P */
+#define USB_DEVICE_ID_LD_UMIC 0x10A0 /* USB Product ID of UMI C */
+#define USB_DEVICE_ID_LD_UMIB 0x10B0 /* USB Product ID of UMI B */
+#define USB_DEVICE_ID_LD_XRAY 0x1100 /* USB Product ID of X-Ray Apparatus 55481 */
+#define USB_DEVICE_ID_LD_XRAY2 0x1101 /* USB Product ID of X-Ray Apparatus 554800 */
+#define USB_DEVICE_ID_LD_XRAYCT 0x1110 /* USB Product ID of X-Ray Apparatus CT 554821*/
+#define USB_DEVICE_ID_LD_VIDEOCOM 0x1200 /* USB Product ID of VideoCom */
+#define USB_DEVICE_ID_LD_MOTOR 0x1210 /* USB Product ID of Motor (reserved) */
+#define USB_DEVICE_ID_LD_COM3LAB 0x2000 /* USB Product ID of COM3LAB */
+#define USB_DEVICE_ID_LD_TELEPORT 0x2010 /* USB Product ID of Terminal Adapter */
+#define USB_DEVICE_ID_LD_NETWORKANALYSER 0x2020 /* USB Product ID of Network Analyser */
+#define USB_DEVICE_ID_LD_POWERCONTROL 0x2030 /* USB Product ID of Converter Control Unit */
+#define USB_DEVICE_ID_LD_MACHINETEST 0x2040 /* USB Product ID of Machine Test System */
+#define USB_DEVICE_ID_LD_MOSTANALYSER 0x2050 /* USB Product ID of MOST Protocol Analyser */
+#define USB_DEVICE_ID_LD_MOSTANALYSER2 0x2051 /* USB Product ID of MOST Protocol Analyser 2 */
+#define USB_DEVICE_ID_LD_ABSESP 0x2060 /* USB Product ID of ABS ESP */
+#define USB_DEVICE_ID_LD_AUTODATABUS 0x2070 /* USB Product ID of Automotive Data Buses */
+#define USB_DEVICE_ID_LD_MCT 0x2080 /* USB Product ID of Microcontroller technique */
+#define USB_DEVICE_ID_LD_HYBRID 0x2090 /* USB Product ID of Automotive Hybrid */
+#define USB_DEVICE_ID_LD_HEATCONTROL 0x20A0 /* USB Product ID of Heat control */
+
+#ifdef CONFIG_USB_DYNAMIC_MINORS
+#define USB_LD_MINOR_BASE 0
+#else
+#define USB_LD_MINOR_BASE 176
+#endif
+
+/* table of devices that work with this driver */
+static const struct usb_device_id ld_usb_table[] = {
+ { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_CASSY) },
+ { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_CASSY2) },
+ { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_POCKETCASSY) },
+ { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_POCKETCASSY2) },
+ { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_MOBILECASSY) },
+ { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_MOBILECASSY2) },
+ { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_MICROCASSYVOLTAGE) },
+ { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_MICROCASSYCURRENT) },
+ { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_MICROCASSYTIME) },
+ { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_MICROCASSYTEMPERATURE) },
+ { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_MICROCASSYPH) },
+ { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_POWERANALYSERCASSY) },
+ { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_CONVERTERCONTROLLERCASSY) },
+ { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_MACHINETESTCASSY) },
+ { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_JWM) },
+ { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_DMMP) },
+ { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_UMIP) },
+ { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_UMIC) },
+ { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_UMIB) },
+ { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_XRAY) },
+ { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_XRAY2) },
+ { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_VIDEOCOM) },
+ { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_MOTOR) },
+ { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_COM3LAB) },
+ { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_TELEPORT) },
+ { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_NETWORKANALYSER) },
+ { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_POWERCONTROL) },
+ { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_MACHINETEST) },
+ { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_MOSTANALYSER) },
+ { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_MOSTANALYSER2) },
+ { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_ABSESP) },
+ { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_AUTODATABUS) },
+ { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_MCT) },
+ { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_HYBRID) },
+ { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_HEATCONTROL) },
+ { } /* Terminating entry */
+};
+MODULE_DEVICE_TABLE(usb, ld_usb_table);
+MODULE_AUTHOR("Michael Hund <mhund@ld-didactic.de>");
+MODULE_DESCRIPTION("LD USB Driver");
+MODULE_LICENSE("GPL");
+
+/* All interrupt in transfers are collected in a ring buffer to
+ * avoid racing conditions and get better performance of the driver.
+ */
+static int ring_buffer_size = 128;
+module_param(ring_buffer_size, int, 0000);
+MODULE_PARM_DESC(ring_buffer_size, "Read ring buffer size in reports");
+
+/* The write_buffer can contain more than one interrupt out transfer.
+ */
+static int write_buffer_size = 10;
+module_param(write_buffer_size, int, 0000);
+MODULE_PARM_DESC(write_buffer_size, "Write buffer size in reports");
+
+/* As of kernel version 2.6.4 ehci-hcd uses an
+ * "only one interrupt transfer per frame" shortcut
+ * to simplify the scheduling of periodic transfers.
+ * This conflicts with our standard 1ms intervals for in and out URBs.
+ * We use default intervals of 2ms for in and 2ms for out transfers,
+ * which should be fast enough.
+ * Increase the interval to allow more devices that do interrupt transfers,
+ * or set to 1 to use the standard interval from the endpoint descriptors.
+ */
+static int min_interrupt_in_interval = 2;
+module_param(min_interrupt_in_interval, int, 0000);
+MODULE_PARM_DESC(min_interrupt_in_interval, "Minimum interrupt in interval in ms");
+
+static int min_interrupt_out_interval = 2;
+module_param(min_interrupt_out_interval, int, 0000);
+MODULE_PARM_DESC(min_interrupt_out_interval, "Minimum interrupt out interval in ms");
+
+/* Structure to hold all of our device specific stuff */
+struct ld_usb {
+ struct mutex mutex; /* locks this structure */
+ struct usb_interface *intf; /* save off the usb interface pointer */
+ unsigned long disconnected:1;
+
+ int open_count; /* number of times this port has been opened */
+
+ char *ring_buffer;
+ unsigned int ring_head;
+ unsigned int ring_tail;
+
+ wait_queue_head_t read_wait;
+ wait_queue_head_t write_wait;
+
+ char *interrupt_in_buffer;
+ struct usb_endpoint_descriptor *interrupt_in_endpoint;
+ struct urb *interrupt_in_urb;
+ int interrupt_in_interval;
+ size_t interrupt_in_endpoint_size;
+ int interrupt_in_running;
+ int interrupt_in_done;
+ int buffer_overflow;
+ spinlock_t rbsl;
+
+ char *interrupt_out_buffer;
+ struct usb_endpoint_descriptor *interrupt_out_endpoint;
+ struct urb *interrupt_out_urb;
+ int interrupt_out_interval;
+ size_t interrupt_out_endpoint_size;
+ int interrupt_out_busy;
+};
+
+static struct usb_driver ld_usb_driver;
+
+/*
+ * ld_usb_abort_transfers
+ * aborts transfers and frees associated data structures
+ */
+static void ld_usb_abort_transfers(struct ld_usb *dev)
+{
+ /* shutdown transfer */
+ if (dev->interrupt_in_running) {
+ dev->interrupt_in_running = 0;
+ usb_kill_urb(dev->interrupt_in_urb);
+ }
+ if (dev->interrupt_out_busy)
+ usb_kill_urb(dev->interrupt_out_urb);
+}
+
+/*
+ * ld_usb_delete
+ */
+static void ld_usb_delete(struct ld_usb *dev)
+{
+ /* free data structures */
+ usb_free_urb(dev->interrupt_in_urb);
+ usb_free_urb(dev->interrupt_out_urb);
+ kfree(dev->ring_buffer);
+ kfree(dev->interrupt_in_buffer);
+ kfree(dev->interrupt_out_buffer);
+ kfree(dev);
+}
+
+/*
+ * ld_usb_interrupt_in_callback
+ */
+static void ld_usb_interrupt_in_callback(struct urb *urb)
+{
+ struct ld_usb *dev = urb->context;
+ size_t *actual_buffer;
+ unsigned int next_ring_head;
+ int status = urb->status;
+ unsigned long flags;
+ int retval;
+
+ if (status) {
+ if (status == -ENOENT ||
+ status == -ECONNRESET ||
+ status == -ESHUTDOWN) {
+ goto exit;
+ } else {
+ dev_dbg(&dev->intf->dev,
+ "%s: nonzero status received: %d\n", __func__,
+ status);
+ spin_lock_irqsave(&dev->rbsl, flags);
+ goto resubmit; /* maybe we can recover */
+ }
+ }
+
+ spin_lock_irqsave(&dev->rbsl, flags);
+ if (urb->actual_length > 0) {
+ next_ring_head = (dev->ring_head+1) % ring_buffer_size;
+ if (next_ring_head != dev->ring_tail) {
+ actual_buffer = (size_t *)(dev->ring_buffer + dev->ring_head * (sizeof(size_t)+dev->interrupt_in_endpoint_size));
+ /* actual_buffer gets urb->actual_length + interrupt_in_buffer */
+ *actual_buffer = urb->actual_length;
+ memcpy(actual_buffer+1, dev->interrupt_in_buffer, urb->actual_length);
+ dev->ring_head = next_ring_head;
+ dev_dbg(&dev->intf->dev, "%s: received %d bytes\n",
+ __func__, urb->actual_length);
+ } else {
+ dev_warn(&dev->intf->dev,
+ "Ring buffer overflow, %d bytes dropped\n",
+ urb->actual_length);
+ dev->buffer_overflow = 1;
+ }
+ }
+
+resubmit:
+ /* resubmit if we're still running */
+ if (dev->interrupt_in_running && !dev->buffer_overflow) {
+ retval = usb_submit_urb(dev->interrupt_in_urb, GFP_ATOMIC);
+ if (retval) {
+ dev_err(&dev->intf->dev,
+ "usb_submit_urb failed (%d)\n", retval);
+ dev->buffer_overflow = 1;
+ }
+ }
+ spin_unlock_irqrestore(&dev->rbsl, flags);
+exit:
+ dev->interrupt_in_done = 1;
+ wake_up_interruptible(&dev->read_wait);
+}
+
+/*
+ * ld_usb_interrupt_out_callback
+ */
+static void ld_usb_interrupt_out_callback(struct urb *urb)
+{
+ struct ld_usb *dev = urb->context;
+ int status = urb->status;
+
+ /* sync/async unlink faults aren't errors */
+ if (status && !(status == -ENOENT ||
+ status == -ECONNRESET ||
+ status == -ESHUTDOWN))
+ dev_dbg(&dev->intf->dev,
+ "%s - nonzero write interrupt status received: %d\n",
+ __func__, status);
+
+ dev->interrupt_out_busy = 0;
+ wake_up_interruptible(&dev->write_wait);
+}
+
+/*
+ * ld_usb_open
+ */
+static int ld_usb_open(struct inode *inode, struct file *file)
+{
+ struct ld_usb *dev;
+ int subminor;
+ int retval;
+ struct usb_interface *interface;
+
+ stream_open(inode, file);
+ subminor = iminor(inode);
+
+ interface = usb_find_interface(&ld_usb_driver, subminor);
+
+ if (!interface) {
+ printk(KERN_ERR "%s - error, can't find device for minor %d\n",
+ __func__, subminor);
+ return -ENODEV;
+ }
+
+ dev = usb_get_intfdata(interface);
+
+ if (!dev)
+ return -ENODEV;
+
+ /* lock this device */
+ if (mutex_lock_interruptible(&dev->mutex))
+ return -ERESTARTSYS;
+
+ /* allow opening only once */
+ if (dev->open_count) {
+ retval = -EBUSY;
+ goto unlock_exit;
+ }
+ dev->open_count = 1;
+
+ /* initialize in direction */
+ dev->ring_head = 0;
+ dev->ring_tail = 0;
+ dev->buffer_overflow = 0;
+ usb_fill_int_urb(dev->interrupt_in_urb,
+ interface_to_usbdev(interface),
+ usb_rcvintpipe(interface_to_usbdev(interface),
+ dev->interrupt_in_endpoint->bEndpointAddress),
+ dev->interrupt_in_buffer,
+ dev->interrupt_in_endpoint_size,
+ ld_usb_interrupt_in_callback,
+ dev,
+ dev->interrupt_in_interval);
+
+ dev->interrupt_in_running = 1;
+ dev->interrupt_in_done = 0;
+
+ retval = usb_submit_urb(dev->interrupt_in_urb, GFP_KERNEL);
+ if (retval) {
+ dev_err(&interface->dev, "Couldn't submit interrupt_in_urb %d\n", retval);
+ dev->interrupt_in_running = 0;
+ dev->open_count = 0;
+ goto unlock_exit;
+ }
+
+ /* save device in the file's private structure */
+ file->private_data = dev;
+
+unlock_exit:
+ mutex_unlock(&dev->mutex);
+
+ return retval;
+}
+
+/*
+ * ld_usb_release
+ */
+static int ld_usb_release(struct inode *inode, struct file *file)
+{
+ struct ld_usb *dev;
+ int retval = 0;
+
+ dev = file->private_data;
+
+ if (dev == NULL) {
+ retval = -ENODEV;
+ goto exit;
+ }
+
+ mutex_lock(&dev->mutex);
+
+ if (dev->open_count != 1) {
+ retval = -ENODEV;
+ goto unlock_exit;
+ }
+ if (dev->disconnected) {
+ /* the device was unplugged before the file was released */
+ mutex_unlock(&dev->mutex);
+ /* unlock here as ld_usb_delete frees dev */
+ ld_usb_delete(dev);
+ goto exit;
+ }
+
+ /* wait until write transfer is finished */
+ if (dev->interrupt_out_busy)
+ wait_event_interruptible_timeout(dev->write_wait, !dev->interrupt_out_busy, 2 * HZ);
+ ld_usb_abort_transfers(dev);
+ dev->open_count = 0;
+
+unlock_exit:
+ mutex_unlock(&dev->mutex);
+
+exit:
+ return retval;
+}
+
+/*
+ * ld_usb_poll
+ */
+static __poll_t ld_usb_poll(struct file *file, poll_table *wait)
+{
+ struct ld_usb *dev;
+ __poll_t mask = 0;
+
+ dev = file->private_data;
+
+ if (dev->disconnected)
+ return EPOLLERR | EPOLLHUP;
+
+ poll_wait(file, &dev->read_wait, wait);
+ poll_wait(file, &dev->write_wait, wait);
+
+ if (dev->ring_head != dev->ring_tail)
+ mask |= EPOLLIN | EPOLLRDNORM;
+ if (!dev->interrupt_out_busy)
+ mask |= EPOLLOUT | EPOLLWRNORM;
+
+ return mask;
+}
+
+/*
+ * ld_usb_read
+ */
+static ssize_t ld_usb_read(struct file *file, char __user *buffer, size_t count,
+ loff_t *ppos)
+{
+ struct ld_usb *dev;
+ size_t *actual_buffer;
+ size_t bytes_to_read;
+ int retval = 0;
+ int rv;
+
+ dev = file->private_data;
+
+ /* verify that we actually have some data to read */
+ if (count == 0)
+ goto exit;
+
+ /* lock this object */
+ if (mutex_lock_interruptible(&dev->mutex)) {
+ retval = -ERESTARTSYS;
+ goto exit;
+ }
+
+ /* verify that the device wasn't unplugged */
+ if (dev->disconnected) {
+ retval = -ENODEV;
+ printk(KERN_ERR "ldusb: No device or device unplugged %d\n", retval);
+ goto unlock_exit;
+ }
+
+ /* wait for data */
+ spin_lock_irq(&dev->rbsl);
+ while (dev->ring_head == dev->ring_tail) {
+ dev->interrupt_in_done = 0;
+ spin_unlock_irq(&dev->rbsl);
+ if (file->f_flags & O_NONBLOCK) {
+ retval = -EAGAIN;
+ goto unlock_exit;
+ }
+ retval = wait_event_interruptible(dev->read_wait, dev->interrupt_in_done);
+ if (retval < 0)
+ goto unlock_exit;
+
+ spin_lock_irq(&dev->rbsl);
+ }
+ spin_unlock_irq(&dev->rbsl);
+
+ /* actual_buffer contains actual_length + interrupt_in_buffer */
+ actual_buffer = (size_t *)(dev->ring_buffer + dev->ring_tail * (sizeof(size_t)+dev->interrupt_in_endpoint_size));
+ if (*actual_buffer > dev->interrupt_in_endpoint_size) {
+ retval = -EIO;
+ goto unlock_exit;
+ }
+ bytes_to_read = min(count, *actual_buffer);
+ if (bytes_to_read < *actual_buffer)
+ dev_warn(&dev->intf->dev, "Read buffer overflow, %zu bytes dropped\n",
+ *actual_buffer-bytes_to_read);
+
+ /* copy one interrupt_in_buffer from ring_buffer into userspace */
+ if (copy_to_user(buffer, actual_buffer+1, bytes_to_read)) {
+ retval = -EFAULT;
+ goto unlock_exit;
+ }
+ retval = bytes_to_read;
+
+ spin_lock_irq(&dev->rbsl);
+ dev->ring_tail = (dev->ring_tail + 1) % ring_buffer_size;
+
+ if (dev->buffer_overflow) {
+ dev->buffer_overflow = 0;
+ spin_unlock_irq(&dev->rbsl);
+ rv = usb_submit_urb(dev->interrupt_in_urb, GFP_KERNEL);
+ if (rv < 0)
+ dev->buffer_overflow = 1;
+ } else {
+ spin_unlock_irq(&dev->rbsl);
+ }
+
+unlock_exit:
+ /* unlock the device */
+ mutex_unlock(&dev->mutex);
+
+exit:
+ return retval;
+}
+
+/*
+ * ld_usb_write
+ */
+static ssize_t ld_usb_write(struct file *file, const char __user *buffer,
+ size_t count, loff_t *ppos)
+{
+ struct ld_usb *dev;
+ size_t bytes_to_write;
+ int retval = 0;
+
+ dev = file->private_data;
+
+ /* verify that we actually have some data to write */
+ if (count == 0)
+ goto exit;
+
+ /* lock this object */
+ if (mutex_lock_interruptible(&dev->mutex)) {
+ retval = -ERESTARTSYS;
+ goto exit;
+ }
+
+ /* verify that the device wasn't unplugged */
+ if (dev->disconnected) {
+ retval = -ENODEV;
+ printk(KERN_ERR "ldusb: No device or device unplugged %d\n", retval);
+ goto unlock_exit;
+ }
+
+ /* wait until previous transfer is finished */
+ if (dev->interrupt_out_busy) {
+ if (file->f_flags & O_NONBLOCK) {
+ retval = -EAGAIN;
+ goto unlock_exit;
+ }
+ retval = wait_event_interruptible(dev->write_wait, !dev->interrupt_out_busy);
+ if (retval < 0) {
+ goto unlock_exit;
+ }
+ }
+
+ /* write the data into interrupt_out_buffer from userspace */
+ bytes_to_write = min(count, write_buffer_size*dev->interrupt_out_endpoint_size);
+ if (bytes_to_write < count)
+ dev_warn(&dev->intf->dev, "Write buffer overflow, %zu bytes dropped\n",
+ count - bytes_to_write);
+ dev_dbg(&dev->intf->dev, "%s: count = %zu, bytes_to_write = %zu\n",
+ __func__, count, bytes_to_write);
+
+ if (copy_from_user(dev->interrupt_out_buffer, buffer, bytes_to_write)) {
+ retval = -EFAULT;
+ goto unlock_exit;
+ }
+
+ if (dev->interrupt_out_endpoint == NULL) {
+ /* try HID_REQ_SET_REPORT=9 on control_endpoint instead of interrupt_out_endpoint */
+ retval = usb_control_msg(interface_to_usbdev(dev->intf),
+ usb_sndctrlpipe(interface_to_usbdev(dev->intf), 0),
+ 9,
+ USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT,
+ 1 << 8, 0,
+ dev->interrupt_out_buffer,
+ bytes_to_write,
+ USB_CTRL_SET_TIMEOUT);
+ if (retval < 0)
+ dev_err(&dev->intf->dev,
+ "Couldn't submit HID_REQ_SET_REPORT %d\n",
+ retval);
+ goto unlock_exit;
+ }
+
+ /* send off the urb */
+ usb_fill_int_urb(dev->interrupt_out_urb,
+ interface_to_usbdev(dev->intf),
+ usb_sndintpipe(interface_to_usbdev(dev->intf),
+ dev->interrupt_out_endpoint->bEndpointAddress),
+ dev->interrupt_out_buffer,
+ bytes_to_write,
+ ld_usb_interrupt_out_callback,
+ dev,
+ dev->interrupt_out_interval);
+
+ dev->interrupt_out_busy = 1;
+ wmb();
+
+ retval = usb_submit_urb(dev->interrupt_out_urb, GFP_KERNEL);
+ if (retval) {
+ dev->interrupt_out_busy = 0;
+ dev_err(&dev->intf->dev,
+ "Couldn't submit interrupt_out_urb %d\n", retval);
+ goto unlock_exit;
+ }
+ retval = bytes_to_write;
+
+unlock_exit:
+ /* unlock the device */
+ mutex_unlock(&dev->mutex);
+
+exit:
+ return retval;
+}
+
+/* file operations needed when we register this driver */
+static const struct file_operations ld_usb_fops = {
+ .owner = THIS_MODULE,
+ .read = ld_usb_read,
+ .write = ld_usb_write,
+ .open = ld_usb_open,
+ .release = ld_usb_release,
+ .poll = ld_usb_poll,
+ .llseek = no_llseek,
+};
+
+/*
+ * usb class driver info in order to get a minor number from the usb core,
+ * and to have the device registered with the driver core
+ */
+static struct usb_class_driver ld_usb_class = {
+ .name = "ldusb%d",
+ .fops = &ld_usb_fops,
+ .minor_base = USB_LD_MINOR_BASE,
+};
+
+/*
+ * ld_usb_probe
+ *
+ * Called by the usb core when a new device is connected that it thinks
+ * this driver might be interested in.
+ */
+static int ld_usb_probe(struct usb_interface *intf, const struct usb_device_id *id)
+{
+ struct usb_device *udev = interface_to_usbdev(intf);
+ struct ld_usb *dev = NULL;
+ struct usb_host_interface *iface_desc;
+ char *buffer;
+ int retval = -ENOMEM;
+ int res;
+
+ /* allocate memory for our device state and initialize it */
+
+ dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+ if (!dev)
+ goto exit;
+ mutex_init(&dev->mutex);
+ spin_lock_init(&dev->rbsl);
+ dev->intf = intf;
+ init_waitqueue_head(&dev->read_wait);
+ init_waitqueue_head(&dev->write_wait);
+
+ /* workaround for early firmware versions on fast computers */
+ if ((le16_to_cpu(udev->descriptor.idVendor) == USB_VENDOR_ID_LD) &&
+ ((le16_to_cpu(udev->descriptor.idProduct) == USB_DEVICE_ID_LD_CASSY) ||
+ (le16_to_cpu(udev->descriptor.idProduct) == USB_DEVICE_ID_LD_COM3LAB)) &&
+ (le16_to_cpu(udev->descriptor.bcdDevice) <= 0x103)) {
+ buffer = kmalloc(256, GFP_KERNEL);
+ if (!buffer)
+ goto error;
+ /* usb_string makes SETUP+STALL to leave always ControlReadLoop */
+ usb_string(udev, 255, buffer, 256);
+ kfree(buffer);
+ }
+
+ iface_desc = intf->cur_altsetting;
+
+ res = usb_find_last_int_in_endpoint(iface_desc,
+ &dev->interrupt_in_endpoint);
+ if (res) {
+ dev_err(&intf->dev, "Interrupt in endpoint not found\n");
+ retval = res;
+ goto error;
+ }
+
+ res = usb_find_last_int_out_endpoint(iface_desc,
+ &dev->interrupt_out_endpoint);
+ if (res)
+ dev_warn(&intf->dev, "Interrupt out endpoint not found (using control endpoint instead)\n");
+
+ dev->interrupt_in_endpoint_size = usb_endpoint_maxp(dev->interrupt_in_endpoint);
+ dev->ring_buffer = kcalloc(ring_buffer_size,
+ sizeof(size_t) + dev->interrupt_in_endpoint_size,
+ GFP_KERNEL);
+ if (!dev->ring_buffer)
+ goto error;
+ dev->interrupt_in_buffer = kmalloc(dev->interrupt_in_endpoint_size, GFP_KERNEL);
+ if (!dev->interrupt_in_buffer)
+ goto error;
+ dev->interrupt_in_urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!dev->interrupt_in_urb)
+ goto error;
+ dev->interrupt_out_endpoint_size = dev->interrupt_out_endpoint ? usb_endpoint_maxp(dev->interrupt_out_endpoint) :
+ udev->descriptor.bMaxPacketSize0;
+ dev->interrupt_out_buffer =
+ kmalloc_array(write_buffer_size,
+ dev->interrupt_out_endpoint_size, GFP_KERNEL);
+ if (!dev->interrupt_out_buffer)
+ goto error;
+ dev->interrupt_out_urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!dev->interrupt_out_urb)
+ goto error;
+ dev->interrupt_in_interval = max_t(int, min_interrupt_in_interval,
+ dev->interrupt_in_endpoint->bInterval);
+ if (dev->interrupt_out_endpoint)
+ dev->interrupt_out_interval = max_t(int, min_interrupt_out_interval,
+ dev->interrupt_out_endpoint->bInterval);
+
+ /* we can register the device now, as it is ready */
+ usb_set_intfdata(intf, dev);
+
+ retval = usb_register_dev(intf, &ld_usb_class);
+ if (retval) {
+ /* something prevented us from registering this driver */
+ dev_err(&intf->dev, "Not able to get a minor for this device.\n");
+ usb_set_intfdata(intf, NULL);
+ goto error;
+ }
+
+ /* let the user know what node this device is now attached to */
+ dev_info(&intf->dev, "LD USB Device #%d now attached to major %d minor %d\n",
+ (intf->minor - USB_LD_MINOR_BASE), USB_MAJOR, intf->minor);
+
+exit:
+ return retval;
+
+error:
+ ld_usb_delete(dev);
+
+ return retval;
+}
+
+/*
+ * ld_usb_disconnect
+ *
+ * Called by the usb core when the device is removed from the system.
+ */
+static void ld_usb_disconnect(struct usb_interface *intf)
+{
+ struct ld_usb *dev;
+ int minor;
+
+ dev = usb_get_intfdata(intf);
+ usb_set_intfdata(intf, NULL);
+
+ minor = intf->minor;
+
+ /* give back our minor */
+ usb_deregister_dev(intf, &ld_usb_class);
+
+ usb_poison_urb(dev->interrupt_in_urb);
+ usb_poison_urb(dev->interrupt_out_urb);
+
+ mutex_lock(&dev->mutex);
+
+ /* if the device is not opened, then we clean up right now */
+ if (!dev->open_count) {
+ mutex_unlock(&dev->mutex);
+ ld_usb_delete(dev);
+ } else {
+ dev->disconnected = 1;
+ /* wake up pollers */
+ wake_up_interruptible_all(&dev->read_wait);
+ wake_up_interruptible_all(&dev->write_wait);
+ mutex_unlock(&dev->mutex);
+ }
+
+ dev_info(&intf->dev, "LD USB Device #%d now disconnected\n",
+ (minor - USB_LD_MINOR_BASE));
+}
+
+/* usb specific object needed to register this driver with the usb subsystem */
+static struct usb_driver ld_usb_driver = {
+ .name = "ldusb",
+ .probe = ld_usb_probe,
+ .disconnect = ld_usb_disconnect,
+ .id_table = ld_usb_table,
+};
+
+module_usb_driver(ld_usb_driver);
+
diff --git a/drivers/usb/misc/legousbtower.c b/drivers/usb/misc/legousbtower.c
new file mode 100644
index 000000000..1c9e09138
--- /dev/null
+++ b/drivers/usb/misc/legousbtower.c
@@ -0,0 +1,879 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * LEGO USB Tower driver
+ *
+ * Copyright (C) 2003 David Glance <davidgsf@sourceforge.net>
+ * 2001-2004 Juergen Stuber <starblue@users.sourceforge.net>
+ *
+ * derived from USB Skeleton driver - 0.5
+ * Copyright (C) 2001 Greg Kroah-Hartman (greg@kroah.com)
+ *
+ * History:
+ *
+ * 2001-10-13 - 0.1 js
+ * - first version
+ * 2001-11-03 - 0.2 js
+ * - simplified buffering, one-shot URBs for writing
+ * 2001-11-10 - 0.3 js
+ * - removed IOCTL (setting power/mode is more complicated, postponed)
+ * 2001-11-28 - 0.4 js
+ * - added vendor commands for mode of operation and power level in open
+ * 2001-12-04 - 0.5 js
+ * - set IR mode by default (by oversight 0.4 set VLL mode)
+ * 2002-01-11 - 0.5? pcchan
+ * - make read buffer reusable and work around bytes_to_write issue between
+ * uhci and legusbtower
+ * 2002-09-23 - 0.52 david (david@csse.uwa.edu.au)
+ * - imported into lejos project
+ * - changed wake_up to wake_up_interruptible
+ * - changed to use lego0 rather than tower0
+ * - changed dbg() to use __func__ rather than deprecated __func__
+ * 2003-01-12 - 0.53 david (david@csse.uwa.edu.au)
+ * - changed read and write to write everything or
+ * timeout (from a patch by Chris Riesen and Brett Thaeler driver)
+ * - added ioctl functionality to set timeouts
+ * 2003-07-18 - 0.54 davidgsf (david@csse.uwa.edu.au)
+ * - initial import into LegoUSB project
+ * - merge of existing LegoUSB.c driver
+ * 2003-07-18 - 0.56 davidgsf (david@csse.uwa.edu.au)
+ * - port to 2.6 style driver
+ * 2004-02-29 - 0.6 Juergen Stuber <starblue@users.sourceforge.net>
+ * - fix locking
+ * - unlink read URBs which are no longer needed
+ * - allow increased buffer size, eliminates need for timeout on write
+ * - have read URB running continuously
+ * - added poll
+ * - forbid seeking
+ * - added nonblocking I/O
+ * - changed back __func__ to __func__
+ * - read and log tower firmware version
+ * - reset tower on probe, avoids failure of first write
+ * 2004-03-09 - 0.7 Juergen Stuber <starblue@users.sourceforge.net>
+ * - timeout read now only after inactivity, shorten default accordingly
+ * 2004-03-11 - 0.8 Juergen Stuber <starblue@users.sourceforge.net>
+ * - log major, minor instead of possibly confusing device filename
+ * - whitespace cleanup
+ * 2004-03-12 - 0.9 Juergen Stuber <starblue@users.sourceforge.net>
+ * - normalize whitespace in debug messages
+ * - take care about endianness in control message responses
+ * 2004-03-13 - 0.91 Juergen Stuber <starblue@users.sourceforge.net>
+ * - make default intervals longer to accommodate current EHCI driver
+ * 2004-03-19 - 0.92 Juergen Stuber <starblue@users.sourceforge.net>
+ * - replaced atomic_t by memory barriers
+ * 2004-04-21 - 0.93 Juergen Stuber <starblue@users.sourceforge.net>
+ * - wait for completion of write urb in release (needed for remotecontrol)
+ * - corrected poll for write direction (missing negation)
+ * 2004-04-22 - 0.94 Juergen Stuber <starblue@users.sourceforge.net>
+ * - make device locking interruptible
+ * 2004-04-30 - 0.95 Juergen Stuber <starblue@users.sourceforge.net>
+ * - check for valid udev on resubmitting and unlinking urbs
+ * 2004-08-03 - 0.96 Juergen Stuber <starblue@users.sourceforge.net>
+ * - move reset into open to clean out spurious data
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/completion.h>
+#include <linux/mutex.h>
+#include <linux/uaccess.h>
+#include <linux/usb.h>
+#include <linux/poll.h>
+
+
+#define DRIVER_AUTHOR "Juergen Stuber <starblue@sourceforge.net>"
+#define DRIVER_DESC "LEGO USB Tower Driver"
+
+
+/* The defaults are chosen to work with the latest versions of leJOS and NQC.
+ */
+
+/* Some legacy software likes to receive packets in one piece.
+ * In this case read_buffer_size should exceed the maximal packet length
+ * (417 for datalog uploads), and packet_timeout should be set.
+ */
+static int read_buffer_size = 480;
+module_param(read_buffer_size, int, 0);
+MODULE_PARM_DESC(read_buffer_size, "Read buffer size");
+
+/* Some legacy software likes to send packets in one piece.
+ * In this case write_buffer_size should exceed the maximal packet length
+ * (417 for firmware and program downloads).
+ * A problem with long writes is that the following read may time out
+ * if the software is not prepared to wait long enough.
+ */
+static int write_buffer_size = 480;
+module_param(write_buffer_size, int, 0);
+MODULE_PARM_DESC(write_buffer_size, "Write buffer size");
+
+/* Some legacy software expects reads to contain whole LASM packets.
+ * To achieve this, characters which arrive before a packet timeout
+ * occurs will be returned in a single read operation.
+ * A problem with long reads is that the software may time out
+ * if it is not prepared to wait long enough.
+ * The packet timeout should be greater than the time between the
+ * reception of subsequent characters, which should arrive about
+ * every 5ms for the standard 2400 baud.
+ * Set it to 0 to disable.
+ */
+static int packet_timeout = 50;
+module_param(packet_timeout, int, 0);
+MODULE_PARM_DESC(packet_timeout, "Packet timeout in ms");
+
+/* Some legacy software expects blocking reads to time out.
+ * Timeout occurs after the specified time of read and write inactivity.
+ * Set it to 0 to disable.
+ */
+static int read_timeout = 200;
+module_param(read_timeout, int, 0);
+MODULE_PARM_DESC(read_timeout, "Read timeout in ms");
+
+/* As of kernel version 2.6.4 ehci-hcd uses an
+ * "only one interrupt transfer per frame" shortcut
+ * to simplify the scheduling of periodic transfers.
+ * This conflicts with our standard 1ms intervals for in and out URBs.
+ * We use default intervals of 2ms for in and 8ms for out transfers,
+ * which is fast enough for 2400 baud and allows a small additional load.
+ * Increase the interval to allow more devices that do interrupt transfers,
+ * or set to 0 to use the standard interval from the endpoint descriptors.
+ */
+static int interrupt_in_interval = 2;
+module_param(interrupt_in_interval, int, 0);
+MODULE_PARM_DESC(interrupt_in_interval, "Interrupt in interval in ms");
+
+static int interrupt_out_interval = 8;
+module_param(interrupt_out_interval, int, 0);
+MODULE_PARM_DESC(interrupt_out_interval, "Interrupt out interval in ms");
+
+/* Define these values to match your device */
+#define LEGO_USB_TOWER_VENDOR_ID 0x0694
+#define LEGO_USB_TOWER_PRODUCT_ID 0x0001
+
+/* Vendor requests */
+#define LEGO_USB_TOWER_REQUEST_RESET 0x04
+#define LEGO_USB_TOWER_REQUEST_GET_VERSION 0xFD
+
+struct tower_reset_reply {
+ __le16 size;
+ __u8 err_code;
+ __u8 spare;
+};
+
+struct tower_get_version_reply {
+ __le16 size;
+ __u8 err_code;
+ __u8 spare;
+ __u8 major;
+ __u8 minor;
+ __le16 build_no;
+};
+
+
+/* table of devices that work with this driver */
+static const struct usb_device_id tower_table[] = {
+ { USB_DEVICE(LEGO_USB_TOWER_VENDOR_ID, LEGO_USB_TOWER_PRODUCT_ID) },
+ { } /* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE(usb, tower_table);
+
+#define LEGO_USB_TOWER_MINOR_BASE 160
+
+
+/* Structure to hold all of our device specific stuff */
+struct lego_usb_tower {
+ struct mutex lock; /* locks this structure */
+ struct usb_device *udev; /* save off the usb device pointer */
+ unsigned char minor; /* the starting minor number for this device */
+
+ int open_count; /* number of times this port has been opened */
+ unsigned long disconnected:1;
+
+ char *read_buffer;
+ size_t read_buffer_length; /* this much came in */
+ size_t read_packet_length; /* this much will be returned on read */
+ spinlock_t read_buffer_lock;
+ int packet_timeout_jiffies;
+ unsigned long read_last_arrival;
+
+ wait_queue_head_t read_wait;
+ wait_queue_head_t write_wait;
+
+ char *interrupt_in_buffer;
+ struct usb_endpoint_descriptor *interrupt_in_endpoint;
+ struct urb *interrupt_in_urb;
+ int interrupt_in_interval;
+ int interrupt_in_done;
+
+ char *interrupt_out_buffer;
+ struct usb_endpoint_descriptor *interrupt_out_endpoint;
+ struct urb *interrupt_out_urb;
+ int interrupt_out_interval;
+ int interrupt_out_busy;
+
+};
+
+
+/* local function prototypes */
+static ssize_t tower_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos);
+static ssize_t tower_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos);
+static inline void tower_delete(struct lego_usb_tower *dev);
+static int tower_open(struct inode *inode, struct file *file);
+static int tower_release(struct inode *inode, struct file *file);
+static __poll_t tower_poll(struct file *file, poll_table *wait);
+static loff_t tower_llseek(struct file *file, loff_t off, int whence);
+
+static void tower_check_for_read_packet(struct lego_usb_tower *dev);
+static void tower_interrupt_in_callback(struct urb *urb);
+static void tower_interrupt_out_callback(struct urb *urb);
+
+static int tower_probe(struct usb_interface *interface, const struct usb_device_id *id);
+static void tower_disconnect(struct usb_interface *interface);
+
+
+/* file operations needed when we register this driver */
+static const struct file_operations tower_fops = {
+ .owner = THIS_MODULE,
+ .read = tower_read,
+ .write = tower_write,
+ .open = tower_open,
+ .release = tower_release,
+ .poll = tower_poll,
+ .llseek = tower_llseek,
+};
+
+static char *legousbtower_devnode(struct device *dev, umode_t *mode)
+{
+ return kasprintf(GFP_KERNEL, "usb/%s", dev_name(dev));
+}
+
+/*
+ * usb class driver info in order to get a minor number from the usb core,
+ * and to have the device registered with the driver core
+ */
+static struct usb_class_driver tower_class = {
+ .name = "legousbtower%d",
+ .devnode = legousbtower_devnode,
+ .fops = &tower_fops,
+ .minor_base = LEGO_USB_TOWER_MINOR_BASE,
+};
+
+
+/* usb specific object needed to register this driver with the usb subsystem */
+static struct usb_driver tower_driver = {
+ .name = "legousbtower",
+ .probe = tower_probe,
+ .disconnect = tower_disconnect,
+ .id_table = tower_table,
+};
+
+
+/*
+ * lego_usb_tower_debug_data
+ */
+static inline void lego_usb_tower_debug_data(struct device *dev,
+ const char *function, int size,
+ const unsigned char *data)
+{
+ dev_dbg(dev, "%s - length = %d, data = %*ph\n",
+ function, size, size, data);
+}
+
+
+/*
+ * tower_delete
+ */
+static inline void tower_delete(struct lego_usb_tower *dev)
+{
+ /* free data structures */
+ usb_free_urb(dev->interrupt_in_urb);
+ usb_free_urb(dev->interrupt_out_urb);
+ kfree(dev->read_buffer);
+ kfree(dev->interrupt_in_buffer);
+ kfree(dev->interrupt_out_buffer);
+ usb_put_dev(dev->udev);
+ kfree(dev);
+}
+
+
+/*
+ * tower_open
+ */
+static int tower_open(struct inode *inode, struct file *file)
+{
+ struct lego_usb_tower *dev = NULL;
+ int subminor;
+ int retval = 0;
+ struct usb_interface *interface;
+ struct tower_reset_reply reset_reply;
+ int result;
+
+ nonseekable_open(inode, file);
+ subminor = iminor(inode);
+
+ interface = usb_find_interface(&tower_driver, subminor);
+ if (!interface) {
+ pr_err("error, can't find device for minor %d\n", subminor);
+ retval = -ENODEV;
+ goto exit;
+ }
+
+ dev = usb_get_intfdata(interface);
+ if (!dev) {
+ retval = -ENODEV;
+ goto exit;
+ }
+
+ /* lock this device */
+ if (mutex_lock_interruptible(&dev->lock)) {
+ retval = -ERESTARTSYS;
+ goto exit;
+ }
+
+
+ /* allow opening only once */
+ if (dev->open_count) {
+ retval = -EBUSY;
+ goto unlock_exit;
+ }
+
+ /* reset the tower */
+ result = usb_control_msg_recv(dev->udev, 0,
+ LEGO_USB_TOWER_REQUEST_RESET,
+ USB_TYPE_VENDOR | USB_DIR_IN | USB_RECIP_DEVICE,
+ 0, 0,
+ &reset_reply, sizeof(reset_reply), 1000,
+ GFP_KERNEL);
+ if (result < 0) {
+ dev_err(&dev->udev->dev,
+ "LEGO USB Tower reset control request failed\n");
+ retval = result;
+ goto unlock_exit;
+ }
+
+ /* initialize in direction */
+ dev->read_buffer_length = 0;
+ dev->read_packet_length = 0;
+ usb_fill_int_urb(dev->interrupt_in_urb,
+ dev->udev,
+ usb_rcvintpipe(dev->udev, dev->interrupt_in_endpoint->bEndpointAddress),
+ dev->interrupt_in_buffer,
+ usb_endpoint_maxp(dev->interrupt_in_endpoint),
+ tower_interrupt_in_callback,
+ dev,
+ dev->interrupt_in_interval);
+
+ dev->interrupt_in_done = 0;
+ mb();
+
+ retval = usb_submit_urb(dev->interrupt_in_urb, GFP_KERNEL);
+ if (retval) {
+ dev_err(&dev->udev->dev,
+ "Couldn't submit interrupt_in_urb %d\n", retval);
+ goto unlock_exit;
+ }
+
+ /* save device in the file's private structure */
+ file->private_data = dev;
+
+ dev->open_count = 1;
+
+unlock_exit:
+ mutex_unlock(&dev->lock);
+
+exit:
+ return retval;
+}
+
+/*
+ * tower_release
+ */
+static int tower_release(struct inode *inode, struct file *file)
+{
+ struct lego_usb_tower *dev;
+ int retval = 0;
+
+ dev = file->private_data;
+ if (dev == NULL) {
+ retval = -ENODEV;
+ goto exit;
+ }
+
+ mutex_lock(&dev->lock);
+
+ if (dev->disconnected) {
+ /* the device was unplugged before the file was released */
+
+ /* unlock here as tower_delete frees dev */
+ mutex_unlock(&dev->lock);
+ tower_delete(dev);
+ goto exit;
+ }
+
+ /* wait until write transfer is finished */
+ if (dev->interrupt_out_busy) {
+ wait_event_interruptible_timeout(dev->write_wait, !dev->interrupt_out_busy,
+ 2 * HZ);
+ }
+
+ /* shutdown transfers */
+ usb_kill_urb(dev->interrupt_in_urb);
+ usb_kill_urb(dev->interrupt_out_urb);
+
+ dev->open_count = 0;
+
+ mutex_unlock(&dev->lock);
+exit:
+ return retval;
+}
+
+/*
+ * tower_check_for_read_packet
+ *
+ * To get correct semantics for signals and non-blocking I/O
+ * with packetizing we pretend not to see any data in the read buffer
+ * until it has been there unchanged for at least
+ * dev->packet_timeout_jiffies, or until the buffer is full.
+ */
+static void tower_check_for_read_packet(struct lego_usb_tower *dev)
+{
+ spin_lock_irq(&dev->read_buffer_lock);
+ if (!packet_timeout
+ || time_after(jiffies, dev->read_last_arrival + dev->packet_timeout_jiffies)
+ || dev->read_buffer_length == read_buffer_size) {
+ dev->read_packet_length = dev->read_buffer_length;
+ }
+ dev->interrupt_in_done = 0;
+ spin_unlock_irq(&dev->read_buffer_lock);
+}
+
+
+/*
+ * tower_poll
+ */
+static __poll_t tower_poll(struct file *file, poll_table *wait)
+{
+ struct lego_usb_tower *dev;
+ __poll_t mask = 0;
+
+ dev = file->private_data;
+
+ if (dev->disconnected)
+ return EPOLLERR | EPOLLHUP;
+
+ poll_wait(file, &dev->read_wait, wait);
+ poll_wait(file, &dev->write_wait, wait);
+
+ tower_check_for_read_packet(dev);
+ if (dev->read_packet_length > 0)
+ mask |= EPOLLIN | EPOLLRDNORM;
+ if (!dev->interrupt_out_busy)
+ mask |= EPOLLOUT | EPOLLWRNORM;
+
+ return mask;
+}
+
+
+/*
+ * tower_llseek
+ */
+static loff_t tower_llseek(struct file *file, loff_t off, int whence)
+{
+ return -ESPIPE; /* unseekable */
+}
+
+
+/*
+ * tower_read
+ */
+static ssize_t tower_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos)
+{
+ struct lego_usb_tower *dev;
+ size_t bytes_to_read;
+ int i;
+ int retval = 0;
+ unsigned long timeout = 0;
+
+ dev = file->private_data;
+
+ /* lock this object */
+ if (mutex_lock_interruptible(&dev->lock)) {
+ retval = -ERESTARTSYS;
+ goto exit;
+ }
+
+ /* verify that the device wasn't unplugged */
+ if (dev->disconnected) {
+ retval = -ENODEV;
+ goto unlock_exit;
+ }
+
+ /* verify that we actually have some data to read */
+ if (count == 0) {
+ dev_dbg(&dev->udev->dev, "read request of 0 bytes\n");
+ goto unlock_exit;
+ }
+
+ if (read_timeout)
+ timeout = jiffies + msecs_to_jiffies(read_timeout);
+
+ /* wait for data */
+ tower_check_for_read_packet(dev);
+ while (dev->read_packet_length == 0) {
+ if (file->f_flags & O_NONBLOCK) {
+ retval = -EAGAIN;
+ goto unlock_exit;
+ }
+ retval = wait_event_interruptible_timeout(dev->read_wait, dev->interrupt_in_done, dev->packet_timeout_jiffies);
+ if (retval < 0)
+ goto unlock_exit;
+
+ /* reset read timeout during read or write activity */
+ if (read_timeout
+ && (dev->read_buffer_length || dev->interrupt_out_busy)) {
+ timeout = jiffies + msecs_to_jiffies(read_timeout);
+ }
+ /* check for read timeout */
+ if (read_timeout && time_after(jiffies, timeout)) {
+ retval = -ETIMEDOUT;
+ goto unlock_exit;
+ }
+ tower_check_for_read_packet(dev);
+ }
+
+ /* copy the data from read_buffer into userspace */
+ bytes_to_read = min(count, dev->read_packet_length);
+
+ if (copy_to_user(buffer, dev->read_buffer, bytes_to_read)) {
+ retval = -EFAULT;
+ goto unlock_exit;
+ }
+
+ spin_lock_irq(&dev->read_buffer_lock);
+ dev->read_buffer_length -= bytes_to_read;
+ dev->read_packet_length -= bytes_to_read;
+ for (i = 0; i < dev->read_buffer_length; i++)
+ dev->read_buffer[i] = dev->read_buffer[i+bytes_to_read];
+ spin_unlock_irq(&dev->read_buffer_lock);
+
+ retval = bytes_to_read;
+
+unlock_exit:
+ /* unlock the device */
+ mutex_unlock(&dev->lock);
+
+exit:
+ return retval;
+}
+
+
+/*
+ * tower_write
+ */
+static ssize_t tower_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos)
+{
+ struct lego_usb_tower *dev;
+ size_t bytes_to_write;
+ int retval = 0;
+
+ dev = file->private_data;
+
+ /* lock this object */
+ if (mutex_lock_interruptible(&dev->lock)) {
+ retval = -ERESTARTSYS;
+ goto exit;
+ }
+
+ /* verify that the device wasn't unplugged */
+ if (dev->disconnected) {
+ retval = -ENODEV;
+ goto unlock_exit;
+ }
+
+ /* verify that we actually have some data to write */
+ if (count == 0) {
+ dev_dbg(&dev->udev->dev, "write request of 0 bytes\n");
+ goto unlock_exit;
+ }
+
+ /* wait until previous transfer is finished */
+ while (dev->interrupt_out_busy) {
+ if (file->f_flags & O_NONBLOCK) {
+ retval = -EAGAIN;
+ goto unlock_exit;
+ }
+ retval = wait_event_interruptible(dev->write_wait,
+ !dev->interrupt_out_busy);
+ if (retval)
+ goto unlock_exit;
+ }
+
+ /* write the data into interrupt_out_buffer from userspace */
+ bytes_to_write = min_t(int, count, write_buffer_size);
+ dev_dbg(&dev->udev->dev, "%s: count = %zd, bytes_to_write = %zd\n",
+ __func__, count, bytes_to_write);
+
+ if (copy_from_user(dev->interrupt_out_buffer, buffer, bytes_to_write)) {
+ retval = -EFAULT;
+ goto unlock_exit;
+ }
+
+ /* send off the urb */
+ usb_fill_int_urb(dev->interrupt_out_urb,
+ dev->udev,
+ usb_sndintpipe(dev->udev, dev->interrupt_out_endpoint->bEndpointAddress),
+ dev->interrupt_out_buffer,
+ bytes_to_write,
+ tower_interrupt_out_callback,
+ dev,
+ dev->interrupt_out_interval);
+
+ dev->interrupt_out_busy = 1;
+ wmb();
+
+ retval = usb_submit_urb(dev->interrupt_out_urb, GFP_KERNEL);
+ if (retval) {
+ dev->interrupt_out_busy = 0;
+ dev_err(&dev->udev->dev,
+ "Couldn't submit interrupt_out_urb %d\n", retval);
+ goto unlock_exit;
+ }
+ retval = bytes_to_write;
+
+unlock_exit:
+ /* unlock the device */
+ mutex_unlock(&dev->lock);
+
+exit:
+ return retval;
+}
+
+
+/*
+ * tower_interrupt_in_callback
+ */
+static void tower_interrupt_in_callback(struct urb *urb)
+{
+ struct lego_usb_tower *dev = urb->context;
+ int status = urb->status;
+ int retval;
+ unsigned long flags;
+
+ lego_usb_tower_debug_data(&dev->udev->dev, __func__,
+ urb->actual_length, urb->transfer_buffer);
+
+ if (status) {
+ if (status == -ENOENT ||
+ status == -ECONNRESET ||
+ status == -ESHUTDOWN) {
+ goto exit;
+ } else {
+ dev_dbg(&dev->udev->dev,
+ "%s: nonzero status received: %d\n", __func__,
+ status);
+ goto resubmit; /* maybe we can recover */
+ }
+ }
+
+ if (urb->actual_length > 0) {
+ spin_lock_irqsave(&dev->read_buffer_lock, flags);
+ if (dev->read_buffer_length + urb->actual_length < read_buffer_size) {
+ memcpy(dev->read_buffer + dev->read_buffer_length,
+ dev->interrupt_in_buffer,
+ urb->actual_length);
+ dev->read_buffer_length += urb->actual_length;
+ dev->read_last_arrival = jiffies;
+ dev_dbg(&dev->udev->dev, "%s: received %d bytes\n",
+ __func__, urb->actual_length);
+ } else {
+ pr_warn("read_buffer overflow, %d bytes dropped\n",
+ urb->actual_length);
+ }
+ spin_unlock_irqrestore(&dev->read_buffer_lock, flags);
+ }
+
+resubmit:
+ retval = usb_submit_urb(dev->interrupt_in_urb, GFP_ATOMIC);
+ if (retval) {
+ dev_err(&dev->udev->dev, "%s: usb_submit_urb failed (%d)\n",
+ __func__, retval);
+ }
+exit:
+ dev->interrupt_in_done = 1;
+ wake_up_interruptible(&dev->read_wait);
+}
+
+
+/*
+ * tower_interrupt_out_callback
+ */
+static void tower_interrupt_out_callback(struct urb *urb)
+{
+ struct lego_usb_tower *dev = urb->context;
+ int status = urb->status;
+
+ lego_usb_tower_debug_data(&dev->udev->dev, __func__,
+ urb->actual_length, urb->transfer_buffer);
+
+ /* sync/async unlink faults aren't errors */
+ if (status && !(status == -ENOENT ||
+ status == -ECONNRESET ||
+ status == -ESHUTDOWN)) {
+ dev_dbg(&dev->udev->dev,
+ "%s: nonzero write bulk status received: %d\n", __func__,
+ status);
+ }
+
+ dev->interrupt_out_busy = 0;
+ wake_up_interruptible(&dev->write_wait);
+}
+
+
+/*
+ * tower_probe
+ *
+ * Called by the usb core when a new device is connected that it thinks
+ * this driver might be interested in.
+ */
+static int tower_probe(struct usb_interface *interface, const struct usb_device_id *id)
+{
+ struct device *idev = &interface->dev;
+ struct usb_device *udev = interface_to_usbdev(interface);
+ struct lego_usb_tower *dev;
+ struct tower_get_version_reply get_version_reply;
+ int retval = -ENOMEM;
+ int result;
+
+ /* allocate memory for our device state and initialize it */
+ dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+ if (!dev)
+ goto exit;
+
+ mutex_init(&dev->lock);
+ dev->udev = usb_get_dev(udev);
+ spin_lock_init(&dev->read_buffer_lock);
+ dev->packet_timeout_jiffies = msecs_to_jiffies(packet_timeout);
+ dev->read_last_arrival = jiffies;
+ init_waitqueue_head(&dev->read_wait);
+ init_waitqueue_head(&dev->write_wait);
+
+ result = usb_find_common_endpoints_reverse(interface->cur_altsetting,
+ NULL, NULL,
+ &dev->interrupt_in_endpoint,
+ &dev->interrupt_out_endpoint);
+ if (result) {
+ dev_err(idev, "interrupt endpoints not found\n");
+ retval = result;
+ goto error;
+ }
+
+ dev->read_buffer = kmalloc(read_buffer_size, GFP_KERNEL);
+ if (!dev->read_buffer)
+ goto error;
+ dev->interrupt_in_buffer = kmalloc(usb_endpoint_maxp(dev->interrupt_in_endpoint), GFP_KERNEL);
+ if (!dev->interrupt_in_buffer)
+ goto error;
+ dev->interrupt_in_urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!dev->interrupt_in_urb)
+ goto error;
+ dev->interrupt_out_buffer = kmalloc(write_buffer_size, GFP_KERNEL);
+ if (!dev->interrupt_out_buffer)
+ goto error;
+ dev->interrupt_out_urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!dev->interrupt_out_urb)
+ goto error;
+ dev->interrupt_in_interval = interrupt_in_interval ? interrupt_in_interval : dev->interrupt_in_endpoint->bInterval;
+ dev->interrupt_out_interval = interrupt_out_interval ? interrupt_out_interval : dev->interrupt_out_endpoint->bInterval;
+
+ /* get the firmware version and log it */
+ result = usb_control_msg_recv(udev, 0,
+ LEGO_USB_TOWER_REQUEST_GET_VERSION,
+ USB_TYPE_VENDOR | USB_DIR_IN | USB_RECIP_DEVICE,
+ 0,
+ 0,
+ &get_version_reply,
+ sizeof(get_version_reply),
+ 1000, GFP_KERNEL);
+ if (result) {
+ dev_err(idev, "get version request failed: %d\n", result);
+ retval = result;
+ goto error;
+ }
+ dev_info(&interface->dev,
+ "LEGO USB Tower firmware version is %d.%d build %d\n",
+ get_version_reply.major,
+ get_version_reply.minor,
+ le16_to_cpu(get_version_reply.build_no));
+
+ /* we can register the device now, as it is ready */
+ usb_set_intfdata(interface, dev);
+
+ retval = usb_register_dev(interface, &tower_class);
+ if (retval) {
+ /* something prevented us from registering this driver */
+ dev_err(idev, "Not able to get a minor for this device.\n");
+ goto error;
+ }
+ dev->minor = interface->minor;
+
+ /* let the user know what node this device is now attached to */
+ dev_info(&interface->dev, "LEGO USB Tower #%d now attached to major "
+ "%d minor %d\n", (dev->minor - LEGO_USB_TOWER_MINOR_BASE),
+ USB_MAJOR, dev->minor);
+
+exit:
+ return retval;
+
+error:
+ tower_delete(dev);
+ return retval;
+}
+
+
+/*
+ * tower_disconnect
+ *
+ * Called by the usb core when the device is removed from the system.
+ */
+static void tower_disconnect(struct usb_interface *interface)
+{
+ struct lego_usb_tower *dev;
+ int minor;
+
+ dev = usb_get_intfdata(interface);
+
+ minor = dev->minor;
+
+ /* give back our minor and prevent further open() */
+ usb_deregister_dev(interface, &tower_class);
+
+ /* stop I/O */
+ usb_poison_urb(dev->interrupt_in_urb);
+ usb_poison_urb(dev->interrupt_out_urb);
+
+ mutex_lock(&dev->lock);
+
+ /* if the device is not opened, then we clean up right now */
+ if (!dev->open_count) {
+ mutex_unlock(&dev->lock);
+ tower_delete(dev);
+ } else {
+ dev->disconnected = 1;
+ /* wake up pollers */
+ wake_up_interruptible_all(&dev->read_wait);
+ wake_up_interruptible_all(&dev->write_wait);
+ mutex_unlock(&dev->lock);
+ }
+
+ dev_info(&interface->dev, "LEGO USB Tower #%d now disconnected\n",
+ (minor - LEGO_USB_TOWER_MINOR_BASE));
+}
+
+module_usb_driver(tower_driver);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/misc/lvstest.c b/drivers/usb/misc/lvstest.c
new file mode 100644
index 000000000..25ec5666a
--- /dev/null
+++ b/drivers/usb/misc/lvstest.c
@@ -0,0 +1,476 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * drivers/usb/misc/lvstest.c
+ *
+ * Test pattern generation for Link Layer Validation System Tests
+ *
+ * Copyright (C) 2014 ST Microelectronics
+ * Pratyush Anand <pratyush.anand@gmail.com>
+ */
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/usb.h>
+#include <linux/usb/ch11.h>
+#include <linux/usb/hcd.h>
+#include <linux/usb/phy.h>
+
+struct lvs_rh {
+ /* root hub interface */
+ struct usb_interface *intf;
+ /* if lvs device connected */
+ bool present;
+ /* port no at which lvs device is present */
+ int portnum;
+ /* urb buffer */
+ u8 buffer[8];
+ /* class descriptor */
+ struct usb_hub_descriptor descriptor;
+ /* urb for polling interrupt pipe */
+ struct urb *urb;
+ /* LVH RH work */
+ struct work_struct rh_work;
+ /* RH port status */
+ struct usb_port_status port_status;
+};
+
+static struct usb_device *create_lvs_device(struct usb_interface *intf)
+{
+ struct usb_device *udev, *hdev;
+ struct usb_hcd *hcd;
+ struct lvs_rh *lvs = usb_get_intfdata(intf);
+
+ if (!lvs->present) {
+ dev_err(&intf->dev, "No LVS device is present\n");
+ return NULL;
+ }
+
+ hdev = interface_to_usbdev(intf);
+ hcd = bus_to_hcd(hdev->bus);
+
+ udev = usb_alloc_dev(hdev, hdev->bus, lvs->portnum);
+ if (!udev) {
+ dev_err(&intf->dev, "Could not allocate lvs udev\n");
+ return NULL;
+ }
+ udev->speed = USB_SPEED_SUPER;
+ udev->ep0.desc.wMaxPacketSize = cpu_to_le16(512);
+ usb_set_device_state(udev, USB_STATE_DEFAULT);
+
+ if (hcd->driver->enable_device) {
+ if (hcd->driver->enable_device(hcd, udev) < 0) {
+ dev_err(&intf->dev, "Failed to enable\n");
+ usb_put_dev(udev);
+ return NULL;
+ }
+ }
+
+ return udev;
+}
+
+static void destroy_lvs_device(struct usb_device *udev)
+{
+ struct usb_device *hdev = udev->parent;
+ struct usb_hcd *hcd = bus_to_hcd(hdev->bus);
+
+ if (hcd->driver->free_dev)
+ hcd->driver->free_dev(hcd, udev);
+
+ usb_put_dev(udev);
+}
+
+static int lvs_rh_clear_port_feature(struct usb_device *hdev,
+ int port1, int feature)
+{
+ return usb_control_msg(hdev, usb_sndctrlpipe(hdev, 0),
+ USB_REQ_CLEAR_FEATURE, USB_RT_PORT, feature, port1,
+ NULL, 0, 1000);
+}
+
+static int lvs_rh_set_port_feature(struct usb_device *hdev,
+ int port1, int feature)
+{
+ return usb_control_msg(hdev, usb_sndctrlpipe(hdev, 0),
+ USB_REQ_SET_FEATURE, USB_RT_PORT, feature, port1,
+ NULL, 0, 1000);
+}
+
+static ssize_t u3_entry_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct usb_interface *intf = to_usb_interface(dev);
+ struct usb_device *hdev = interface_to_usbdev(intf);
+ struct lvs_rh *lvs = usb_get_intfdata(intf);
+ struct usb_device *udev;
+ int ret;
+
+ udev = create_lvs_device(intf);
+ if (!udev) {
+ dev_err(dev, "failed to create lvs device\n");
+ return -ENOMEM;
+ }
+
+ ret = lvs_rh_set_port_feature(hdev, lvs->portnum,
+ USB_PORT_FEAT_SUSPEND);
+ if (ret < 0)
+ dev_err(dev, "can't issue U3 entry %d\n", ret);
+
+ destroy_lvs_device(udev);
+
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+static DEVICE_ATTR_WO(u3_entry);
+
+static ssize_t u3_exit_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct usb_interface *intf = to_usb_interface(dev);
+ struct usb_device *hdev = interface_to_usbdev(intf);
+ struct lvs_rh *lvs = usb_get_intfdata(intf);
+ struct usb_device *udev;
+ int ret;
+
+ udev = create_lvs_device(intf);
+ if (!udev) {
+ dev_err(dev, "failed to create lvs device\n");
+ return -ENOMEM;
+ }
+
+ ret = lvs_rh_clear_port_feature(hdev, lvs->portnum,
+ USB_PORT_FEAT_SUSPEND);
+ if (ret < 0)
+ dev_err(dev, "can't issue U3 exit %d\n", ret);
+
+ destroy_lvs_device(udev);
+
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+static DEVICE_ATTR_WO(u3_exit);
+
+static ssize_t hot_reset_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct usb_interface *intf = to_usb_interface(dev);
+ struct usb_device *hdev = interface_to_usbdev(intf);
+ struct lvs_rh *lvs = usb_get_intfdata(intf);
+ int ret;
+
+ ret = lvs_rh_set_port_feature(hdev, lvs->portnum,
+ USB_PORT_FEAT_RESET);
+ if (ret < 0) {
+ dev_err(dev, "can't issue hot reset %d\n", ret);
+ return ret;
+ }
+
+ return count;
+}
+static DEVICE_ATTR_WO(hot_reset);
+
+static ssize_t warm_reset_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct usb_interface *intf = to_usb_interface(dev);
+ struct usb_device *hdev = interface_to_usbdev(intf);
+ struct lvs_rh *lvs = usb_get_intfdata(intf);
+ int ret;
+
+ ret = lvs_rh_set_port_feature(hdev, lvs->portnum,
+ USB_PORT_FEAT_BH_PORT_RESET);
+ if (ret < 0) {
+ dev_err(dev, "can't issue warm reset %d\n", ret);
+ return ret;
+ }
+
+ return count;
+}
+static DEVICE_ATTR_WO(warm_reset);
+
+static ssize_t u2_timeout_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct usb_interface *intf = to_usb_interface(dev);
+ struct usb_device *hdev = interface_to_usbdev(intf);
+ struct lvs_rh *lvs = usb_get_intfdata(intf);
+ unsigned long val;
+ int ret;
+
+ ret = kstrtoul(buf, 10, &val);
+ if (ret < 0) {
+ dev_err(dev, "couldn't parse string %d\n", ret);
+ return ret;
+ }
+
+ if (val > 127)
+ return -EINVAL;
+
+ ret = lvs_rh_set_port_feature(hdev, lvs->portnum | (val << 8),
+ USB_PORT_FEAT_U2_TIMEOUT);
+ if (ret < 0) {
+ dev_err(dev, "Error %d while setting U2 timeout %ld\n", ret, val);
+ return ret;
+ }
+
+ return count;
+}
+static DEVICE_ATTR_WO(u2_timeout);
+
+static ssize_t u1_timeout_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct usb_interface *intf = to_usb_interface(dev);
+ struct usb_device *hdev = interface_to_usbdev(intf);
+ struct lvs_rh *lvs = usb_get_intfdata(intf);
+ unsigned long val;
+ int ret;
+
+ ret = kstrtoul(buf, 10, &val);
+ if (ret < 0) {
+ dev_err(dev, "couldn't parse string %d\n", ret);
+ return ret;
+ }
+
+ if (val > 127)
+ return -EINVAL;
+
+ ret = lvs_rh_set_port_feature(hdev, lvs->portnum | (val << 8),
+ USB_PORT_FEAT_U1_TIMEOUT);
+ if (ret < 0) {
+ dev_err(dev, "Error %d while setting U1 timeout %ld\n", ret, val);
+ return ret;
+ }
+
+ return count;
+}
+static DEVICE_ATTR_WO(u1_timeout);
+
+static ssize_t get_dev_desc_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct usb_interface *intf = to_usb_interface(dev);
+ struct usb_device *udev;
+ struct usb_device_descriptor *descriptor;
+ int ret;
+
+ descriptor = kmalloc(sizeof(*descriptor), GFP_KERNEL);
+ if (!descriptor)
+ return -ENOMEM;
+
+ udev = create_lvs_device(intf);
+ if (!udev) {
+ dev_err(dev, "failed to create lvs device\n");
+ ret = -ENOMEM;
+ goto free_desc;
+ }
+
+ ret = usb_control_msg(udev, (PIPE_CONTROL << 30) | USB_DIR_IN,
+ USB_REQ_GET_DESCRIPTOR, USB_DIR_IN, USB_DT_DEVICE << 8,
+ 0, descriptor, sizeof(*descriptor),
+ USB_CTRL_GET_TIMEOUT);
+ if (ret < 0)
+ dev_err(dev, "can't read device descriptor %d\n", ret);
+
+ destroy_lvs_device(udev);
+
+free_desc:
+ kfree(descriptor);
+
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+static DEVICE_ATTR_WO(get_dev_desc);
+
+static ssize_t enable_compliance_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct usb_interface *intf = to_usb_interface(dev);
+ struct usb_device *hdev = interface_to_usbdev(intf);
+ struct lvs_rh *lvs = usb_get_intfdata(intf);
+ int ret;
+
+ ret = lvs_rh_set_port_feature(hdev,
+ lvs->portnum | USB_SS_PORT_LS_COMP_MOD << 3,
+ USB_PORT_FEAT_LINK_STATE);
+ if (ret < 0) {
+ dev_err(dev, "can't enable compliance mode %d\n", ret);
+ return ret;
+ }
+
+ return count;
+}
+static DEVICE_ATTR_WO(enable_compliance);
+
+static struct attribute *lvs_attrs[] = {
+ &dev_attr_get_dev_desc.attr,
+ &dev_attr_u1_timeout.attr,
+ &dev_attr_u2_timeout.attr,
+ &dev_attr_hot_reset.attr,
+ &dev_attr_warm_reset.attr,
+ &dev_attr_u3_entry.attr,
+ &dev_attr_u3_exit.attr,
+ &dev_attr_enable_compliance.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(lvs);
+
+static void lvs_rh_work(struct work_struct *work)
+{
+ struct lvs_rh *lvs = container_of(work, struct lvs_rh, rh_work);
+ struct usb_interface *intf = lvs->intf;
+ struct usb_device *hdev = interface_to_usbdev(intf);
+ struct usb_hcd *hcd = bus_to_hcd(hdev->bus);
+ struct usb_hub_descriptor *descriptor = &lvs->descriptor;
+ struct usb_port_status *port_status = &lvs->port_status;
+ int i, ret = 0;
+ u16 portchange;
+
+ /* Examine each root port */
+ for (i = 1; i <= descriptor->bNbrPorts; i++) {
+ ret = usb_control_msg(hdev, usb_rcvctrlpipe(hdev, 0),
+ USB_REQ_GET_STATUS, USB_DIR_IN | USB_RT_PORT, 0, i,
+ port_status, sizeof(*port_status), 1000);
+ if (ret < 4)
+ continue;
+
+ portchange = le16_to_cpu(port_status->wPortChange);
+
+ if (portchange & USB_PORT_STAT_C_LINK_STATE)
+ lvs_rh_clear_port_feature(hdev, i,
+ USB_PORT_FEAT_C_PORT_LINK_STATE);
+ if (portchange & USB_PORT_STAT_C_ENABLE)
+ lvs_rh_clear_port_feature(hdev, i,
+ USB_PORT_FEAT_C_ENABLE);
+ if (portchange & USB_PORT_STAT_C_RESET)
+ lvs_rh_clear_port_feature(hdev, i,
+ USB_PORT_FEAT_C_RESET);
+ if (portchange & USB_PORT_STAT_C_BH_RESET)
+ lvs_rh_clear_port_feature(hdev, i,
+ USB_PORT_FEAT_C_BH_PORT_RESET);
+ if (portchange & USB_PORT_STAT_C_CONNECTION) {
+ lvs_rh_clear_port_feature(hdev, i,
+ USB_PORT_FEAT_C_CONNECTION);
+
+ if (le16_to_cpu(port_status->wPortStatus) &
+ USB_PORT_STAT_CONNECTION) {
+ lvs->present = true;
+ lvs->portnum = i;
+ if (hcd->usb_phy)
+ usb_phy_notify_connect(hcd->usb_phy,
+ USB_SPEED_SUPER);
+ } else {
+ lvs->present = false;
+ if (hcd->usb_phy)
+ usb_phy_notify_disconnect(hcd->usb_phy,
+ USB_SPEED_SUPER);
+ }
+ break;
+ }
+ }
+
+ ret = usb_submit_urb(lvs->urb, GFP_KERNEL);
+ if (ret != 0 && ret != -ENODEV && ret != -EPERM)
+ dev_err(&intf->dev, "urb resubmit error %d\n", ret);
+}
+
+static void lvs_rh_irq(struct urb *urb)
+{
+ struct lvs_rh *lvs = urb->context;
+
+ schedule_work(&lvs->rh_work);
+}
+
+static int lvs_rh_probe(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ struct usb_device *hdev;
+ struct usb_host_interface *desc;
+ struct usb_endpoint_descriptor *endpoint;
+ struct lvs_rh *lvs;
+ unsigned int pipe;
+ int ret, maxp;
+
+ hdev = interface_to_usbdev(intf);
+ desc = intf->cur_altsetting;
+
+ ret = usb_find_int_in_endpoint(desc, &endpoint);
+ if (ret)
+ return ret;
+
+ /* valid only for SS root hub */
+ if (hdev->descriptor.bDeviceProtocol != USB_HUB_PR_SS || hdev->parent) {
+ dev_err(&intf->dev, "Bind LVS driver with SS root Hub only\n");
+ return -EINVAL;
+ }
+
+ lvs = devm_kzalloc(&intf->dev, sizeof(*lvs), GFP_KERNEL);
+ if (!lvs)
+ return -ENOMEM;
+
+ lvs->intf = intf;
+ usb_set_intfdata(intf, lvs);
+
+ /* how many number of ports this root hub has */
+ ret = usb_control_msg(hdev, usb_rcvctrlpipe(hdev, 0),
+ USB_REQ_GET_DESCRIPTOR, USB_DIR_IN | USB_RT_HUB,
+ USB_DT_SS_HUB << 8, 0, &lvs->descriptor,
+ USB_DT_SS_HUB_SIZE, USB_CTRL_GET_TIMEOUT);
+ if (ret < (USB_DT_HUB_NONVAR_SIZE + 2)) {
+ dev_err(&hdev->dev, "wrong root hub descriptor read %d\n", ret);
+ return ret < 0 ? ret : -EINVAL;
+ }
+
+ /* submit urb to poll interrupt endpoint */
+ lvs->urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!lvs->urb)
+ return -ENOMEM;
+
+ INIT_WORK(&lvs->rh_work, lvs_rh_work);
+
+ pipe = usb_rcvintpipe(hdev, endpoint->bEndpointAddress);
+ maxp = usb_maxpacket(hdev, pipe);
+ usb_fill_int_urb(lvs->urb, hdev, pipe, &lvs->buffer[0], maxp,
+ lvs_rh_irq, lvs, endpoint->bInterval);
+
+ ret = usb_submit_urb(lvs->urb, GFP_KERNEL);
+ if (ret < 0) {
+ dev_err(&intf->dev, "couldn't submit lvs urb %d\n", ret);
+ goto free_urb;
+ }
+
+ return ret;
+
+free_urb:
+ usb_free_urb(lvs->urb);
+ return ret;
+}
+
+static void lvs_rh_disconnect(struct usb_interface *intf)
+{
+ struct lvs_rh *lvs = usb_get_intfdata(intf);
+
+ usb_poison_urb(lvs->urb); /* used in scheduled work */
+ flush_work(&lvs->rh_work);
+ usb_free_urb(lvs->urb);
+}
+
+static struct usb_driver lvs_driver = {
+ .name = "lvs",
+ .probe = lvs_rh_probe,
+ .disconnect = lvs_rh_disconnect,
+ .dev_groups = lvs_groups,
+};
+
+module_usb_driver(lvs_driver);
+
+MODULE_DESCRIPTION("Link Layer Validation System Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/misc/onboard_usb_hub.c b/drivers/usb/misc/onboard_usb_hub.c
new file mode 100644
index 000000000..8edd0375e
--- /dev/null
+++ b/drivers/usb/misc/onboard_usb_hub.c
@@ -0,0 +1,461 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for onboard USB hubs
+ *
+ * Copyright (c) 2022, Google LLC
+ */
+
+#include <linux/device.h>
+#include <linux/export.h>
+#include <linux/gpio/consumer.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <linux/suspend.h>
+#include <linux/sysfs.h>
+#include <linux/usb.h>
+#include <linux/usb/hcd.h>
+#include <linux/usb/onboard_hub.h>
+#include <linux/workqueue.h>
+
+#include "onboard_usb_hub.h"
+
+static void onboard_hub_attach_usb_driver(struct work_struct *work);
+
+static struct usb_device_driver onboard_hub_usbdev_driver;
+static DECLARE_WORK(attach_usb_driver_work, onboard_hub_attach_usb_driver);
+
+/************************** Platform driver **************************/
+
+struct usbdev_node {
+ struct usb_device *udev;
+ struct list_head list;
+};
+
+struct onboard_hub {
+ struct regulator *vdd;
+ struct device *dev;
+ const struct onboard_hub_pdata *pdata;
+ struct gpio_desc *reset_gpio;
+ bool always_powered_in_suspend;
+ bool is_powered_on;
+ bool going_away;
+ struct list_head udev_list;
+ struct mutex lock;
+};
+
+static int onboard_hub_power_on(struct onboard_hub *hub)
+{
+ int err;
+
+ err = regulator_enable(hub->vdd);
+ if (err) {
+ dev_err(hub->dev, "failed to enable regulator: %d\n", err);
+ return err;
+ }
+
+ fsleep(hub->pdata->reset_us);
+ gpiod_set_value_cansleep(hub->reset_gpio, 0);
+
+ hub->is_powered_on = true;
+
+ return 0;
+}
+
+static int onboard_hub_power_off(struct onboard_hub *hub)
+{
+ int err;
+
+ gpiod_set_value_cansleep(hub->reset_gpio, 1);
+
+ err = regulator_disable(hub->vdd);
+ if (err) {
+ dev_err(hub->dev, "failed to disable regulator: %d\n", err);
+ return err;
+ }
+
+ hub->is_powered_on = false;
+
+ return 0;
+}
+
+static int __maybe_unused onboard_hub_suspend(struct device *dev)
+{
+ struct onboard_hub *hub = dev_get_drvdata(dev);
+ struct usbdev_node *node;
+ bool power_off = true;
+
+ if (hub->always_powered_in_suspend)
+ return 0;
+
+ mutex_lock(&hub->lock);
+
+ list_for_each_entry(node, &hub->udev_list, list) {
+ if (!device_may_wakeup(node->udev->bus->controller))
+ continue;
+
+ if (usb_wakeup_enabled_descendants(node->udev)) {
+ power_off = false;
+ break;
+ }
+ }
+
+ mutex_unlock(&hub->lock);
+
+ if (!power_off)
+ return 0;
+
+ return onboard_hub_power_off(hub);
+}
+
+static int __maybe_unused onboard_hub_resume(struct device *dev)
+{
+ struct onboard_hub *hub = dev_get_drvdata(dev);
+
+ if (hub->is_powered_on)
+ return 0;
+
+ return onboard_hub_power_on(hub);
+}
+
+static inline void get_udev_link_name(const struct usb_device *udev, char *buf, size_t size)
+{
+ snprintf(buf, size, "usb_dev.%s", dev_name(&udev->dev));
+}
+
+static int onboard_hub_add_usbdev(struct onboard_hub *hub, struct usb_device *udev)
+{
+ struct usbdev_node *node;
+ char link_name[64];
+ int err;
+
+ mutex_lock(&hub->lock);
+
+ if (hub->going_away) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ node = kzalloc(sizeof(*node), GFP_KERNEL);
+ if (!node) {
+ err = -ENOMEM;
+ goto error;
+ }
+
+ node->udev = udev;
+
+ list_add(&node->list, &hub->udev_list);
+
+ mutex_unlock(&hub->lock);
+
+ get_udev_link_name(udev, link_name, sizeof(link_name));
+ WARN_ON(sysfs_create_link(&hub->dev->kobj, &udev->dev.kobj, link_name));
+
+ return 0;
+
+error:
+ mutex_unlock(&hub->lock);
+
+ return err;
+}
+
+static void onboard_hub_remove_usbdev(struct onboard_hub *hub, const struct usb_device *udev)
+{
+ struct usbdev_node *node;
+ char link_name[64];
+
+ get_udev_link_name(udev, link_name, sizeof(link_name));
+ sysfs_remove_link(&hub->dev->kobj, link_name);
+
+ mutex_lock(&hub->lock);
+
+ list_for_each_entry(node, &hub->udev_list, list) {
+ if (node->udev == udev) {
+ list_del(&node->list);
+ kfree(node);
+ break;
+ }
+ }
+
+ mutex_unlock(&hub->lock);
+}
+
+static ssize_t always_powered_in_suspend_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ const struct onboard_hub *hub = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%d\n", hub->always_powered_in_suspend);
+}
+
+static ssize_t always_powered_in_suspend_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct onboard_hub *hub = dev_get_drvdata(dev);
+ bool val;
+ int ret;
+
+ ret = kstrtobool(buf, &val);
+ if (ret < 0)
+ return ret;
+
+ hub->always_powered_in_suspend = val;
+
+ return count;
+}
+static DEVICE_ATTR_RW(always_powered_in_suspend);
+
+static struct attribute *onboard_hub_attrs[] = {
+ &dev_attr_always_powered_in_suspend.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(onboard_hub);
+
+static void onboard_hub_attach_usb_driver(struct work_struct *work)
+{
+ int err;
+
+ err = driver_attach(&onboard_hub_usbdev_driver.drvwrap.driver);
+ if (err)
+ pr_err("Failed to attach USB driver: %d\n", err);
+}
+
+static int onboard_hub_probe(struct platform_device *pdev)
+{
+ const struct of_device_id *of_id;
+ struct device *dev = &pdev->dev;
+ struct onboard_hub *hub;
+ int err;
+
+ hub = devm_kzalloc(dev, sizeof(*hub), GFP_KERNEL);
+ if (!hub)
+ return -ENOMEM;
+
+ of_id = of_match_device(onboard_hub_match, &pdev->dev);
+ if (!of_id)
+ return -ENODEV;
+
+ hub->pdata = of_id->data;
+ if (!hub->pdata)
+ return -EINVAL;
+
+ hub->vdd = devm_regulator_get(dev, "vdd");
+ if (IS_ERR(hub->vdd))
+ return PTR_ERR(hub->vdd);
+
+ hub->reset_gpio = devm_gpiod_get_optional(dev, "reset",
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(hub->reset_gpio))
+ return dev_err_probe(dev, PTR_ERR(hub->reset_gpio), "failed to get reset GPIO\n");
+
+ hub->dev = dev;
+ mutex_init(&hub->lock);
+ INIT_LIST_HEAD(&hub->udev_list);
+
+ dev_set_drvdata(dev, hub);
+
+ err = onboard_hub_power_on(hub);
+ if (err)
+ return err;
+
+ /*
+ * The USB driver might have been detached from the USB devices by
+ * onboard_hub_remove() (e.g. through an 'unbind' by userspace),
+ * make sure to re-attach it if needed.
+ *
+ * This needs to be done deferred to avoid self-deadlocks on systems
+ * with nested onboard hubs.
+ */
+ schedule_work(&attach_usb_driver_work);
+
+ return 0;
+}
+
+static int onboard_hub_remove(struct platform_device *pdev)
+{
+ struct onboard_hub *hub = dev_get_drvdata(&pdev->dev);
+ struct usbdev_node *node;
+ struct usb_device *udev;
+
+ hub->going_away = true;
+
+ mutex_lock(&hub->lock);
+
+ /* unbind the USB devices to avoid dangling references to this device */
+ while (!list_empty(&hub->udev_list)) {
+ node = list_first_entry(&hub->udev_list, struct usbdev_node, list);
+ udev = node->udev;
+
+ /*
+ * Unbinding the driver will call onboard_hub_remove_usbdev(),
+ * which acquires hub->lock. We must release the lock first.
+ */
+ get_device(&udev->dev);
+ mutex_unlock(&hub->lock);
+ device_release_driver(&udev->dev);
+ put_device(&udev->dev);
+ mutex_lock(&hub->lock);
+ }
+
+ mutex_unlock(&hub->lock);
+
+ return onboard_hub_power_off(hub);
+}
+
+MODULE_DEVICE_TABLE(of, onboard_hub_match);
+
+static const struct dev_pm_ops __maybe_unused onboard_hub_pm_ops = {
+ SET_LATE_SYSTEM_SLEEP_PM_OPS(onboard_hub_suspend, onboard_hub_resume)
+};
+
+static struct platform_driver onboard_hub_driver = {
+ .probe = onboard_hub_probe,
+ .remove = onboard_hub_remove,
+
+ .driver = {
+ .name = "onboard-usb-hub",
+ .of_match_table = onboard_hub_match,
+ .pm = pm_ptr(&onboard_hub_pm_ops),
+ .dev_groups = onboard_hub_groups,
+ },
+};
+
+/************************** USB driver **************************/
+
+#define VENDOR_ID_GENESYS 0x05e3
+#define VENDOR_ID_MICROCHIP 0x0424
+#define VENDOR_ID_REALTEK 0x0bda
+#define VENDOR_ID_TI 0x0451
+
+/*
+ * Returns the onboard_hub platform device that is associated with the USB
+ * device passed as parameter.
+ */
+static struct onboard_hub *_find_onboard_hub(struct device *dev)
+{
+ struct platform_device *pdev;
+ struct device_node *np;
+ struct onboard_hub *hub;
+
+ pdev = of_find_device_by_node(dev->of_node);
+ if (!pdev) {
+ np = of_parse_phandle(dev->of_node, "peer-hub", 0);
+ if (!np) {
+ dev_err(dev, "failed to find device node for peer hub\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ pdev = of_find_device_by_node(np);
+ of_node_put(np);
+
+ if (!pdev)
+ return ERR_PTR(-ENODEV);
+ }
+
+ hub = dev_get_drvdata(&pdev->dev);
+ put_device(&pdev->dev);
+
+ /*
+ * The presence of drvdata ('hub') indicates that the platform driver
+ * finished probing. This handles the case where (conceivably) we could
+ * be running at the exact same time as the platform driver's probe. If
+ * we detect the race we request probe deferral and we'll come back and
+ * try again.
+ */
+ if (!hub)
+ return ERR_PTR(-EPROBE_DEFER);
+
+ return hub;
+}
+
+static int onboard_hub_usbdev_probe(struct usb_device *udev)
+{
+ struct device *dev = &udev->dev;
+ struct onboard_hub *hub;
+ int err;
+
+ /* ignore supported hubs without device tree node */
+ if (!dev->of_node)
+ return -ENODEV;
+
+ hub = _find_onboard_hub(dev);
+ if (IS_ERR(hub))
+ return PTR_ERR(hub);
+
+ dev_set_drvdata(dev, hub);
+
+ err = onboard_hub_add_usbdev(hub, udev);
+ if (err)
+ return err;
+
+ return 0;
+}
+
+static void onboard_hub_usbdev_disconnect(struct usb_device *udev)
+{
+ struct onboard_hub *hub = dev_get_drvdata(&udev->dev);
+
+ onboard_hub_remove_usbdev(hub, udev);
+}
+
+static const struct usb_device_id onboard_hub_id_table[] = {
+ { USB_DEVICE(VENDOR_ID_GENESYS, 0x0608) }, /* Genesys Logic GL850G USB 2.0 */
+ { USB_DEVICE(VENDOR_ID_GENESYS, 0x0610) }, /* Genesys Logic GL852G USB 2.0 */
+ { USB_DEVICE(VENDOR_ID_GENESYS, 0x0620) }, /* Genesys Logic GL3523 USB 3.1 */
+ { USB_DEVICE(VENDOR_ID_MICROCHIP, 0x2412) }, /* USB2412 USB 2.0 */
+ { USB_DEVICE(VENDOR_ID_MICROCHIP, 0x2514) }, /* USB2514B USB 2.0 */
+ { USB_DEVICE(VENDOR_ID_MICROCHIP, 0x2517) }, /* USB2517 USB 2.0 */
+ { USB_DEVICE(VENDOR_ID_REALTEK, 0x0411) }, /* RTS5411 USB 3.1 */
+ { USB_DEVICE(VENDOR_ID_REALTEK, 0x5411) }, /* RTS5411 USB 2.1 */
+ { USB_DEVICE(VENDOR_ID_REALTEK, 0x0414) }, /* RTS5414 USB 3.2 */
+ { USB_DEVICE(VENDOR_ID_REALTEK, 0x5414) }, /* RTS5414 USB 2.1 */
+ { USB_DEVICE(VENDOR_ID_TI, 0x8140) }, /* TI USB8041 3.0 */
+ { USB_DEVICE(VENDOR_ID_TI, 0x8142) }, /* TI USB8041 2.0 */
+ {}
+};
+MODULE_DEVICE_TABLE(usb, onboard_hub_id_table);
+
+static struct usb_device_driver onboard_hub_usbdev_driver = {
+ .name = "onboard-usb-hub",
+ .probe = onboard_hub_usbdev_probe,
+ .disconnect = onboard_hub_usbdev_disconnect,
+ .generic_subclass = 1,
+ .supports_autosuspend = 1,
+ .id_table = onboard_hub_id_table,
+};
+
+static int __init onboard_hub_init(void)
+{
+ int ret;
+
+ ret = usb_register_device_driver(&onboard_hub_usbdev_driver, THIS_MODULE);
+ if (ret)
+ return ret;
+
+ ret = platform_driver_register(&onboard_hub_driver);
+ if (ret)
+ usb_deregister_device_driver(&onboard_hub_usbdev_driver);
+
+ return ret;
+}
+module_init(onboard_hub_init);
+
+static void __exit onboard_hub_exit(void)
+{
+ usb_deregister_device_driver(&onboard_hub_usbdev_driver);
+ platform_driver_unregister(&onboard_hub_driver);
+
+ cancel_work_sync(&attach_usb_driver_work);
+}
+module_exit(onboard_hub_exit);
+
+MODULE_AUTHOR("Matthias Kaehlcke <mka@chromium.org>");
+MODULE_DESCRIPTION("Driver for discrete onboard USB hubs");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/usb/misc/onboard_usb_hub.h b/drivers/usb/misc/onboard_usb_hub.h
new file mode 100644
index 000000000..d023fb90b
--- /dev/null
+++ b/drivers/usb/misc/onboard_usb_hub.h
@@ -0,0 +1,49 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright (c) 2022, Google LLC
+ */
+
+#ifndef _USB_MISC_ONBOARD_USB_HUB_H
+#define _USB_MISC_ONBOARD_USB_HUB_H
+
+struct onboard_hub_pdata {
+ unsigned long reset_us; /* reset pulse width in us */
+};
+
+static const struct onboard_hub_pdata microchip_usb424_data = {
+ .reset_us = 1,
+};
+
+static const struct onboard_hub_pdata realtek_rts5411_data = {
+ .reset_us = 0,
+};
+
+static const struct onboard_hub_pdata ti_tusb8041_data = {
+ .reset_us = 3000,
+};
+
+static const struct onboard_hub_pdata genesys_gl850g_data = {
+ .reset_us = 3,
+};
+
+static const struct onboard_hub_pdata genesys_gl852g_data = {
+ .reset_us = 50,
+};
+
+static const struct of_device_id onboard_hub_match[] = {
+ { .compatible = "usb424,2412", .data = &microchip_usb424_data, },
+ { .compatible = "usb424,2514", .data = &microchip_usb424_data, },
+ { .compatible = "usb424,2517", .data = &microchip_usb424_data, },
+ { .compatible = "usb451,8140", .data = &ti_tusb8041_data, },
+ { .compatible = "usb451,8142", .data = &ti_tusb8041_data, },
+ { .compatible = "usb5e3,608", .data = &genesys_gl850g_data, },
+ { .compatible = "usb5e3,610", .data = &genesys_gl852g_data, },
+ { .compatible = "usb5e3,620", .data = &genesys_gl852g_data, },
+ { .compatible = "usbbda,411", .data = &realtek_rts5411_data, },
+ { .compatible = "usbbda,5411", .data = &realtek_rts5411_data, },
+ { .compatible = "usbbda,414", .data = &realtek_rts5411_data, },
+ { .compatible = "usbbda,5414", .data = &realtek_rts5411_data, },
+ {}
+};
+
+#endif /* _USB_MISC_ONBOARD_USB_HUB_H */
diff --git a/drivers/usb/misc/onboard_usb_hub_pdevs.c b/drivers/usb/misc/onboard_usb_hub_pdevs.c
new file mode 100644
index 000000000..ed22a18f4
--- /dev/null
+++ b/drivers/usb/misc/onboard_usb_hub_pdevs.c
@@ -0,0 +1,143 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * API for creating and destroying USB onboard hub platform devices
+ *
+ * Copyright (c) 2022, Google LLC
+ */
+
+#include <linux/device.h>
+#include <linux/export.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/usb.h>
+#include <linux/usb/hcd.h>
+#include <linux/usb/of.h>
+#include <linux/usb/onboard_hub.h>
+
+#include "onboard_usb_hub.h"
+
+struct pdev_list_entry {
+ struct platform_device *pdev;
+ struct list_head node;
+};
+
+static bool of_is_onboard_usb_hub(const struct device_node *np)
+{
+ return !!of_match_node(onboard_hub_match, np);
+}
+
+/**
+ * onboard_hub_create_pdevs -- create platform devices for onboard USB hubs
+ * @parent_hub : parent hub to scan for connected onboard hubs
+ * @pdev_list : list of onboard hub platform devices owned by the parent hub
+ *
+ * Creates a platform device for each supported onboard hub that is connected to
+ * the given parent hub. The platform device is in charge of initializing the
+ * hub (enable regulators, take the hub out of reset, ...) and can optionally
+ * control whether the hub remains powered during system suspend or not.
+ *
+ * To keep track of the platform devices they are added to a list that is owned
+ * by the parent hub.
+ *
+ * Some background about the logic in this function, which can be a bit hard
+ * to follow:
+ *
+ * Root hubs don't have dedicated device tree nodes, but use the node of their
+ * HCD. The primary and secondary HCD are usually represented by a single DT
+ * node. That means the root hubs of the primary and secondary HCD share the
+ * same device tree node (the HCD node). As a result this function can be called
+ * twice with the same DT node for root hubs. We only want to create a single
+ * platform device for each physical onboard hub, hence for root hubs the loop
+ * is only executed for the root hub of the primary HCD. Since the function
+ * scans through all child nodes it still creates pdevs for onboard hubs
+ * connected to the root hub of the secondary HCD if needed.
+ *
+ * Further there must be only one platform device for onboard hubs with a peer
+ * hub (the hub is a single physical device). To achieve this two measures are
+ * taken: pdevs for onboard hubs with a peer are only created when the function
+ * is called on behalf of the parent hub that is connected to the primary HCD
+ * (directly or through other hubs). For onboard hubs connected to root hubs
+ * the function processes the nodes of both peers. A platform device is only
+ * created if the peer hub doesn't have one already.
+ */
+void onboard_hub_create_pdevs(struct usb_device *parent_hub, struct list_head *pdev_list)
+{
+ int i;
+ struct usb_hcd *hcd = bus_to_hcd(parent_hub->bus);
+ struct device_node *np, *npc;
+ struct platform_device *pdev;
+ struct pdev_list_entry *pdle;
+
+ if (!parent_hub->dev.of_node)
+ return;
+
+ if (!parent_hub->parent && !usb_hcd_is_primary_hcd(hcd))
+ return;
+
+ for (i = 1; i <= parent_hub->maxchild; i++) {
+ np = usb_of_get_device_node(parent_hub, i);
+ if (!np)
+ continue;
+
+ if (!of_is_onboard_usb_hub(np))
+ goto node_put;
+
+ npc = of_parse_phandle(np, "peer-hub", 0);
+ if (npc) {
+ if (!usb_hcd_is_primary_hcd(hcd)) {
+ of_node_put(npc);
+ goto node_put;
+ }
+
+ pdev = of_find_device_by_node(npc);
+ of_node_put(npc);
+
+ if (pdev) {
+ put_device(&pdev->dev);
+ goto node_put;
+ }
+ }
+
+ pdev = of_platform_device_create(np, NULL, &parent_hub->dev);
+ if (!pdev) {
+ dev_err(&parent_hub->dev,
+ "failed to create platform device for onboard hub '%pOF'\n", np);
+ goto node_put;
+ }
+
+ pdle = kzalloc(sizeof(*pdle), GFP_KERNEL);
+ if (!pdle) {
+ of_platform_device_destroy(&pdev->dev, NULL);
+ goto node_put;
+ }
+
+ pdle->pdev = pdev;
+ list_add(&pdle->node, pdev_list);
+
+node_put:
+ of_node_put(np);
+ }
+}
+EXPORT_SYMBOL_GPL(onboard_hub_create_pdevs);
+
+/**
+ * onboard_hub_destroy_pdevs -- free resources of onboard hub platform devices
+ * @pdev_list : list of onboard hub platform devices
+ *
+ * Destroys the platform devices in the given list and frees the memory associated
+ * with the list entry.
+ */
+void onboard_hub_destroy_pdevs(struct list_head *pdev_list)
+{
+ struct pdev_list_entry *pdle, *tmp;
+
+ list_for_each_entry_safe(pdle, tmp, pdev_list, node) {
+ list_del(&pdle->node);
+ of_platform_device_destroy(&pdle->pdev->dev, NULL);
+ kfree(pdle);
+ }
+}
+EXPORT_SYMBOL_GPL(onboard_hub_destroy_pdevs);
diff --git a/drivers/usb/misc/qcom_eud.c b/drivers/usb/misc/qcom_eud.c
new file mode 100644
index 000000000..b7f13df00
--- /dev/null
+++ b/drivers/usb/misc/qcom_eud.c
@@ -0,0 +1,251 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2015-2021, The Linux Foundation. All rights reserved.
+ */
+
+#include <linux/bitops.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/sysfs.h>
+#include <linux/usb/role.h>
+
+#define EUD_REG_INT1_EN_MASK 0x0024
+#define EUD_REG_INT_STATUS_1 0x0044
+#define EUD_REG_CTL_OUT_1 0x0074
+#define EUD_REG_VBUS_INT_CLR 0x0080
+#define EUD_REG_CSR_EUD_EN 0x1014
+#define EUD_REG_SW_ATTACH_DET 0x1018
+#define EUD_REG_EUD_EN2 0x0000
+
+#define EUD_ENABLE BIT(0)
+#define EUD_INT_PET_EUD BIT(0)
+#define EUD_INT_VBUS BIT(2)
+#define EUD_INT_SAFE_MODE BIT(4)
+#define EUD_INT_ALL (EUD_INT_VBUS | EUD_INT_SAFE_MODE)
+
+struct eud_chip {
+ struct device *dev;
+ struct usb_role_switch *role_sw;
+ void __iomem *base;
+ void __iomem *mode_mgr;
+ unsigned int int_status;
+ int irq;
+ bool enabled;
+ bool usb_attached;
+};
+
+static int enable_eud(struct eud_chip *priv)
+{
+ writel(EUD_ENABLE, priv->base + EUD_REG_CSR_EUD_EN);
+ writel(EUD_INT_VBUS | EUD_INT_SAFE_MODE,
+ priv->base + EUD_REG_INT1_EN_MASK);
+ writel(1, priv->mode_mgr + EUD_REG_EUD_EN2);
+
+ return usb_role_switch_set_role(priv->role_sw, USB_ROLE_DEVICE);
+}
+
+static void disable_eud(struct eud_chip *priv)
+{
+ writel(0, priv->base + EUD_REG_CSR_EUD_EN);
+ writel(0, priv->mode_mgr + EUD_REG_EUD_EN2);
+}
+
+static ssize_t enable_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct eud_chip *chip = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%d\n", chip->enabled);
+}
+
+static ssize_t enable_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct eud_chip *chip = dev_get_drvdata(dev);
+ bool enable;
+ int ret;
+
+ if (kstrtobool(buf, &enable))
+ return -EINVAL;
+
+ if (enable) {
+ ret = enable_eud(chip);
+ if (!ret)
+ chip->enabled = enable;
+ else
+ disable_eud(chip);
+ } else {
+ disable_eud(chip);
+ }
+
+ return count;
+}
+
+static DEVICE_ATTR_RW(enable);
+
+static struct attribute *eud_attrs[] = {
+ &dev_attr_enable.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(eud);
+
+static void usb_attach_detach(struct eud_chip *chip)
+{
+ u32 reg;
+
+ /* read ctl_out_1[4] to find USB attach or detach event */
+ reg = readl(chip->base + EUD_REG_CTL_OUT_1);
+ chip->usb_attached = reg & EUD_INT_SAFE_MODE;
+}
+
+static void pet_eud(struct eud_chip *chip)
+{
+ u32 reg;
+ int ret;
+
+ /* When the EUD_INT_PET_EUD in SW_ATTACH_DET is set, the cable has been
+ * disconnected and we need to detach the pet to check if EUD is in safe
+ * mode before attaching again.
+ */
+ reg = readl(chip->base + EUD_REG_SW_ATTACH_DET);
+ if (reg & EUD_INT_PET_EUD) {
+ /* Detach & Attach pet for EUD */
+ writel(0, chip->base + EUD_REG_SW_ATTACH_DET);
+ /* Delay to make sure detach pet is done before attach pet */
+ ret = readl_poll_timeout(chip->base + EUD_REG_SW_ATTACH_DET,
+ reg, (reg == 0), 1, 100);
+ if (ret) {
+ dev_err(chip->dev, "Detach pet failed\n");
+ return;
+ }
+ }
+ /* Attach pet for EUD */
+ writel(EUD_INT_PET_EUD, chip->base + EUD_REG_SW_ATTACH_DET);
+}
+
+static irqreturn_t handle_eud_irq(int irq, void *data)
+{
+ struct eud_chip *chip = data;
+ u32 reg;
+
+ reg = readl(chip->base + EUD_REG_INT_STATUS_1);
+ switch (reg & EUD_INT_ALL) {
+ case EUD_INT_VBUS:
+ usb_attach_detach(chip);
+ return IRQ_WAKE_THREAD;
+ case EUD_INT_SAFE_MODE:
+ pet_eud(chip);
+ return IRQ_HANDLED;
+ default:
+ return IRQ_NONE;
+ }
+}
+
+static irqreturn_t handle_eud_irq_thread(int irq, void *data)
+{
+ struct eud_chip *chip = data;
+ int ret;
+
+ if (chip->usb_attached)
+ ret = usb_role_switch_set_role(chip->role_sw, USB_ROLE_DEVICE);
+ else
+ ret = usb_role_switch_set_role(chip->role_sw, USB_ROLE_HOST);
+ if (ret)
+ dev_err(chip->dev, "failed to set role switch\n");
+
+ /* set and clear vbus_int_clr[0] to clear interrupt */
+ writel(BIT(0), chip->base + EUD_REG_VBUS_INT_CLR);
+ writel(0, chip->base + EUD_REG_VBUS_INT_CLR);
+
+ return IRQ_HANDLED;
+}
+
+static void eud_role_switch_release(void *data)
+{
+ struct eud_chip *chip = data;
+
+ usb_role_switch_put(chip->role_sw);
+}
+
+static int eud_probe(struct platform_device *pdev)
+{
+ struct eud_chip *chip;
+ int ret;
+
+ chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->dev = &pdev->dev;
+
+ chip->role_sw = usb_role_switch_get(&pdev->dev);
+ if (IS_ERR(chip->role_sw))
+ return dev_err_probe(chip->dev, PTR_ERR(chip->role_sw),
+ "failed to get role switch\n");
+
+ ret = devm_add_action_or_reset(chip->dev, eud_role_switch_release, chip);
+ if (ret)
+ return dev_err_probe(chip->dev, ret,
+ "failed to add role switch release action\n");
+
+ chip->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(chip->base))
+ return PTR_ERR(chip->base);
+
+ chip->mode_mgr = devm_platform_ioremap_resource(pdev, 1);
+ if (IS_ERR(chip->mode_mgr))
+ return PTR_ERR(chip->mode_mgr);
+
+ chip->irq = platform_get_irq(pdev, 0);
+ ret = devm_request_threaded_irq(&pdev->dev, chip->irq, handle_eud_irq,
+ handle_eud_irq_thread, IRQF_ONESHOT, NULL, chip);
+ if (ret)
+ return dev_err_probe(chip->dev, ret, "failed to allocate irq\n");
+
+ enable_irq_wake(chip->irq);
+
+ platform_set_drvdata(pdev, chip);
+
+ return 0;
+}
+
+static int eud_remove(struct platform_device *pdev)
+{
+ struct eud_chip *chip = platform_get_drvdata(pdev);
+
+ if (chip->enabled)
+ disable_eud(chip);
+
+ device_init_wakeup(&pdev->dev, false);
+ disable_irq_wake(chip->irq);
+
+ return 0;
+}
+
+static const struct of_device_id eud_dt_match[] = {
+ { .compatible = "qcom,sc7280-eud" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, eud_dt_match);
+
+static struct platform_driver eud_driver = {
+ .probe = eud_probe,
+ .remove = eud_remove,
+ .driver = {
+ .name = "qcom_eud",
+ .dev_groups = eud_groups,
+ .of_match_table = eud_dt_match,
+ },
+};
+module_platform_driver(eud_driver);
+
+MODULE_DESCRIPTION("QTI EUD driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/usb/misc/sisusbvga/Kconfig b/drivers/usb/misc/sisusbvga/Kconfig
new file mode 100644
index 000000000..c12cdd015
--- /dev/null
+++ b/drivers/usb/misc/sisusbvga/Kconfig
@@ -0,0 +1,48 @@
+# SPDX-License-Identifier: GPL-2.0
+
+config USB_SISUSBVGA
+ tristate "USB 2.0 SVGA dongle support (Net2280/SiS315)"
+ depends on (USB_MUSB_HDRC || USB_EHCI_HCD)
+ select FONT_SUPPORT if USB_SISUSBVGA_CON
+ help
+ Say Y here if you intend to attach a USB2VGA dongle based on a
+ Net2280 and a SiS315 chip.
+
+ Note that this device requires a USB 2.0 host controller. It will not
+ work with USB 1.x controllers.
+
+ To compile this driver as a module, choose M here; the module will be
+ called sisusbvga. If unsure, say N.
+
+config USB_SISUSBVGA_CON
+ bool "Text console and mode switching support" if USB_SISUSBVGA
+ depends on VT && BROKEN
+ select FONT_8x16
+ help
+ Say Y here if you want a VGA text console via the USB dongle or
+ want to support userland applications that utilize the driver's
+ display mode switching capabilities.
+
+ Note that this console supports VGA/EGA text mode only.
+
+ By default, the console part of the driver will not kick in when
+ the driver is initialized. If you want the driver to take over
+ one or more of the consoles, you need to specify the number of
+ the first and last consoles (starting at 1) as driver parameters.
+
+ For example, if the driver is compiled as a module:
+
+ modprobe sisusbvga first=1 last=5
+
+ If you use hotplug, add this to your modutils config files with
+ the "options" keyword, such as eg.
+
+ options sisusbvga first=1 last=5
+
+ If the driver is compiled into the kernel image, the parameters
+ must be given in the kernel command like, such as
+
+ sisusbvga.first=1 sisusbvga.last=5
+
+
+
diff --git a/drivers/usb/misc/sisusbvga/Makefile b/drivers/usb/misc/sisusbvga/Makefile
new file mode 100644
index 000000000..6551bce68
--- /dev/null
+++ b/drivers/usb/misc/sisusbvga/Makefile
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for the sisusb driver (if driver is inside kernel tree).
+#
+
+obj-$(CONFIG_USB_SISUSBVGA) += sisusbvga.o
+
+sisusbvga-y := sisusb.o
+sisusbvga-$(CONFIG_USB_SISUSBVGA_CON) += sisusb_con.o sisusb_init.o
diff --git a/drivers/usb/misc/sisusbvga/sisusb.c b/drivers/usb/misc/sisusbvga/sisusb.c
new file mode 100644
index 000000000..8ed803c4a
--- /dev/null
+++ b/drivers/usb/misc/sisusbvga/sisusb.c
@@ -0,0 +1,3244 @@
+// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+/*
+ * sisusb - usb kernel driver for SiS315(E) based USB2VGA dongles
+ *
+ * Main part
+ *
+ * Copyright (C) 2005 by Thomas Winischhofer, Vienna, Austria
+ *
+ * If distributed as part of the Linux kernel, this code is licensed under the
+ * terms of the GPL v2.
+ *
+ * Otherwise, the following license terms apply:
+ *
+ * * Redistribution and use in source and binary forms, with or without
+ * * modification, are permitted provided that the following conditions
+ * * are met:
+ * * 1) Redistributions of source code must retain the above copyright
+ * * notice, this list of conditions and the following disclaimer.
+ * * 2) Redistributions in binary form must reproduce the above copyright
+ * * notice, this list of conditions and the following disclaimer in the
+ * * documentation and/or other materials provided with the distribution.
+ * * 3) The name of the author may not be used to endorse or promote products
+ * * derived from this software without specific psisusbr written permission.
+ * *
+ * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESSED OR
+ * * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Author: Thomas Winischhofer <thomas@winischhofer.net>
+ *
+ */
+
+#include <linux/mutex.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/signal.h>
+#include <linux/errno.h>
+#include <linux/poll.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/kref.h>
+#include <linux/usb.h>
+#include <linux/vmalloc.h>
+
+#include "sisusb.h"
+#include "sisusb_init.h"
+
+#ifdef CONFIG_USB_SISUSBVGA_CON
+#include <linux/font.h>
+#endif
+
+#define SISUSB_DONTSYNC
+
+/* Forward declarations / clean-up routines */
+
+#ifdef CONFIG_USB_SISUSBVGA_CON
+static int sisusb_first_vc;
+static int sisusb_last_vc;
+module_param_named(first, sisusb_first_vc, int, 0);
+module_param_named(last, sisusb_last_vc, int, 0);
+MODULE_PARM_DESC(first, "Number of first console to take over (1 - MAX_NR_CONSOLES)");
+MODULE_PARM_DESC(last, "Number of last console to take over (1 - MAX_NR_CONSOLES)");
+#endif
+
+static struct usb_driver sisusb_driver;
+
+static void sisusb_free_buffers(struct sisusb_usb_data *sisusb)
+{
+ int i;
+
+ for (i = 0; i < NUMOBUFS; i++) {
+ kfree(sisusb->obuf[i]);
+ sisusb->obuf[i] = NULL;
+ }
+ kfree(sisusb->ibuf);
+ sisusb->ibuf = NULL;
+}
+
+static void sisusb_free_urbs(struct sisusb_usb_data *sisusb)
+{
+ int i;
+
+ for (i = 0; i < NUMOBUFS; i++) {
+ usb_free_urb(sisusb->sisurbout[i]);
+ sisusb->sisurbout[i] = NULL;
+ }
+ usb_free_urb(sisusb->sisurbin);
+ sisusb->sisurbin = NULL;
+}
+
+/* Level 0: USB transport layer */
+
+/* 1. out-bulks */
+
+/* out-urb management */
+
+/* Return 1 if all free, 0 otherwise */
+static int sisusb_all_free(struct sisusb_usb_data *sisusb)
+{
+ int i;
+
+ for (i = 0; i < sisusb->numobufs; i++) {
+
+ if (sisusb->urbstatus[i] & SU_URB_BUSY)
+ return 0;
+
+ }
+
+ return 1;
+}
+
+/* Kill all busy URBs */
+static void sisusb_kill_all_busy(struct sisusb_usb_data *sisusb)
+{
+ int i;
+
+ if (sisusb_all_free(sisusb))
+ return;
+
+ for (i = 0; i < sisusb->numobufs; i++) {
+
+ if (sisusb->urbstatus[i] & SU_URB_BUSY)
+ usb_kill_urb(sisusb->sisurbout[i]);
+
+ }
+}
+
+/* Return 1 if ok, 0 if error (not all complete within timeout) */
+static int sisusb_wait_all_out_complete(struct sisusb_usb_data *sisusb)
+{
+ int timeout = 5 * HZ, i = 1;
+
+ wait_event_timeout(sisusb->wait_q, (i = sisusb_all_free(sisusb)),
+ timeout);
+
+ return i;
+}
+
+static int sisusb_outurb_available(struct sisusb_usb_data *sisusb)
+{
+ int i;
+
+ for (i = 0; i < sisusb->numobufs; i++) {
+
+ if ((sisusb->urbstatus[i] & (SU_URB_BUSY|SU_URB_ALLOC)) == 0)
+ return i;
+
+ }
+
+ return -1;
+}
+
+static int sisusb_get_free_outbuf(struct sisusb_usb_data *sisusb)
+{
+ int i, timeout = 5 * HZ;
+
+ wait_event_timeout(sisusb->wait_q,
+ ((i = sisusb_outurb_available(sisusb)) >= 0), timeout);
+
+ return i;
+}
+
+static int sisusb_alloc_outbuf(struct sisusb_usb_data *sisusb)
+{
+ int i;
+
+ i = sisusb_outurb_available(sisusb);
+
+ if (i >= 0)
+ sisusb->urbstatus[i] |= SU_URB_ALLOC;
+
+ return i;
+}
+
+static void sisusb_free_outbuf(struct sisusb_usb_data *sisusb, int index)
+{
+ if ((index >= 0) && (index < sisusb->numobufs))
+ sisusb->urbstatus[index] &= ~SU_URB_ALLOC;
+}
+
+/* completion callback */
+
+static void sisusb_bulk_completeout(struct urb *urb)
+{
+ struct sisusb_urb_context *context = urb->context;
+ struct sisusb_usb_data *sisusb;
+
+ if (!context)
+ return;
+
+ sisusb = context->sisusb;
+
+ if (!sisusb || !sisusb->sisusb_dev || !sisusb->present)
+ return;
+
+#ifndef SISUSB_DONTSYNC
+ if (context->actual_length)
+ *(context->actual_length) += urb->actual_length;
+#endif
+
+ sisusb->urbstatus[context->urbindex] &= ~SU_URB_BUSY;
+ wake_up(&sisusb->wait_q);
+}
+
+static int sisusb_bulkout_msg(struct sisusb_usb_data *sisusb, int index,
+ unsigned int pipe, void *data, int len, int *actual_length,
+ int timeout, unsigned int tflags)
+{
+ struct urb *urb = sisusb->sisurbout[index];
+ int retval, byteswritten = 0;
+
+ /* Set up URB */
+ urb->transfer_flags = 0;
+
+ usb_fill_bulk_urb(urb, sisusb->sisusb_dev, pipe, data, len,
+ sisusb_bulk_completeout,
+ &sisusb->urbout_context[index]);
+
+ urb->transfer_flags |= tflags;
+ urb->actual_length = 0;
+
+ /* Set up context */
+ sisusb->urbout_context[index].actual_length = (timeout) ?
+ NULL : actual_length;
+
+ /* Declare this urb/buffer in use */
+ sisusb->urbstatus[index] |= SU_URB_BUSY;
+
+ /* Submit URB */
+ retval = usb_submit_urb(urb, GFP_KERNEL);
+
+ /* If OK, and if timeout > 0, wait for completion */
+ if ((retval == 0) && timeout) {
+ wait_event_timeout(sisusb->wait_q,
+ (!(sisusb->urbstatus[index] & SU_URB_BUSY)),
+ timeout);
+ if (sisusb->urbstatus[index] & SU_URB_BUSY) {
+ /* URB timed out... kill it and report error */
+ usb_kill_urb(urb);
+ retval = -ETIMEDOUT;
+ } else {
+ /* Otherwise, report urb status */
+ retval = urb->status;
+ byteswritten = urb->actual_length;
+ }
+ }
+
+ if (actual_length)
+ *actual_length = byteswritten;
+
+ return retval;
+}
+
+/* 2. in-bulks */
+
+/* completion callback */
+
+static void sisusb_bulk_completein(struct urb *urb)
+{
+ struct sisusb_usb_data *sisusb = urb->context;
+
+ if (!sisusb || !sisusb->sisusb_dev || !sisusb->present)
+ return;
+
+ sisusb->completein = 1;
+ wake_up(&sisusb->wait_q);
+}
+
+static int sisusb_bulkin_msg(struct sisusb_usb_data *sisusb,
+ unsigned int pipe, void *data, int len,
+ int *actual_length, int timeout, unsigned int tflags)
+{
+ struct urb *urb = sisusb->sisurbin;
+ int retval, readbytes = 0;
+
+ urb->transfer_flags = 0;
+
+ usb_fill_bulk_urb(urb, sisusb->sisusb_dev, pipe, data, len,
+ sisusb_bulk_completein, sisusb);
+
+ urb->transfer_flags |= tflags;
+ urb->actual_length = 0;
+
+ sisusb->completein = 0;
+ retval = usb_submit_urb(urb, GFP_KERNEL);
+ if (retval == 0) {
+ wait_event_timeout(sisusb->wait_q, sisusb->completein, timeout);
+ if (!sisusb->completein) {
+ /* URB timed out... kill it and report error */
+ usb_kill_urb(urb);
+ retval = -ETIMEDOUT;
+ } else {
+ /* URB completed within timeout */
+ retval = urb->status;
+ readbytes = urb->actual_length;
+ }
+ }
+
+ if (actual_length)
+ *actual_length = readbytes;
+
+ return retval;
+}
+
+
+/* Level 1: */
+
+/* Send a bulk message of variable size
+ *
+ * To copy the data from userspace, give pointer to "userbuffer",
+ * to copy from (non-DMA) kernel memory, give "kernbuffer". If
+ * both of these are NULL, it is assumed, that the transfer
+ * buffer "sisusb->obuf[index]" is set up with the data to send.
+ * Index is ignored if either kernbuffer or userbuffer is set.
+ * If async is nonzero, URBs will be sent without waiting for
+ * completion of the previous URB.
+ *
+ * (return 0 on success)
+ */
+
+static int sisusb_send_bulk_msg(struct sisusb_usb_data *sisusb, int ep, int len,
+ char *kernbuffer, const char __user *userbuffer, int index,
+ ssize_t *bytes_written, unsigned int tflags, int async)
+{
+ int result = 0, retry, count = len;
+ int passsize, thispass, transferred_len = 0;
+ int fromuser = (userbuffer != NULL) ? 1 : 0;
+ int fromkern = (kernbuffer != NULL) ? 1 : 0;
+ unsigned int pipe;
+ char *buffer;
+
+ (*bytes_written) = 0;
+
+ /* Sanity check */
+ if (!sisusb || !sisusb->present || !sisusb->sisusb_dev)
+ return -ENODEV;
+
+ /* If we copy data from kernel or userspace, force the
+ * allocation of a buffer/urb. If we have the data in
+ * the transfer buffer[index] already, reuse the buffer/URB
+ * if the length is > buffer size. (So, transmitting
+ * large data amounts directly from the transfer buffer
+ * treats the buffer as a ring buffer. However, we need
+ * to sync in this case.)
+ */
+ if (fromuser || fromkern)
+ index = -1;
+ else if (len > sisusb->obufsize)
+ async = 0;
+
+ pipe = usb_sndbulkpipe(sisusb->sisusb_dev, ep);
+
+ do {
+ passsize = thispass = (sisusb->obufsize < count) ?
+ sisusb->obufsize : count;
+
+ if (index < 0)
+ index = sisusb_get_free_outbuf(sisusb);
+
+ if (index < 0)
+ return -EIO;
+
+ buffer = sisusb->obuf[index];
+
+ if (fromuser) {
+
+ if (copy_from_user(buffer, userbuffer, passsize))
+ return -EFAULT;
+
+ userbuffer += passsize;
+
+ } else if (fromkern) {
+
+ memcpy(buffer, kernbuffer, passsize);
+ kernbuffer += passsize;
+
+ }
+
+ retry = 5;
+ while (thispass) {
+
+ if (!sisusb->sisusb_dev)
+ return -ENODEV;
+
+ result = sisusb_bulkout_msg(sisusb, index, pipe,
+ buffer, thispass, &transferred_len,
+ async ? 0 : 5 * HZ, tflags);
+
+ if (result == -ETIMEDOUT) {
+
+ /* Will not happen if async */
+ if (!retry--)
+ return -ETIME;
+
+ continue;
+ }
+
+ if ((result == 0) && !async && transferred_len) {
+
+ thispass -= transferred_len;
+ buffer += transferred_len;
+
+ } else
+ break;
+ }
+
+ if (result)
+ return result;
+
+ (*bytes_written) += passsize;
+ count -= passsize;
+
+ /* Force new allocation in next iteration */
+ if (fromuser || fromkern)
+ index = -1;
+
+ } while (count > 0);
+
+ if (async) {
+#ifdef SISUSB_DONTSYNC
+ (*bytes_written) = len;
+ /* Some URBs/buffers might be busy */
+#else
+ sisusb_wait_all_out_complete(sisusb);
+ (*bytes_written) = transferred_len;
+ /* All URBs and all buffers are available */
+#endif
+ }
+
+ return ((*bytes_written) == len) ? 0 : -EIO;
+}
+
+/* Receive a bulk message of variable size
+ *
+ * To copy the data to userspace, give pointer to "userbuffer",
+ * to copy to kernel memory, give "kernbuffer". One of them
+ * MUST be set. (There is no technique for letting the caller
+ * read directly from the ibuf.)
+ *
+ */
+
+static int sisusb_recv_bulk_msg(struct sisusb_usb_data *sisusb, int ep, int len,
+ void *kernbuffer, char __user *userbuffer, ssize_t *bytes_read,
+ unsigned int tflags)
+{
+ int result = 0, retry, count = len;
+ int bufsize, thispass, transferred_len;
+ unsigned int pipe;
+ char *buffer;
+
+ (*bytes_read) = 0;
+
+ /* Sanity check */
+ if (!sisusb || !sisusb->present || !sisusb->sisusb_dev)
+ return -ENODEV;
+
+ pipe = usb_rcvbulkpipe(sisusb->sisusb_dev, ep);
+ buffer = sisusb->ibuf;
+ bufsize = sisusb->ibufsize;
+
+ retry = 5;
+
+#ifdef SISUSB_DONTSYNC
+ if (!(sisusb_wait_all_out_complete(sisusb)))
+ return -EIO;
+#endif
+
+ while (count > 0) {
+
+ if (!sisusb->sisusb_dev)
+ return -ENODEV;
+
+ thispass = (bufsize < count) ? bufsize : count;
+
+ result = sisusb_bulkin_msg(sisusb, pipe, buffer, thispass,
+ &transferred_len, 5 * HZ, tflags);
+
+ if (transferred_len)
+ thispass = transferred_len;
+
+ else if (result == -ETIMEDOUT) {
+
+ if (!retry--)
+ return -ETIME;
+
+ continue;
+
+ } else
+ return -EIO;
+
+
+ if (thispass) {
+
+ (*bytes_read) += thispass;
+ count -= thispass;
+
+ if (userbuffer) {
+
+ if (copy_to_user(userbuffer, buffer, thispass))
+ return -EFAULT;
+
+ userbuffer += thispass;
+
+ } else {
+
+ memcpy(kernbuffer, buffer, thispass);
+ kernbuffer += thispass;
+
+ }
+
+ }
+
+ }
+
+ return ((*bytes_read) == len) ? 0 : -EIO;
+}
+
+static int sisusb_send_packet(struct sisusb_usb_data *sisusb, int len,
+ struct sisusb_packet *packet)
+{
+ int ret;
+ ssize_t bytes_transferred = 0;
+ __le32 tmp;
+
+ if (len == 6)
+ packet->data = 0;
+
+#ifdef SISUSB_DONTSYNC
+ if (!(sisusb_wait_all_out_complete(sisusb)))
+ return 1;
+#endif
+
+ /* Eventually correct endianness */
+ SISUSB_CORRECT_ENDIANNESS_PACKET(packet);
+
+ /* 1. send the packet */
+ ret = sisusb_send_bulk_msg(sisusb, SISUSB_EP_GFX_OUT, len,
+ (char *)packet, NULL, 0, &bytes_transferred, 0, 0);
+
+ if ((ret == 0) && (len == 6)) {
+
+ /* 2. if packet len == 6, it means we read, so wait for 32bit
+ * return value and write it to packet->data
+ */
+ ret = sisusb_recv_bulk_msg(sisusb, SISUSB_EP_GFX_IN, 4,
+ (char *)&tmp, NULL, &bytes_transferred, 0);
+
+ packet->data = le32_to_cpu(tmp);
+ }
+
+ return ret;
+}
+
+static int sisusb_send_bridge_packet(struct sisusb_usb_data *sisusb, int len,
+ struct sisusb_packet *packet, unsigned int tflags)
+{
+ int ret;
+ ssize_t bytes_transferred = 0;
+ __le32 tmp;
+
+ if (len == 6)
+ packet->data = 0;
+
+#ifdef SISUSB_DONTSYNC
+ if (!(sisusb_wait_all_out_complete(sisusb)))
+ return 1;
+#endif
+
+ /* Eventually correct endianness */
+ SISUSB_CORRECT_ENDIANNESS_PACKET(packet);
+
+ /* 1. send the packet */
+ ret = sisusb_send_bulk_msg(sisusb, SISUSB_EP_BRIDGE_OUT, len,
+ (char *)packet, NULL, 0, &bytes_transferred, tflags, 0);
+
+ if ((ret == 0) && (len == 6)) {
+
+ /* 2. if packet len == 6, it means we read, so wait for 32bit
+ * return value and write it to packet->data
+ */
+ ret = sisusb_recv_bulk_msg(sisusb, SISUSB_EP_BRIDGE_IN, 4,
+ (char *)&tmp, NULL, &bytes_transferred, 0);
+
+ packet->data = le32_to_cpu(tmp);
+ }
+
+ return ret;
+}
+
+/* access video memory and mmio (return 0 on success) */
+
+/* Low level */
+
+/* The following routines assume being used to transfer byte, word,
+ * long etc.
+ * This means that
+ * - the write routines expect "data" in machine endianness format.
+ * The data will be converted to leXX in sisusb_xxx_packet.
+ * - the read routines can expect read data in machine-endianess.
+ */
+
+static int sisusb_write_memio_byte(struct sisusb_usb_data *sisusb, int type,
+ u32 addr, u8 data)
+{
+ struct sisusb_packet packet;
+
+ packet.header = (1 << (addr & 3)) | (type << 6);
+ packet.address = addr & ~3;
+ packet.data = data << ((addr & 3) << 3);
+ return sisusb_send_packet(sisusb, 10, &packet);
+}
+
+static int sisusb_write_memio_word(struct sisusb_usb_data *sisusb, int type,
+ u32 addr, u16 data)
+{
+ struct sisusb_packet packet;
+ int ret = 0;
+
+ packet.address = addr & ~3;
+
+ switch (addr & 3) {
+ case 0:
+ packet.header = (type << 6) | 0x0003;
+ packet.data = (u32)data;
+ ret = sisusb_send_packet(sisusb, 10, &packet);
+ break;
+ case 1:
+ packet.header = (type << 6) | 0x0006;
+ packet.data = (u32)data << 8;
+ ret = sisusb_send_packet(sisusb, 10, &packet);
+ break;
+ case 2:
+ packet.header = (type << 6) | 0x000c;
+ packet.data = (u32)data << 16;
+ ret = sisusb_send_packet(sisusb, 10, &packet);
+ break;
+ case 3:
+ packet.header = (type << 6) | 0x0008;
+ packet.data = (u32)data << 24;
+ ret = sisusb_send_packet(sisusb, 10, &packet);
+ packet.header = (type << 6) | 0x0001;
+ packet.address = (addr & ~3) + 4;
+ packet.data = (u32)data >> 8;
+ ret |= sisusb_send_packet(sisusb, 10, &packet);
+ }
+
+ return ret;
+}
+
+static int sisusb_write_memio_24bit(struct sisusb_usb_data *sisusb, int type,
+ u32 addr, u32 data)
+{
+ struct sisusb_packet packet;
+ int ret = 0;
+
+ packet.address = addr & ~3;
+
+ switch (addr & 3) {
+ case 0:
+ packet.header = (type << 6) | 0x0007;
+ packet.data = data & 0x00ffffff;
+ ret = sisusb_send_packet(sisusb, 10, &packet);
+ break;
+ case 1:
+ packet.header = (type << 6) | 0x000e;
+ packet.data = data << 8;
+ ret = sisusb_send_packet(sisusb, 10, &packet);
+ break;
+ case 2:
+ packet.header = (type << 6) | 0x000c;
+ packet.data = data << 16;
+ ret = sisusb_send_packet(sisusb, 10, &packet);
+ packet.header = (type << 6) | 0x0001;
+ packet.address = (addr & ~3) + 4;
+ packet.data = (data >> 16) & 0x00ff;
+ ret |= sisusb_send_packet(sisusb, 10, &packet);
+ break;
+ case 3:
+ packet.header = (type << 6) | 0x0008;
+ packet.data = data << 24;
+ ret = sisusb_send_packet(sisusb, 10, &packet);
+ packet.header = (type << 6) | 0x0003;
+ packet.address = (addr & ~3) + 4;
+ packet.data = (data >> 8) & 0xffff;
+ ret |= sisusb_send_packet(sisusb, 10, &packet);
+ }
+
+ return ret;
+}
+
+static int sisusb_write_memio_long(struct sisusb_usb_data *sisusb, int type,
+ u32 addr, u32 data)
+{
+ struct sisusb_packet packet;
+ int ret = 0;
+
+ packet.address = addr & ~3;
+
+ switch (addr & 3) {
+ case 0:
+ packet.header = (type << 6) | 0x000f;
+ packet.data = data;
+ ret = sisusb_send_packet(sisusb, 10, &packet);
+ break;
+ case 1:
+ packet.header = (type << 6) | 0x000e;
+ packet.data = data << 8;
+ ret = sisusb_send_packet(sisusb, 10, &packet);
+ packet.header = (type << 6) | 0x0001;
+ packet.address = (addr & ~3) + 4;
+ packet.data = data >> 24;
+ ret |= sisusb_send_packet(sisusb, 10, &packet);
+ break;
+ case 2:
+ packet.header = (type << 6) | 0x000c;
+ packet.data = data << 16;
+ ret = sisusb_send_packet(sisusb, 10, &packet);
+ packet.header = (type << 6) | 0x0003;
+ packet.address = (addr & ~3) + 4;
+ packet.data = data >> 16;
+ ret |= sisusb_send_packet(sisusb, 10, &packet);
+ break;
+ case 3:
+ packet.header = (type << 6) | 0x0008;
+ packet.data = data << 24;
+ ret = sisusb_send_packet(sisusb, 10, &packet);
+ packet.header = (type << 6) | 0x0007;
+ packet.address = (addr & ~3) + 4;
+ packet.data = data >> 8;
+ ret |= sisusb_send_packet(sisusb, 10, &packet);
+ }
+
+ return ret;
+}
+
+/* The xxx_bulk routines copy a buffer of variable size. They treat the
+ * buffer as chars, therefore lsb/msb has to be corrected if using the
+ * byte/word/long/etc routines for speed-up
+ *
+ * If data is from userland, set "userbuffer" (and clear "kernbuffer"),
+ * if data is in kernel space, set "kernbuffer" (and clear "userbuffer");
+ * if neither "kernbuffer" nor "userbuffer" are given, it is assumed
+ * that the data already is in the transfer buffer "sisusb->obuf[index]".
+ */
+
+static int sisusb_write_mem_bulk(struct sisusb_usb_data *sisusb, u32 addr,
+ char *kernbuffer, int length, const char __user *userbuffer,
+ int index, ssize_t *bytes_written)
+{
+ struct sisusb_packet packet;
+ int ret = 0;
+ static int msgcount;
+ u8 swap8, fromkern = kernbuffer ? 1 : 0;
+ u16 swap16;
+ u32 swap32, flag = (length >> 28) & 1;
+ u8 buf[4];
+
+ /* if neither kernbuffer not userbuffer are given, assume
+ * data in obuf
+ */
+ if (!fromkern && !userbuffer)
+ kernbuffer = sisusb->obuf[index];
+
+ (*bytes_written = 0);
+
+ length &= 0x00ffffff;
+
+ while (length) {
+ switch (length) {
+ case 1:
+ if (userbuffer) {
+ if (get_user(swap8, (u8 __user *)userbuffer))
+ return -EFAULT;
+ } else
+ swap8 = kernbuffer[0];
+
+ ret = sisusb_write_memio_byte(sisusb, SISUSB_TYPE_MEM,
+ addr, swap8);
+
+ if (!ret)
+ (*bytes_written)++;
+
+ return ret;
+
+ case 2:
+ if (userbuffer) {
+ if (get_user(swap16, (u16 __user *)userbuffer))
+ return -EFAULT;
+ } else
+ swap16 = *((u16 *)kernbuffer);
+
+ ret = sisusb_write_memio_word(sisusb, SISUSB_TYPE_MEM,
+ addr, swap16);
+
+ if (!ret)
+ (*bytes_written) += 2;
+
+ return ret;
+
+ case 3:
+ if (userbuffer) {
+ if (copy_from_user(&buf, userbuffer, 3))
+ return -EFAULT;
+#ifdef __BIG_ENDIAN
+ swap32 = (buf[0] << 16) |
+ (buf[1] << 8) |
+ buf[2];
+#else
+ swap32 = (buf[2] << 16) |
+ (buf[1] << 8) |
+ buf[0];
+#endif
+ } else
+#ifdef __BIG_ENDIAN
+ swap32 = (kernbuffer[0] << 16) |
+ (kernbuffer[1] << 8) |
+ kernbuffer[2];
+#else
+ swap32 = (kernbuffer[2] << 16) |
+ (kernbuffer[1] << 8) |
+ kernbuffer[0];
+#endif
+
+ ret = sisusb_write_memio_24bit(sisusb, SISUSB_TYPE_MEM,
+ addr, swap32);
+
+ if (!ret)
+ (*bytes_written) += 3;
+
+ return ret;
+
+ case 4:
+ if (userbuffer) {
+ if (get_user(swap32, (u32 __user *)userbuffer))
+ return -EFAULT;
+ } else
+ swap32 = *((u32 *)kernbuffer);
+
+ ret = sisusb_write_memio_long(sisusb, SISUSB_TYPE_MEM,
+ addr, swap32);
+ if (!ret)
+ (*bytes_written) += 4;
+
+ return ret;
+
+ default:
+ if ((length & ~3) > 0x10000) {
+
+ packet.header = 0x001f;
+ packet.address = 0x000001d4;
+ packet.data = addr;
+ ret = sisusb_send_bridge_packet(sisusb, 10,
+ &packet, 0);
+ packet.header = 0x001f;
+ packet.address = 0x000001d0;
+ packet.data = (length & ~3);
+ ret |= sisusb_send_bridge_packet(sisusb, 10,
+ &packet, 0);
+ packet.header = 0x001f;
+ packet.address = 0x000001c0;
+ packet.data = flag | 0x16;
+ ret |= sisusb_send_bridge_packet(sisusb, 10,
+ &packet, 0);
+ if (userbuffer) {
+ ret |= sisusb_send_bulk_msg(sisusb,
+ SISUSB_EP_GFX_LBULK_OUT,
+ (length & ~3),
+ NULL, userbuffer, 0,
+ bytes_written, 0, 1);
+ userbuffer += (*bytes_written);
+ } else if (fromkern) {
+ ret |= sisusb_send_bulk_msg(sisusb,
+ SISUSB_EP_GFX_LBULK_OUT,
+ (length & ~3),
+ kernbuffer, NULL, 0,
+ bytes_written, 0, 1);
+ kernbuffer += (*bytes_written);
+ } else {
+ ret |= sisusb_send_bulk_msg(sisusb,
+ SISUSB_EP_GFX_LBULK_OUT,
+ (length & ~3),
+ NULL, NULL, index,
+ bytes_written, 0, 1);
+ kernbuffer += ((*bytes_written) &
+ (sisusb->obufsize-1));
+ }
+
+ } else {
+
+ packet.header = 0x001f;
+ packet.address = 0x00000194;
+ packet.data = addr;
+ ret = sisusb_send_bridge_packet(sisusb, 10,
+ &packet, 0);
+ packet.header = 0x001f;
+ packet.address = 0x00000190;
+ packet.data = (length & ~3);
+ ret |= sisusb_send_bridge_packet(sisusb, 10,
+ &packet, 0);
+ if (sisusb->flagb0 != 0x16) {
+ packet.header = 0x001f;
+ packet.address = 0x00000180;
+ packet.data = flag | 0x16;
+ ret |= sisusb_send_bridge_packet(sisusb,
+ 10, &packet, 0);
+ sisusb->flagb0 = 0x16;
+ }
+ if (userbuffer) {
+ ret |= sisusb_send_bulk_msg(sisusb,
+ SISUSB_EP_GFX_BULK_OUT,
+ (length & ~3),
+ NULL, userbuffer, 0,
+ bytes_written, 0, 1);
+ userbuffer += (*bytes_written);
+ } else if (fromkern) {
+ ret |= sisusb_send_bulk_msg(sisusb,
+ SISUSB_EP_GFX_BULK_OUT,
+ (length & ~3),
+ kernbuffer, NULL, 0,
+ bytes_written, 0, 1);
+ kernbuffer += (*bytes_written);
+ } else {
+ ret |= sisusb_send_bulk_msg(sisusb,
+ SISUSB_EP_GFX_BULK_OUT,
+ (length & ~3),
+ NULL, NULL, index,
+ bytes_written, 0, 1);
+ kernbuffer += ((*bytes_written) &
+ (sisusb->obufsize-1));
+ }
+ }
+ if (ret) {
+ msgcount++;
+ if (msgcount < 500)
+ dev_err(&sisusb->sisusb_dev->dev,
+ "Wrote %zd of %d bytes, error %d\n",
+ *bytes_written, length,
+ ret);
+ else if (msgcount == 500)
+ dev_err(&sisusb->sisusb_dev->dev,
+ "Too many errors, logging stopped\n");
+ }
+ addr += (*bytes_written);
+ length -= (*bytes_written);
+ }
+
+ if (ret)
+ break;
+
+ }
+
+ return ret ? -EIO : 0;
+}
+
+/* Remember: Read data in packet is in machine-endianess! So for
+ * byte, word, 24bit, long no endian correction is necessary.
+ */
+
+static int sisusb_read_memio_byte(struct sisusb_usb_data *sisusb, int type,
+ u32 addr, u8 *data)
+{
+ struct sisusb_packet packet;
+ int ret;
+
+ CLEARPACKET(&packet);
+ packet.header = (1 << (addr & 3)) | (type << 6);
+ packet.address = addr & ~3;
+ ret = sisusb_send_packet(sisusb, 6, &packet);
+ *data = (u8)(packet.data >> ((addr & 3) << 3));
+ return ret;
+}
+
+static int sisusb_read_memio_word(struct sisusb_usb_data *sisusb, int type,
+ u32 addr, u16 *data)
+{
+ struct sisusb_packet packet;
+ int ret = 0;
+
+ CLEARPACKET(&packet);
+
+ packet.address = addr & ~3;
+
+ switch (addr & 3) {
+ case 0:
+ packet.header = (type << 6) | 0x0003;
+ ret = sisusb_send_packet(sisusb, 6, &packet);
+ *data = (u16)(packet.data);
+ break;
+ case 1:
+ packet.header = (type << 6) | 0x0006;
+ ret = sisusb_send_packet(sisusb, 6, &packet);
+ *data = (u16)(packet.data >> 8);
+ break;
+ case 2:
+ packet.header = (type << 6) | 0x000c;
+ ret = sisusb_send_packet(sisusb, 6, &packet);
+ *data = (u16)(packet.data >> 16);
+ break;
+ case 3:
+ packet.header = (type << 6) | 0x0008;
+ ret = sisusb_send_packet(sisusb, 6, &packet);
+ *data = (u16)(packet.data >> 24);
+ packet.header = (type << 6) | 0x0001;
+ packet.address = (addr & ~3) + 4;
+ ret |= sisusb_send_packet(sisusb, 6, &packet);
+ *data |= (u16)(packet.data << 8);
+ }
+
+ return ret;
+}
+
+static int sisusb_read_memio_24bit(struct sisusb_usb_data *sisusb, int type,
+ u32 addr, u32 *data)
+{
+ struct sisusb_packet packet;
+ int ret = 0;
+
+ packet.address = addr & ~3;
+
+ switch (addr & 3) {
+ case 0:
+ packet.header = (type << 6) | 0x0007;
+ ret = sisusb_send_packet(sisusb, 6, &packet);
+ *data = packet.data & 0x00ffffff;
+ break;
+ case 1:
+ packet.header = (type << 6) | 0x000e;
+ ret = sisusb_send_packet(sisusb, 6, &packet);
+ *data = packet.data >> 8;
+ break;
+ case 2:
+ packet.header = (type << 6) | 0x000c;
+ ret = sisusb_send_packet(sisusb, 6, &packet);
+ *data = packet.data >> 16;
+ packet.header = (type << 6) | 0x0001;
+ packet.address = (addr & ~3) + 4;
+ ret |= sisusb_send_packet(sisusb, 6, &packet);
+ *data |= ((packet.data & 0xff) << 16);
+ break;
+ case 3:
+ packet.header = (type << 6) | 0x0008;
+ ret = sisusb_send_packet(sisusb, 6, &packet);
+ *data = packet.data >> 24;
+ packet.header = (type << 6) | 0x0003;
+ packet.address = (addr & ~3) + 4;
+ ret |= sisusb_send_packet(sisusb, 6, &packet);
+ *data |= ((packet.data & 0xffff) << 8);
+ }
+
+ return ret;
+}
+
+static int sisusb_read_memio_long(struct sisusb_usb_data *sisusb, int type,
+ u32 addr, u32 *data)
+{
+ struct sisusb_packet packet;
+ int ret = 0;
+
+ packet.address = addr & ~3;
+
+ switch (addr & 3) {
+ case 0:
+ packet.header = (type << 6) | 0x000f;
+ ret = sisusb_send_packet(sisusb, 6, &packet);
+ *data = packet.data;
+ break;
+ case 1:
+ packet.header = (type << 6) | 0x000e;
+ ret = sisusb_send_packet(sisusb, 6, &packet);
+ *data = packet.data >> 8;
+ packet.header = (type << 6) | 0x0001;
+ packet.address = (addr & ~3) + 4;
+ ret |= sisusb_send_packet(sisusb, 6, &packet);
+ *data |= (packet.data << 24);
+ break;
+ case 2:
+ packet.header = (type << 6) | 0x000c;
+ ret = sisusb_send_packet(sisusb, 6, &packet);
+ *data = packet.data >> 16;
+ packet.header = (type << 6) | 0x0003;
+ packet.address = (addr & ~3) + 4;
+ ret |= sisusb_send_packet(sisusb, 6, &packet);
+ *data |= (packet.data << 16);
+ break;
+ case 3:
+ packet.header = (type << 6) | 0x0008;
+ ret = sisusb_send_packet(sisusb, 6, &packet);
+ *data = packet.data >> 24;
+ packet.header = (type << 6) | 0x0007;
+ packet.address = (addr & ~3) + 4;
+ ret |= sisusb_send_packet(sisusb, 6, &packet);
+ *data |= (packet.data << 8);
+ }
+
+ return ret;
+}
+
+static int sisusb_read_mem_bulk(struct sisusb_usb_data *sisusb, u32 addr,
+ char *kernbuffer, int length, char __user *userbuffer,
+ ssize_t *bytes_read)
+{
+ int ret = 0;
+ char buf[4];
+ u16 swap16;
+ u32 swap32;
+
+ (*bytes_read = 0);
+
+ length &= 0x00ffffff;
+
+ while (length) {
+ switch (length) {
+ case 1:
+ ret |= sisusb_read_memio_byte(sisusb, SISUSB_TYPE_MEM,
+ addr, &buf[0]);
+ if (!ret) {
+ (*bytes_read)++;
+ if (userbuffer) {
+ if (put_user(buf[0], (u8 __user *)userbuffer))
+ return -EFAULT;
+ } else
+ kernbuffer[0] = buf[0];
+ }
+ return ret;
+
+ case 2:
+ ret |= sisusb_read_memio_word(sisusb, SISUSB_TYPE_MEM,
+ addr, &swap16);
+ if (!ret) {
+ (*bytes_read) += 2;
+ if (userbuffer) {
+ if (put_user(swap16, (u16 __user *)userbuffer))
+ return -EFAULT;
+ } else {
+ *((u16 *)kernbuffer) = swap16;
+ }
+ }
+ return ret;
+
+ case 3:
+ ret |= sisusb_read_memio_24bit(sisusb, SISUSB_TYPE_MEM,
+ addr, &swap32);
+ if (!ret) {
+ (*bytes_read) += 3;
+#ifdef __BIG_ENDIAN
+ buf[0] = (swap32 >> 16) & 0xff;
+ buf[1] = (swap32 >> 8) & 0xff;
+ buf[2] = swap32 & 0xff;
+#else
+ buf[2] = (swap32 >> 16) & 0xff;
+ buf[1] = (swap32 >> 8) & 0xff;
+ buf[0] = swap32 & 0xff;
+#endif
+ if (userbuffer) {
+ if (copy_to_user(userbuffer,
+ &buf[0], 3))
+ return -EFAULT;
+ } else {
+ kernbuffer[0] = buf[0];
+ kernbuffer[1] = buf[1];
+ kernbuffer[2] = buf[2];
+ }
+ }
+ return ret;
+
+ default:
+ ret |= sisusb_read_memio_long(sisusb, SISUSB_TYPE_MEM,
+ addr, &swap32);
+ if (!ret) {
+ (*bytes_read) += 4;
+ if (userbuffer) {
+ if (put_user(swap32, (u32 __user *)userbuffer))
+ return -EFAULT;
+
+ userbuffer += 4;
+ } else {
+ *((u32 *)kernbuffer) = swap32;
+ kernbuffer += 4;
+ }
+ addr += 4;
+ length -= 4;
+ }
+ }
+ if (ret)
+ break;
+ }
+
+ return ret;
+}
+
+/* High level: Gfx (indexed) register access */
+
+#ifdef CONFIG_USB_SISUSBVGA_CON
+int sisusb_setreg(struct sisusb_usb_data *sisusb, u32 port, u8 data)
+{
+ return sisusb_write_memio_byte(sisusb, SISUSB_TYPE_IO, port, data);
+}
+
+int sisusb_getreg(struct sisusb_usb_data *sisusb, u32 port, u8 *data)
+{
+ return sisusb_read_memio_byte(sisusb, SISUSB_TYPE_IO, port, data);
+}
+#endif
+
+int sisusb_setidxreg(struct sisusb_usb_data *sisusb, u32 port,
+ u8 index, u8 data)
+{
+ int ret;
+
+ ret = sisusb_write_memio_byte(sisusb, SISUSB_TYPE_IO, port, index);
+ ret |= sisusb_write_memio_byte(sisusb, SISUSB_TYPE_IO, port + 1, data);
+ return ret;
+}
+
+int sisusb_getidxreg(struct sisusb_usb_data *sisusb, u32 port,
+ u8 index, u8 *data)
+{
+ int ret;
+
+ ret = sisusb_write_memio_byte(sisusb, SISUSB_TYPE_IO, port, index);
+ ret |= sisusb_read_memio_byte(sisusb, SISUSB_TYPE_IO, port + 1, data);
+ return ret;
+}
+
+int sisusb_setidxregandor(struct sisusb_usb_data *sisusb, u32 port, u8 idx,
+ u8 myand, u8 myor)
+{
+ int ret;
+ u8 tmp;
+
+ ret = sisusb_write_memio_byte(sisusb, SISUSB_TYPE_IO, port, idx);
+ ret |= sisusb_read_memio_byte(sisusb, SISUSB_TYPE_IO, port + 1, &tmp);
+ tmp &= myand;
+ tmp |= myor;
+ ret |= sisusb_write_memio_byte(sisusb, SISUSB_TYPE_IO, port + 1, tmp);
+ return ret;
+}
+
+static int sisusb_setidxregmask(struct sisusb_usb_data *sisusb,
+ u32 port, u8 idx, u8 data, u8 mask)
+{
+ int ret;
+ u8 tmp;
+
+ ret = sisusb_write_memio_byte(sisusb, SISUSB_TYPE_IO, port, idx);
+ ret |= sisusb_read_memio_byte(sisusb, SISUSB_TYPE_IO, port + 1, &tmp);
+ tmp &= ~(mask);
+ tmp |= (data & mask);
+ ret |= sisusb_write_memio_byte(sisusb, SISUSB_TYPE_IO, port + 1, tmp);
+ return ret;
+}
+
+int sisusb_setidxregor(struct sisusb_usb_data *sisusb, u32 port,
+ u8 index, u8 myor)
+{
+ return sisusb_setidxregandor(sisusb, port, index, 0xff, myor);
+}
+
+int sisusb_setidxregand(struct sisusb_usb_data *sisusb, u32 port,
+ u8 idx, u8 myand)
+{
+ return sisusb_setidxregandor(sisusb, port, idx, myand, 0x00);
+}
+
+/* Write/read video ram */
+
+#ifdef CONFIG_USB_SISUSBVGA_CON
+int sisusb_writeb(struct sisusb_usb_data *sisusb, u32 adr, u8 data)
+{
+ return sisusb_write_memio_byte(sisusb, SISUSB_TYPE_MEM, adr, data);
+}
+
+int sisusb_readb(struct sisusb_usb_data *sisusb, u32 adr, u8 *data)
+{
+ return sisusb_read_memio_byte(sisusb, SISUSB_TYPE_MEM, adr, data);
+}
+
+int sisusb_copy_memory(struct sisusb_usb_data *sisusb, u8 *src,
+ u32 dest, int length)
+{
+ size_t dummy;
+
+ return sisusb_write_mem_bulk(sisusb, dest, src, length,
+ NULL, 0, &dummy);
+}
+
+#ifdef SISUSBENDIANTEST
+static int sisusb_read_memory(struct sisusb_usb_data *sisusb, char *dest,
+ u32 src, int length)
+{
+ size_t dummy;
+
+ return sisusb_read_mem_bulk(sisusb, src, dest, length,
+ NULL, &dummy);
+}
+#endif
+#endif
+
+#ifdef SISUSBENDIANTEST
+static void sisusb_testreadwrite(struct sisusb_usb_data *sisusb)
+{
+ static u8 srcbuffer[] = { 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77 };
+ char destbuffer[10];
+ int i, j;
+
+ sisusb_copy_memory(sisusb, srcbuffer, sisusb->vrambase, 7);
+
+ for (i = 1; i <= 7; i++) {
+ dev_dbg(&sisusb->sisusb_dev->dev,
+ "sisusb: rwtest %d bytes\n", i);
+ sisusb_read_memory(sisusb, destbuffer, sisusb->vrambase, i);
+ for (j = 0; j < i; j++) {
+ dev_dbg(&sisusb->sisusb_dev->dev,
+ "rwtest read[%d] = %x\n",
+ j, destbuffer[j]);
+ }
+ }
+}
+#endif
+
+/* access pci config registers (reg numbers 0, 4, 8, etc) */
+
+static int sisusb_write_pci_config(struct sisusb_usb_data *sisusb,
+ int regnum, u32 data)
+{
+ struct sisusb_packet packet;
+
+ packet.header = 0x008f;
+ packet.address = regnum | 0x10000;
+ packet.data = data;
+ return sisusb_send_packet(sisusb, 10, &packet);
+}
+
+static int sisusb_read_pci_config(struct sisusb_usb_data *sisusb,
+ int regnum, u32 *data)
+{
+ struct sisusb_packet packet;
+ int ret;
+
+ packet.header = 0x008f;
+ packet.address = (u32)regnum | 0x10000;
+ ret = sisusb_send_packet(sisusb, 6, &packet);
+ *data = packet.data;
+ return ret;
+}
+
+/* Clear video RAM */
+
+static int sisusb_clear_vram(struct sisusb_usb_data *sisusb,
+ u32 address, int length)
+{
+ int ret, i;
+ ssize_t j;
+
+ if (address < sisusb->vrambase)
+ return 1;
+
+ if (address >= sisusb->vrambase + sisusb->vramsize)
+ return 1;
+
+ if (address + length > sisusb->vrambase + sisusb->vramsize)
+ length = sisusb->vrambase + sisusb->vramsize - address;
+
+ if (length <= 0)
+ return 0;
+
+ /* allocate free buffer/urb and clear the buffer */
+ i = sisusb_alloc_outbuf(sisusb);
+ if (i < 0)
+ return -EBUSY;
+
+ memset(sisusb->obuf[i], 0, sisusb->obufsize);
+
+ /* We can write a length > buffer size here. The buffer
+ * data will simply be re-used (like a ring-buffer).
+ */
+ ret = sisusb_write_mem_bulk(sisusb, address, NULL, length, NULL, i, &j);
+
+ /* Free the buffer/urb */
+ sisusb_free_outbuf(sisusb, i);
+
+ return ret;
+}
+
+/* Initialize the graphics core (return 0 on success)
+ * This resets the graphics hardware and puts it into
+ * a defined mode (640x480@60Hz)
+ */
+
+#define GETREG(r, d) sisusb_read_memio_byte(sisusb, SISUSB_TYPE_IO, r, d)
+#define SETREG(r, d) sisusb_write_memio_byte(sisusb, SISUSB_TYPE_IO, r, d)
+#define SETIREG(r, i, d) sisusb_setidxreg(sisusb, r, i, d)
+#define GETIREG(r, i, d) sisusb_getidxreg(sisusb, r, i, d)
+#define SETIREGOR(r, i, o) sisusb_setidxregor(sisusb, r, i, o)
+#define SETIREGAND(r, i, a) sisusb_setidxregand(sisusb, r, i, a)
+#define SETIREGANDOR(r, i, a, o) sisusb_setidxregandor(sisusb, r, i, a, o)
+#define READL(a, d) sisusb_read_memio_long(sisusb, SISUSB_TYPE_MEM, a, d)
+#define WRITEL(a, d) sisusb_write_memio_long(sisusb, SISUSB_TYPE_MEM, a, d)
+#define READB(a, d) sisusb_read_memio_byte(sisusb, SISUSB_TYPE_MEM, a, d)
+#define WRITEB(a, d) sisusb_write_memio_byte(sisusb, SISUSB_TYPE_MEM, a, d)
+
+static int sisusb_triggersr16(struct sisusb_usb_data *sisusb, u8 ramtype)
+{
+ int ret;
+ u8 tmp8;
+
+ ret = GETIREG(SISSR, 0x16, &tmp8);
+ if (ramtype <= 1) {
+ tmp8 &= 0x3f;
+ ret |= SETIREG(SISSR, 0x16, tmp8);
+ tmp8 |= 0x80;
+ ret |= SETIREG(SISSR, 0x16, tmp8);
+ } else {
+ tmp8 |= 0xc0;
+ ret |= SETIREG(SISSR, 0x16, tmp8);
+ tmp8 &= 0x0f;
+ ret |= SETIREG(SISSR, 0x16, tmp8);
+ tmp8 |= 0x80;
+ ret |= SETIREG(SISSR, 0x16, tmp8);
+ tmp8 &= 0x0f;
+ ret |= SETIREG(SISSR, 0x16, tmp8);
+ tmp8 |= 0xd0;
+ ret |= SETIREG(SISSR, 0x16, tmp8);
+ tmp8 &= 0x0f;
+ ret |= SETIREG(SISSR, 0x16, tmp8);
+ tmp8 |= 0xa0;
+ ret |= SETIREG(SISSR, 0x16, tmp8);
+ }
+ return ret;
+}
+
+static int sisusb_getbuswidth(struct sisusb_usb_data *sisusb,
+ int *bw, int *chab)
+{
+ int ret;
+ u8 ramtype, done = 0;
+ u32 t0, t1, t2, t3;
+ u32 ramptr = SISUSB_PCI_MEMBASE;
+
+ ret = GETIREG(SISSR, 0x3a, &ramtype);
+ ramtype &= 3;
+
+ ret |= SETIREG(SISSR, 0x13, 0x00);
+
+ if (ramtype <= 1) {
+ ret |= SETIREG(SISSR, 0x14, 0x12);
+ ret |= SETIREGAND(SISSR, 0x15, 0xef);
+ } else {
+ ret |= SETIREG(SISSR, 0x14, 0x02);
+ }
+
+ ret |= sisusb_triggersr16(sisusb, ramtype);
+ ret |= WRITEL(ramptr + 0, 0x01234567);
+ ret |= WRITEL(ramptr + 4, 0x456789ab);
+ ret |= WRITEL(ramptr + 8, 0x89abcdef);
+ ret |= WRITEL(ramptr + 12, 0xcdef0123);
+ ret |= WRITEL(ramptr + 16, 0x55555555);
+ ret |= WRITEL(ramptr + 20, 0x55555555);
+ ret |= WRITEL(ramptr + 24, 0xffffffff);
+ ret |= WRITEL(ramptr + 28, 0xffffffff);
+ ret |= READL(ramptr + 0, &t0);
+ ret |= READL(ramptr + 4, &t1);
+ ret |= READL(ramptr + 8, &t2);
+ ret |= READL(ramptr + 12, &t3);
+
+ if (ramtype <= 1) {
+
+ *chab = 0; *bw = 64;
+
+ if ((t3 != 0xcdef0123) || (t2 != 0x89abcdef)) {
+ if ((t1 == 0x456789ab) && (t0 == 0x01234567)) {
+ *chab = 0; *bw = 64;
+ ret |= SETIREGAND(SISSR, 0x14, 0xfd);
+ }
+ }
+ if ((t1 != 0x456789ab) || (t0 != 0x01234567)) {
+ *chab = 1; *bw = 64;
+ ret |= SETIREGANDOR(SISSR, 0x14, 0xfc, 0x01);
+
+ ret |= sisusb_triggersr16(sisusb, ramtype);
+ ret |= WRITEL(ramptr + 0, 0x89abcdef);
+ ret |= WRITEL(ramptr + 4, 0xcdef0123);
+ ret |= WRITEL(ramptr + 8, 0x55555555);
+ ret |= WRITEL(ramptr + 12, 0x55555555);
+ ret |= WRITEL(ramptr + 16, 0xaaaaaaaa);
+ ret |= WRITEL(ramptr + 20, 0xaaaaaaaa);
+ ret |= READL(ramptr + 4, &t1);
+
+ if (t1 != 0xcdef0123) {
+ *bw = 32;
+ ret |= SETIREGOR(SISSR, 0x15, 0x10);
+ }
+ }
+
+ } else {
+
+ *chab = 0; *bw = 64; /* default: cha, bw = 64 */
+
+ done = 0;
+
+ if (t1 == 0x456789ab) {
+ if (t0 == 0x01234567) {
+ *chab = 0; *bw = 64;
+ done = 1;
+ }
+ } else {
+ if (t0 == 0x01234567) {
+ *chab = 0; *bw = 32;
+ ret |= SETIREG(SISSR, 0x14, 0x00);
+ done = 1;
+ }
+ }
+
+ if (!done) {
+ ret |= SETIREG(SISSR, 0x14, 0x03);
+ ret |= sisusb_triggersr16(sisusb, ramtype);
+
+ ret |= WRITEL(ramptr + 0, 0x01234567);
+ ret |= WRITEL(ramptr + 4, 0x456789ab);
+ ret |= WRITEL(ramptr + 8, 0x89abcdef);
+ ret |= WRITEL(ramptr + 12, 0xcdef0123);
+ ret |= WRITEL(ramptr + 16, 0x55555555);
+ ret |= WRITEL(ramptr + 20, 0x55555555);
+ ret |= WRITEL(ramptr + 24, 0xffffffff);
+ ret |= WRITEL(ramptr + 28, 0xffffffff);
+ ret |= READL(ramptr + 0, &t0);
+ ret |= READL(ramptr + 4, &t1);
+
+ if (t1 == 0x456789ab) {
+ if (t0 == 0x01234567) {
+ *chab = 1; *bw = 64;
+ return ret;
+ } /* else error */
+ } else {
+ if (t0 == 0x01234567) {
+ *chab = 1; *bw = 32;
+ ret |= SETIREG(SISSR, 0x14, 0x01);
+ } /* else error */
+ }
+ }
+ }
+ return ret;
+}
+
+static int sisusb_verify_mclk(struct sisusb_usb_data *sisusb)
+{
+ int ret = 0;
+ u32 ramptr = SISUSB_PCI_MEMBASE;
+ u8 tmp1, tmp2, i, j;
+
+ ret |= WRITEB(ramptr, 0xaa);
+ ret |= WRITEB(ramptr + 16, 0x55);
+ ret |= READB(ramptr, &tmp1);
+ ret |= READB(ramptr + 16, &tmp2);
+ if ((tmp1 != 0xaa) || (tmp2 != 0x55)) {
+ for (i = 0, j = 16; i < 2; i++, j += 16) {
+ ret |= GETIREG(SISSR, 0x21, &tmp1);
+ ret |= SETIREGAND(SISSR, 0x21, (tmp1 & 0xfb));
+ ret |= SETIREGOR(SISSR, 0x3c, 0x01); /* not on 330 */
+ ret |= SETIREGAND(SISSR, 0x3c, 0xfe); /* not on 330 */
+ ret |= SETIREG(SISSR, 0x21, tmp1);
+ ret |= WRITEB(ramptr + 16 + j, j);
+ ret |= READB(ramptr + 16 + j, &tmp1);
+ if (tmp1 == j) {
+ ret |= WRITEB(ramptr + j, j);
+ break;
+ }
+ }
+ }
+ return ret;
+}
+
+static int sisusb_set_rank(struct sisusb_usb_data *sisusb, int *iret,
+ int index, u8 rankno, u8 chab, const u8 dramtype[][5], int bw)
+{
+ int ret = 0, ranksize;
+ u8 tmp;
+
+ *iret = 0;
+
+ if ((rankno == 2) && (dramtype[index][0] == 2))
+ return ret;
+
+ ranksize = dramtype[index][3] / 2 * bw / 32;
+
+ if ((ranksize * rankno) > 128)
+ return ret;
+
+ tmp = 0;
+ while ((ranksize >>= 1) > 0)
+ tmp += 0x10;
+
+ tmp |= ((rankno - 1) << 2);
+ tmp |= ((bw / 64) & 0x02);
+ tmp |= (chab & 0x01);
+
+ ret = SETIREG(SISSR, 0x14, tmp);
+ ret |= sisusb_triggersr16(sisusb, 0); /* sic! */
+
+ *iret = 1;
+
+ return ret;
+}
+
+static int sisusb_check_rbc(struct sisusb_usb_data *sisusb, int *iret,
+ u32 inc, int testn)
+{
+ int ret = 0, i;
+ u32 j, tmp;
+
+ *iret = 0;
+
+ for (i = 0, j = 0; i < testn; i++) {
+ ret |= WRITEL(sisusb->vrambase + j, j);
+ j += inc;
+ }
+
+ for (i = 0, j = 0; i < testn; i++) {
+ ret |= READL(sisusb->vrambase + j, &tmp);
+ if (tmp != j)
+ return ret;
+
+ j += inc;
+ }
+
+ *iret = 1;
+ return ret;
+}
+
+static int sisusb_check_ranks(struct sisusb_usb_data *sisusb,
+ int *iret, int rankno, int idx, int bw, const u8 rtype[][5])
+{
+ int ret = 0, i, i2ret;
+ u32 inc;
+
+ *iret = 0;
+
+ for (i = rankno; i >= 1; i--) {
+ inc = 1 << (rtype[idx][2] + rtype[idx][1] + rtype[idx][0] +
+ bw / 64 + i);
+ ret |= sisusb_check_rbc(sisusb, &i2ret, inc, 2);
+ if (!i2ret)
+ return ret;
+ }
+
+ inc = 1 << (rtype[idx][2] + bw / 64 + 2);
+ ret |= sisusb_check_rbc(sisusb, &i2ret, inc, 4);
+ if (!i2ret)
+ return ret;
+
+ inc = 1 << (10 + bw / 64);
+ ret |= sisusb_check_rbc(sisusb, &i2ret, inc, 2);
+ if (!i2ret)
+ return ret;
+
+ *iret = 1;
+ return ret;
+}
+
+static int sisusb_get_sdram_size(struct sisusb_usb_data *sisusb, int *iret,
+ int bw, int chab)
+{
+ int ret = 0, i2ret = 0, i, j;
+ static const u8 sdramtype[13][5] = {
+ { 2, 12, 9, 64, 0x35 },
+ { 1, 13, 9, 64, 0x44 },
+ { 2, 12, 8, 32, 0x31 },
+ { 2, 11, 9, 32, 0x25 },
+ { 1, 12, 9, 32, 0x34 },
+ { 1, 13, 8, 32, 0x40 },
+ { 2, 11, 8, 16, 0x21 },
+ { 1, 12, 8, 16, 0x30 },
+ { 1, 11, 9, 16, 0x24 },
+ { 1, 11, 8, 8, 0x20 },
+ { 2, 9, 8, 4, 0x01 },
+ { 1, 10, 8, 4, 0x10 },
+ { 1, 9, 8, 2, 0x00 }
+ };
+
+ *iret = 1; /* error */
+
+ for (i = 0; i < 13; i++) {
+ ret |= SETIREGANDOR(SISSR, 0x13, 0x80, sdramtype[i][4]);
+ for (j = 2; j > 0; j--) {
+ ret |= sisusb_set_rank(sisusb, &i2ret, i, j, chab,
+ sdramtype, bw);
+ if (!i2ret)
+ continue;
+
+ ret |= sisusb_check_ranks(sisusb, &i2ret, j, i, bw,
+ sdramtype);
+ if (i2ret) {
+ *iret = 0; /* ram size found */
+ return ret;
+ }
+ }
+ }
+
+ return ret;
+}
+
+static int sisusb_setup_screen(struct sisusb_usb_data *sisusb,
+ int clrall, int drwfr)
+{
+ int ret = 0;
+ u32 address;
+ int i, length, modex, modey, bpp;
+
+ modex = 640; modey = 480; bpp = 2;
+
+ address = sisusb->vrambase; /* Clear video ram */
+
+ if (clrall)
+ length = sisusb->vramsize;
+ else
+ length = modex * bpp * modey;
+
+ ret = sisusb_clear_vram(sisusb, address, length);
+
+ if (!ret && drwfr) {
+ for (i = 0; i < modex; i++) {
+ address = sisusb->vrambase + (i * bpp);
+ ret |= sisusb_write_memio_word(sisusb, SISUSB_TYPE_MEM,
+ address, 0xf100);
+ address += (modex * (modey-1) * bpp);
+ ret |= sisusb_write_memio_word(sisusb, SISUSB_TYPE_MEM,
+ address, 0xf100);
+ }
+ for (i = 0; i < modey; i++) {
+ address = sisusb->vrambase + ((i * modex) * bpp);
+ ret |= sisusb_write_memio_word(sisusb, SISUSB_TYPE_MEM,
+ address, 0xf100);
+ address += ((modex - 1) * bpp);
+ ret |= sisusb_write_memio_word(sisusb, SISUSB_TYPE_MEM,
+ address, 0xf100);
+ }
+ }
+
+ return ret;
+}
+
+static void sisusb_set_default_mode(struct sisusb_usb_data *sisusb,
+ int touchengines)
+{
+ int i, j, modex, bpp, du;
+ u8 sr31, cr63, tmp8;
+ static const char attrdata[] = {
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+ 0x01, 0x00, 0x00, 0x00
+ };
+ static const char crtcrdata[] = {
+ 0x5f, 0x4f, 0x50, 0x82, 0x54, 0x80, 0x0b, 0x3e,
+ 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xea, 0x8c, 0xdf, 0x28, 0x40, 0xe7, 0x04, 0xa3,
+ 0xff
+ };
+ static const char grcdata[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x05, 0x0f,
+ 0xff
+ };
+ static const char crtcdata[] = {
+ 0x5f, 0x4f, 0x4f, 0x83, 0x55, 0x81, 0x0b, 0x3e,
+ 0xe9, 0x8b, 0xdf, 0xe8, 0x0c, 0x00, 0x00, 0x05,
+ 0x00
+ };
+
+ modex = 640; bpp = 2;
+
+ GETIREG(SISSR, 0x31, &sr31);
+ GETIREG(SISCR, 0x63, &cr63);
+ SETIREGOR(SISSR, 0x01, 0x20);
+ SETIREG(SISCR, 0x63, cr63 & 0xbf);
+ SETIREGOR(SISCR, 0x17, 0x80);
+ SETIREGOR(SISSR, 0x1f, 0x04);
+ SETIREGAND(SISSR, 0x07, 0xfb);
+ SETIREG(SISSR, 0x00, 0x03); /* seq */
+ SETIREG(SISSR, 0x01, 0x21);
+ SETIREG(SISSR, 0x02, 0x0f);
+ SETIREG(SISSR, 0x03, 0x00);
+ SETIREG(SISSR, 0x04, 0x0e);
+ SETREG(SISMISCW, 0x23); /* misc */
+ for (i = 0; i <= 0x18; i++) { /* crtc */
+ SETIREG(SISCR, i, crtcrdata[i]);
+ }
+ for (i = 0; i <= 0x13; i++) { /* att */
+ GETREG(SISINPSTAT, &tmp8);
+ SETREG(SISAR, i);
+ SETREG(SISAR, attrdata[i]);
+ }
+ GETREG(SISINPSTAT, &tmp8);
+ SETREG(SISAR, 0x14);
+ SETREG(SISAR, 0x00);
+ GETREG(SISINPSTAT, &tmp8);
+ SETREG(SISAR, 0x20);
+ GETREG(SISINPSTAT, &tmp8);
+ for (i = 0; i <= 0x08; i++) { /* grc */
+ SETIREG(SISGR, i, grcdata[i]);
+ }
+ SETIREGAND(SISGR, 0x05, 0xbf);
+ for (i = 0x0A; i <= 0x0E; i++) { /* clr ext */
+ SETIREG(SISSR, i, 0x00);
+ }
+ SETIREGAND(SISSR, 0x37, 0xfe);
+ SETREG(SISMISCW, 0xef); /* sync */
+ SETIREG(SISCR, 0x11, 0x00); /* crtc */
+ for (j = 0x00, i = 0; i <= 7; i++, j++)
+ SETIREG(SISCR, j, crtcdata[i]);
+
+ for (j = 0x10; i <= 10; i++, j++)
+ SETIREG(SISCR, j, crtcdata[i]);
+
+ for (j = 0x15; i <= 12; i++, j++)
+ SETIREG(SISCR, j, crtcdata[i]);
+
+ for (j = 0x0A; i <= 15; i++, j++)
+ SETIREG(SISSR, j, crtcdata[i]);
+
+ SETIREG(SISSR, 0x0E, (crtcdata[16] & 0xE0));
+ SETIREGANDOR(SISCR, 0x09, 0x5f, ((crtcdata[16] & 0x01) << 5));
+ SETIREG(SISCR, 0x14, 0x4f);
+ du = (modex / 16) * (bpp * 2); /* offset/pitch */
+ SETIREGANDOR(SISSR, 0x0e, 0xf0, ((du >> 8) & 0x0f));
+ SETIREG(SISCR, 0x13, (du & 0xff));
+ du <<= 5;
+ tmp8 = du >> 8;
+ SETIREG(SISSR, 0x10, tmp8);
+ SETIREG(SISSR, 0x31, 0x00); /* VCLK */
+ SETIREG(SISSR, 0x2b, 0x1b);
+ SETIREG(SISSR, 0x2c, 0xe1);
+ SETIREG(SISSR, 0x2d, 0x01);
+ SETIREGAND(SISSR, 0x3d, 0xfe); /* FIFO */
+ SETIREG(SISSR, 0x08, 0xae);
+ SETIREGAND(SISSR, 0x09, 0xf0);
+ SETIREG(SISSR, 0x08, 0x34);
+ SETIREGOR(SISSR, 0x3d, 0x01);
+ SETIREGAND(SISSR, 0x1f, 0x3f); /* mode regs */
+ SETIREGANDOR(SISSR, 0x06, 0xc0, 0x0a);
+ SETIREG(SISCR, 0x19, 0x00);
+ SETIREGAND(SISCR, 0x1a, 0xfc);
+ SETIREGAND(SISSR, 0x0f, 0xb7);
+ SETIREGAND(SISSR, 0x31, 0xfb);
+ SETIREGANDOR(SISSR, 0x21, 0x1f, 0xa0);
+ SETIREGAND(SISSR, 0x32, 0xf3);
+ SETIREGANDOR(SISSR, 0x07, 0xf8, 0x03);
+ SETIREG(SISCR, 0x52, 0x6c);
+
+ SETIREG(SISCR, 0x0d, 0x00); /* adjust frame */
+ SETIREG(SISCR, 0x0c, 0x00);
+ SETIREG(SISSR, 0x0d, 0x00);
+ SETIREGAND(SISSR, 0x37, 0xfe);
+
+ SETIREG(SISCR, 0x32, 0x20);
+ SETIREGAND(SISSR, 0x01, 0xdf); /* enable display */
+ SETIREG(SISCR, 0x63, (cr63 & 0xbf));
+ SETIREG(SISSR, 0x31, (sr31 & 0xfb));
+
+ if (touchengines) {
+ SETIREG(SISSR, 0x20, 0xa1); /* enable engines */
+ SETIREGOR(SISSR, 0x1e, 0x5a);
+
+ SETIREG(SISSR, 0x26, 0x01); /* disable cmdqueue */
+ SETIREG(SISSR, 0x27, 0x1f);
+ SETIREG(SISSR, 0x26, 0x00);
+ }
+
+ SETIREG(SISCR, 0x34, 0x44); /* we just set std mode #44 */
+}
+
+static int sisusb_init_gfxcore(struct sisusb_usb_data *sisusb)
+{
+ int ret = 0, i, j, bw, chab, iret, retry = 3;
+ u8 tmp8, ramtype;
+ u32 tmp32;
+ static const char mclktable[] = {
+ 0x3b, 0x22, 0x01, 143,
+ 0x3b, 0x22, 0x01, 143,
+ 0x3b, 0x22, 0x01, 143,
+ 0x3b, 0x22, 0x01, 143
+ };
+ static const char eclktable[] = {
+ 0x3b, 0x22, 0x01, 143,
+ 0x3b, 0x22, 0x01, 143,
+ 0x3b, 0x22, 0x01, 143,
+ 0x3b, 0x22, 0x01, 143
+ };
+ static const char ramtypetable1[] = {
+ 0x00, 0x04, 0x60, 0x60,
+ 0x0f, 0x0f, 0x1f, 0x1f,
+ 0xba, 0xba, 0xba, 0xba,
+ 0xa9, 0xa9, 0xac, 0xac,
+ 0xa0, 0xa0, 0xa0, 0xa8,
+ 0x00, 0x00, 0x02, 0x02,
+ 0x30, 0x30, 0x40, 0x40
+ };
+ static const char ramtypetable2[] = {
+ 0x77, 0x77, 0x44, 0x44,
+ 0x77, 0x77, 0x44, 0x44,
+ 0x00, 0x00, 0x00, 0x00,
+ 0x5b, 0x5b, 0xab, 0xab,
+ 0x00, 0x00, 0xf0, 0xf8
+ };
+
+ while (retry--) {
+
+ /* Enable VGA */
+ ret = GETREG(SISVGAEN, &tmp8);
+ ret |= SETREG(SISVGAEN, (tmp8 | 0x01));
+
+ /* Enable GPU access to VRAM */
+ ret |= GETREG(SISMISCR, &tmp8);
+ ret |= SETREG(SISMISCW, (tmp8 | 0x01));
+
+ if (ret)
+ continue;
+
+ /* Reset registers */
+ ret |= SETIREGAND(SISCR, 0x5b, 0xdf);
+ ret |= SETIREG(SISSR, 0x05, 0x86);
+ ret |= SETIREGOR(SISSR, 0x20, 0x01);
+
+ ret |= SETREG(SISMISCW, 0x67);
+
+ for (i = 0x06; i <= 0x1f; i++)
+ ret |= SETIREG(SISSR, i, 0x00);
+
+ for (i = 0x21; i <= 0x27; i++)
+ ret |= SETIREG(SISSR, i, 0x00);
+
+ for (i = 0x31; i <= 0x3d; i++)
+ ret |= SETIREG(SISSR, i, 0x00);
+
+ for (i = 0x12; i <= 0x1b; i++)
+ ret |= SETIREG(SISSR, i, 0x00);
+
+ for (i = 0x79; i <= 0x7c; i++)
+ ret |= SETIREG(SISCR, i, 0x00);
+
+ if (ret)
+ continue;
+
+ ret |= SETIREG(SISCR, 0x63, 0x80);
+
+ ret |= GETIREG(SISSR, 0x3a, &ramtype);
+ ramtype &= 0x03;
+
+ ret |= SETIREG(SISSR, 0x28, mclktable[ramtype * 4]);
+ ret |= SETIREG(SISSR, 0x29, mclktable[(ramtype * 4) + 1]);
+ ret |= SETIREG(SISSR, 0x2a, mclktable[(ramtype * 4) + 2]);
+
+ ret |= SETIREG(SISSR, 0x2e, eclktable[ramtype * 4]);
+ ret |= SETIREG(SISSR, 0x2f, eclktable[(ramtype * 4) + 1]);
+ ret |= SETIREG(SISSR, 0x30, eclktable[(ramtype * 4) + 2]);
+
+ ret |= SETIREG(SISSR, 0x07, 0x18);
+ ret |= SETIREG(SISSR, 0x11, 0x0f);
+
+ if (ret)
+ continue;
+
+ for (i = 0x15, j = 0; i <= 0x1b; i++, j++) {
+ ret |= SETIREG(SISSR, i,
+ ramtypetable1[(j*4) + ramtype]);
+ }
+ for (i = 0x40, j = 0; i <= 0x44; i++, j++) {
+ ret |= SETIREG(SISCR, i,
+ ramtypetable2[(j*4) + ramtype]);
+ }
+
+ ret |= SETIREG(SISCR, 0x49, 0xaa);
+
+ ret |= SETIREG(SISSR, 0x1f, 0x00);
+ ret |= SETIREG(SISSR, 0x20, 0xa0);
+ ret |= SETIREG(SISSR, 0x23, 0xf6);
+ ret |= SETIREG(SISSR, 0x24, 0x0d);
+ ret |= SETIREG(SISSR, 0x25, 0x33);
+
+ ret |= SETIREG(SISSR, 0x11, 0x0f);
+
+ ret |= SETIREGOR(SISPART1, 0x2f, 0x01);
+
+ ret |= SETIREGAND(SISCAP, 0x3f, 0xef);
+
+ if (ret)
+ continue;
+
+ ret |= SETIREG(SISPART1, 0x00, 0x00);
+
+ ret |= GETIREG(SISSR, 0x13, &tmp8);
+ tmp8 >>= 4;
+
+ ret |= SETIREG(SISPART1, 0x02, 0x00);
+ ret |= SETIREG(SISPART1, 0x2e, 0x08);
+
+ ret |= sisusb_read_pci_config(sisusb, 0x50, &tmp32);
+ tmp32 &= 0x00f00000;
+ tmp8 = (tmp32 == 0x100000) ? 0x33 : 0x03;
+ ret |= SETIREG(SISSR, 0x25, tmp8);
+ tmp8 = (tmp32 == 0x100000) ? 0xaa : 0x88;
+ ret |= SETIREG(SISCR, 0x49, tmp8);
+
+ ret |= SETIREG(SISSR, 0x27, 0x1f);
+ ret |= SETIREG(SISSR, 0x31, 0x00);
+ ret |= SETIREG(SISSR, 0x32, 0x11);
+ ret |= SETIREG(SISSR, 0x33, 0x00);
+
+ if (ret)
+ continue;
+
+ ret |= SETIREG(SISCR, 0x83, 0x00);
+
+ sisusb_set_default_mode(sisusb, 0);
+
+ ret |= SETIREGAND(SISSR, 0x21, 0xdf);
+ ret |= SETIREGOR(SISSR, 0x01, 0x20);
+ ret |= SETIREGOR(SISSR, 0x16, 0x0f);
+
+ ret |= sisusb_triggersr16(sisusb, ramtype);
+
+ /* Disable refresh */
+ ret |= SETIREGAND(SISSR, 0x17, 0xf8);
+ ret |= SETIREGOR(SISSR, 0x19, 0x03);
+
+ ret |= sisusb_getbuswidth(sisusb, &bw, &chab);
+ ret |= sisusb_verify_mclk(sisusb);
+
+ if (ramtype <= 1) {
+ ret |= sisusb_get_sdram_size(sisusb, &iret, bw, chab);
+ if (iret) {
+ dev_err(&sisusb->sisusb_dev->dev,
+ "RAM size detection failed, assuming 8MB video RAM\n");
+ ret |= SETIREG(SISSR, 0x14, 0x31);
+ /* TODO */
+ }
+ } else {
+ dev_err(&sisusb->sisusb_dev->dev,
+ "DDR RAM device found, assuming 8MB video RAM\n");
+ ret |= SETIREG(SISSR, 0x14, 0x31);
+ /* *** TODO *** */
+ }
+
+ /* Enable refresh */
+ ret |= SETIREG(SISSR, 0x16, ramtypetable1[4 + ramtype]);
+ ret |= SETIREG(SISSR, 0x17, ramtypetable1[8 + ramtype]);
+ ret |= SETIREG(SISSR, 0x19, ramtypetable1[16 + ramtype]);
+
+ ret |= SETIREGOR(SISSR, 0x21, 0x20);
+
+ ret |= SETIREG(SISSR, 0x22, 0xfb);
+ ret |= SETIREG(SISSR, 0x21, 0xa5);
+
+ if (ret == 0)
+ break;
+ }
+
+ return ret;
+}
+
+#undef SETREG
+#undef GETREG
+#undef SETIREG
+#undef GETIREG
+#undef SETIREGOR
+#undef SETIREGAND
+#undef SETIREGANDOR
+#undef READL
+#undef WRITEL
+
+static void sisusb_get_ramconfig(struct sisusb_usb_data *sisusb)
+{
+ u8 tmp8, tmp82, ramtype;
+ int bw = 0;
+ char *ramtypetext1 = NULL;
+ static const char ram_datarate[4] = {'S', 'S', 'D', 'D'};
+ static const char ram_dynamictype[4] = {'D', 'G', 'D', 'G'};
+ static const int busSDR[4] = {64, 64, 128, 128};
+ static const int busDDR[4] = {32, 32, 64, 64};
+ static const int busDDRA[4] = {64+32, 64+32, (64+32)*2, (64+32)*2};
+
+ sisusb_getidxreg(sisusb, SISSR, 0x14, &tmp8);
+ sisusb_getidxreg(sisusb, SISSR, 0x15, &tmp82);
+ sisusb_getidxreg(sisusb, SISSR, 0x3a, &ramtype);
+ sisusb->vramsize = (1 << ((tmp8 & 0xf0) >> 4)) * 1024 * 1024;
+ ramtype &= 0x03;
+ switch ((tmp8 >> 2) & 0x03) {
+ case 0:
+ ramtypetext1 = "1 ch/1 r";
+ if (tmp82 & 0x10)
+ bw = 32;
+ else
+ bw = busSDR[(tmp8 & 0x03)];
+
+ break;
+ case 1:
+ ramtypetext1 = "1 ch/2 r";
+ sisusb->vramsize <<= 1;
+ bw = busSDR[(tmp8 & 0x03)];
+ break;
+ case 2:
+ ramtypetext1 = "asymmetric";
+ sisusb->vramsize += sisusb->vramsize/2;
+ bw = busDDRA[(tmp8 & 0x03)];
+ break;
+ case 3:
+ ramtypetext1 = "2 channel";
+ sisusb->vramsize <<= 1;
+ bw = busDDR[(tmp8 & 0x03)];
+ break;
+ }
+
+ dev_info(&sisusb->sisusb_dev->dev,
+ "%dMB %s %cDR S%cRAM, bus width %d\n",
+ sisusb->vramsize >> 20, ramtypetext1,
+ ram_datarate[ramtype], ram_dynamictype[ramtype], bw);
+}
+
+static int sisusb_do_init_gfxdevice(struct sisusb_usb_data *sisusb)
+{
+ struct sisusb_packet packet;
+ int ret;
+ u32 tmp32;
+
+ /* Do some magic */
+ packet.header = 0x001f;
+ packet.address = 0x00000324;
+ packet.data = 0x00000004;
+ ret = sisusb_send_bridge_packet(sisusb, 10, &packet, 0);
+
+ packet.header = 0x001f;
+ packet.address = 0x00000364;
+ packet.data = 0x00000004;
+ ret |= sisusb_send_bridge_packet(sisusb, 10, &packet, 0);
+
+ packet.header = 0x001f;
+ packet.address = 0x00000384;
+ packet.data = 0x00000004;
+ ret |= sisusb_send_bridge_packet(sisusb, 10, &packet, 0);
+
+ packet.header = 0x001f;
+ packet.address = 0x00000100;
+ packet.data = 0x00000700;
+ ret |= sisusb_send_bridge_packet(sisusb, 10, &packet, 0);
+
+ packet.header = 0x000f;
+ packet.address = 0x00000004;
+ ret |= sisusb_send_bridge_packet(sisusb, 6, &packet, 0);
+ packet.data |= 0x17;
+ ret |= sisusb_send_bridge_packet(sisusb, 10, &packet, 0);
+
+ /* Init BAR 0 (VRAM) */
+ ret |= sisusb_read_pci_config(sisusb, 0x10, &tmp32);
+ ret |= sisusb_write_pci_config(sisusb, 0x10, 0xfffffff0);
+ ret |= sisusb_read_pci_config(sisusb, 0x10, &tmp32);
+ tmp32 &= 0x0f;
+ tmp32 |= SISUSB_PCI_MEMBASE;
+ ret |= sisusb_write_pci_config(sisusb, 0x10, tmp32);
+
+ /* Init BAR 1 (MMIO) */
+ ret |= sisusb_read_pci_config(sisusb, 0x14, &tmp32);
+ ret |= sisusb_write_pci_config(sisusb, 0x14, 0xfffffff0);
+ ret |= sisusb_read_pci_config(sisusb, 0x14, &tmp32);
+ tmp32 &= 0x0f;
+ tmp32 |= SISUSB_PCI_MMIOBASE;
+ ret |= sisusb_write_pci_config(sisusb, 0x14, tmp32);
+
+ /* Init BAR 2 (i/o ports) */
+ ret |= sisusb_read_pci_config(sisusb, 0x18, &tmp32);
+ ret |= sisusb_write_pci_config(sisusb, 0x18, 0xfffffff0);
+ ret |= sisusb_read_pci_config(sisusb, 0x18, &tmp32);
+ tmp32 &= 0x0f;
+ tmp32 |= SISUSB_PCI_IOPORTBASE;
+ ret |= sisusb_write_pci_config(sisusb, 0x18, tmp32);
+
+ /* Enable memory and i/o access */
+ ret |= sisusb_read_pci_config(sisusb, 0x04, &tmp32);
+ tmp32 |= 0x3;
+ ret |= sisusb_write_pci_config(sisusb, 0x04, tmp32);
+
+ if (ret == 0) {
+ /* Some further magic */
+ packet.header = 0x001f;
+ packet.address = 0x00000050;
+ packet.data = 0x000000ff;
+ ret |= sisusb_send_bridge_packet(sisusb, 10, &packet, 0);
+ }
+
+ return ret;
+}
+
+/* Initialize the graphics device (return 0 on success)
+ * This initializes the net2280 as well as the PCI registers
+ * of the graphics board.
+ */
+
+static int sisusb_init_gfxdevice(struct sisusb_usb_data *sisusb, int initscreen)
+{
+ int ret = 0, test = 0;
+ u32 tmp32;
+
+ if (sisusb->devinit == 1) {
+ /* Read PCI BARs and see if they have been set up */
+ ret |= sisusb_read_pci_config(sisusb, 0x10, &tmp32);
+ if (ret)
+ return ret;
+
+ if ((tmp32 & 0xfffffff0) == SISUSB_PCI_MEMBASE)
+ test++;
+
+ ret |= sisusb_read_pci_config(sisusb, 0x14, &tmp32);
+ if (ret)
+ return ret;
+
+ if ((tmp32 & 0xfffffff0) == SISUSB_PCI_MMIOBASE)
+ test++;
+
+ ret |= sisusb_read_pci_config(sisusb, 0x18, &tmp32);
+ if (ret)
+ return ret;
+
+ if ((tmp32 & 0xfffffff0) == SISUSB_PCI_IOPORTBASE)
+ test++;
+ }
+
+ /* No? So reset the device */
+ if ((sisusb->devinit == 0) || (test != 3)) {
+
+ ret |= sisusb_do_init_gfxdevice(sisusb);
+
+ if (ret == 0)
+ sisusb->devinit = 1;
+
+ }
+
+ if (sisusb->devinit) {
+ /* Initialize the graphics core */
+ if (sisusb_init_gfxcore(sisusb) == 0) {
+ sisusb->gfxinit = 1;
+ sisusb_get_ramconfig(sisusb);
+ sisusb_set_default_mode(sisusb, 1);
+ ret |= sisusb_setup_screen(sisusb, 1, initscreen);
+ }
+ }
+
+ return ret;
+}
+
+
+#ifdef CONFIG_USB_SISUSBVGA_CON
+
+/* Set up default text mode:
+ * - Set text mode (0x03)
+ * - Upload default font
+ * - Upload user font (if available)
+ */
+
+int sisusb_reset_text_mode(struct sisusb_usb_data *sisusb, int init)
+{
+ int ret = 0, slot = sisusb->font_slot, i;
+ const struct font_desc *myfont;
+ u8 *tempbuf;
+ u16 *tempbufb;
+ static const char bootstring[] =
+ "SiSUSB VGA text console, (C) 2005 Thomas Winischhofer.";
+ static const char bootlogo[] = "(o_ //\\ V_/_";
+
+ /* sisusb->lock is down */
+
+ if (!sisusb->SiS_Pr)
+ return 1;
+
+ sisusb->SiS_Pr->IOAddress = SISUSB_PCI_IOPORTBASE + 0x30;
+ sisusb->SiS_Pr->sisusb = (void *)sisusb;
+
+ /* Set mode 0x03 */
+ SiSUSBSetMode(sisusb->SiS_Pr, 0x03);
+
+ myfont = find_font("VGA8x16");
+ if (!myfont)
+ return 1;
+
+ tempbuf = vmalloc(8192);
+ if (!tempbuf)
+ return 1;
+
+ for (i = 0; i < 256; i++)
+ memcpy(tempbuf + (i * 32), myfont->data + (i * 16), 16);
+
+ /* Upload default font */
+ ret = sisusbcon_do_font_op(sisusb, 1, 0, tempbuf, 8192,
+ 0, 1, NULL, 16, 0);
+
+ vfree(tempbuf);
+
+ /* Upload user font (and reset current slot) */
+ if (sisusb->font_backup) {
+ ret |= sisusbcon_do_font_op(sisusb, 1, 2, sisusb->font_backup,
+ 8192, sisusb->font_backup_512, 1, NULL,
+ sisusb->font_backup_height, 0);
+ if (slot != 2)
+ sisusbcon_do_font_op(sisusb, 1, 0, NULL, 0, 0, 1,
+ NULL, 16, 0);
+ }
+
+ if (init && !sisusb->scrbuf) {
+
+ tempbuf = vmalloc(8192);
+ if (tempbuf) {
+
+ i = 4096;
+ tempbufb = (u16 *)tempbuf;
+ while (i--)
+ *(tempbufb++) = 0x0720;
+
+ i = 0;
+ tempbufb = (u16 *)tempbuf;
+ while (bootlogo[i]) {
+ *(tempbufb++) = 0x0700 | bootlogo[i++];
+ if (!(i % 4))
+ tempbufb += 76;
+ }
+
+ i = 0;
+ tempbufb = (u16 *)tempbuf + 6;
+ while (bootstring[i])
+ *(tempbufb++) = 0x0700 | bootstring[i++];
+
+ ret |= sisusb_copy_memory(sisusb, tempbuf,
+ sisusb->vrambase, 8192);
+
+ vfree(tempbuf);
+
+ }
+
+ } else if (sisusb->scrbuf) {
+ ret |= sisusb_copy_memory(sisusb, (u8 *)sisusb->scrbuf,
+ sisusb->vrambase, sisusb->scrbuf_size);
+ }
+
+ if (sisusb->sisusb_cursor_size_from >= 0 &&
+ sisusb->sisusb_cursor_size_to >= 0) {
+ sisusb_setidxreg(sisusb, SISCR, 0x0a,
+ sisusb->sisusb_cursor_size_from);
+ sisusb_setidxregandor(sisusb, SISCR, 0x0b, 0xe0,
+ sisusb->sisusb_cursor_size_to);
+ } else {
+ sisusb_setidxreg(sisusb, SISCR, 0x0a, 0x2d);
+ sisusb_setidxreg(sisusb, SISCR, 0x0b, 0x0e);
+ sisusb->sisusb_cursor_size_to = -1;
+ }
+
+ slot = sisusb->sisusb_cursor_loc;
+ if (slot < 0)
+ slot = 0;
+
+ sisusb->sisusb_cursor_loc = -1;
+ sisusb->bad_cursor_pos = 1;
+
+ sisusb_set_cursor(sisusb, slot);
+
+ sisusb_setidxreg(sisusb, SISCR, 0x0c, (sisusb->cur_start_addr >> 8));
+ sisusb_setidxreg(sisusb, SISCR, 0x0d, (sisusb->cur_start_addr & 0xff));
+
+ sisusb->textmodedestroyed = 0;
+
+ /* sisusb->lock is down */
+
+ return ret;
+}
+
+#endif
+
+/* fops */
+
+static int sisusb_open(struct inode *inode, struct file *file)
+{
+ struct sisusb_usb_data *sisusb;
+ struct usb_interface *interface;
+ int subminor = iminor(inode);
+
+ interface = usb_find_interface(&sisusb_driver, subminor);
+ if (!interface)
+ return -ENODEV;
+
+ sisusb = usb_get_intfdata(interface);
+ if (!sisusb)
+ return -ENODEV;
+
+ mutex_lock(&sisusb->lock);
+
+ if (!sisusb->present || !sisusb->ready) {
+ mutex_unlock(&sisusb->lock);
+ return -ENODEV;
+ }
+
+ if (sisusb->isopen) {
+ mutex_unlock(&sisusb->lock);
+ return -EBUSY;
+ }
+
+ if (!sisusb->devinit) {
+ if (sisusb->sisusb_dev->speed == USB_SPEED_HIGH ||
+ sisusb->sisusb_dev->speed >= USB_SPEED_SUPER) {
+ if (sisusb_init_gfxdevice(sisusb, 0)) {
+ mutex_unlock(&sisusb->lock);
+ dev_err(&sisusb->sisusb_dev->dev,
+ "Failed to initialize device\n");
+ return -EIO;
+ }
+ } else {
+ mutex_unlock(&sisusb->lock);
+ dev_err(&sisusb->sisusb_dev->dev,
+ "Device not attached to USB 2.0 hub\n");
+ return -EIO;
+ }
+ }
+
+ /* Increment usage count for our sisusb */
+ kref_get(&sisusb->kref);
+
+ sisusb->isopen = 1;
+
+ file->private_data = sisusb;
+
+ mutex_unlock(&sisusb->lock);
+
+ return 0;
+}
+
+void sisusb_delete(struct kref *kref)
+{
+ struct sisusb_usb_data *sisusb = to_sisusb_dev(kref);
+
+ if (!sisusb)
+ return;
+
+ usb_put_dev(sisusb->sisusb_dev);
+
+ sisusb->sisusb_dev = NULL;
+ sisusb_free_buffers(sisusb);
+ sisusb_free_urbs(sisusb);
+#ifdef CONFIG_USB_SISUSBVGA_CON
+ kfree(sisusb->SiS_Pr);
+#endif
+ kfree(sisusb);
+}
+
+static int sisusb_release(struct inode *inode, struct file *file)
+{
+ struct sisusb_usb_data *sisusb;
+
+ sisusb = file->private_data;
+ if (!sisusb)
+ return -ENODEV;
+
+ mutex_lock(&sisusb->lock);
+
+ if (sisusb->present) {
+ /* Wait for all URBs to finish if device still present */
+ if (!sisusb_wait_all_out_complete(sisusb))
+ sisusb_kill_all_busy(sisusb);
+ }
+
+ sisusb->isopen = 0;
+ file->private_data = NULL;
+
+ mutex_unlock(&sisusb->lock);
+
+ /* decrement the usage count on our device */
+ kref_put(&sisusb->kref, sisusb_delete);
+
+ return 0;
+}
+
+static ssize_t sisusb_read(struct file *file, char __user *buffer,
+ size_t count, loff_t *ppos)
+{
+ struct sisusb_usb_data *sisusb;
+ ssize_t bytes_read = 0;
+ int errno = 0;
+ u8 buf8;
+ u16 buf16;
+ u32 buf32, address;
+
+ sisusb = file->private_data;
+ if (!sisusb)
+ return -ENODEV;
+
+ mutex_lock(&sisusb->lock);
+
+ /* Sanity check */
+ if (!sisusb->present || !sisusb->ready || !sisusb->sisusb_dev) {
+ mutex_unlock(&sisusb->lock);
+ return -ENODEV;
+ }
+
+ if ((*ppos) >= SISUSB_PCI_PSEUDO_IOPORTBASE &&
+ (*ppos) < SISUSB_PCI_PSEUDO_IOPORTBASE + 128) {
+
+ address = (*ppos) - SISUSB_PCI_PSEUDO_IOPORTBASE +
+ SISUSB_PCI_IOPORTBASE;
+
+ /* Read i/o ports
+ * Byte, word and long(32) can be read. As this
+ * emulates inX instructions, the data returned is
+ * in machine-endianness.
+ */
+ switch (count) {
+ case 1:
+ if (sisusb_read_memio_byte(sisusb, SISUSB_TYPE_IO,
+ address, &buf8))
+ errno = -EIO;
+ else if (put_user(buf8, (u8 __user *)buffer))
+ errno = -EFAULT;
+ else
+ bytes_read = 1;
+
+ break;
+
+ case 2:
+ if (sisusb_read_memio_word(sisusb, SISUSB_TYPE_IO,
+ address, &buf16))
+ errno = -EIO;
+ else if (put_user(buf16, (u16 __user *)buffer))
+ errno = -EFAULT;
+ else
+ bytes_read = 2;
+
+ break;
+
+ case 4:
+ if (sisusb_read_memio_long(sisusb, SISUSB_TYPE_IO,
+ address, &buf32))
+ errno = -EIO;
+ else if (put_user(buf32, (u32 __user *)buffer))
+ errno = -EFAULT;
+ else
+ bytes_read = 4;
+
+ break;
+
+ default:
+ errno = -EIO;
+
+ }
+
+ } else if ((*ppos) >= SISUSB_PCI_PSEUDO_MEMBASE && (*ppos) <
+ SISUSB_PCI_PSEUDO_MEMBASE + sisusb->vramsize) {
+
+ address = (*ppos) - SISUSB_PCI_PSEUDO_MEMBASE +
+ SISUSB_PCI_MEMBASE;
+
+ /* Read video ram
+ * Remember: Data delivered is never endian-corrected
+ */
+ errno = sisusb_read_mem_bulk(sisusb, address,
+ NULL, count, buffer, &bytes_read);
+
+ if (bytes_read)
+ errno = bytes_read;
+
+ } else if ((*ppos) >= SISUSB_PCI_PSEUDO_MMIOBASE &&
+ (*ppos) < SISUSB_PCI_PSEUDO_MMIOBASE +
+ SISUSB_PCI_MMIOSIZE) {
+
+ address = (*ppos) - SISUSB_PCI_PSEUDO_MMIOBASE +
+ SISUSB_PCI_MMIOBASE;
+
+ /* Read MMIO
+ * Remember: Data delivered is never endian-corrected
+ */
+ errno = sisusb_read_mem_bulk(sisusb, address,
+ NULL, count, buffer, &bytes_read);
+
+ if (bytes_read)
+ errno = bytes_read;
+
+ } else if ((*ppos) >= SISUSB_PCI_PSEUDO_PCIBASE &&
+ (*ppos) <= SISUSB_PCI_PSEUDO_PCIBASE + 0x5c) {
+
+ if (count != 4) {
+ mutex_unlock(&sisusb->lock);
+ return -EINVAL;
+ }
+
+ address = (*ppos) - SISUSB_PCI_PSEUDO_PCIBASE;
+
+ /* Read PCI config register
+ * Return value delivered in machine endianness.
+ */
+ if (sisusb_read_pci_config(sisusb, address, &buf32))
+ errno = -EIO;
+ else if (put_user(buf32, (u32 __user *)buffer))
+ errno = -EFAULT;
+ else
+ bytes_read = 4;
+
+ } else {
+
+ errno = -EBADFD;
+
+ }
+
+ (*ppos) += bytes_read;
+
+ mutex_unlock(&sisusb->lock);
+
+ return errno ? errno : bytes_read;
+}
+
+static ssize_t sisusb_write(struct file *file, const char __user *buffer,
+ size_t count, loff_t *ppos)
+{
+ struct sisusb_usb_data *sisusb;
+ int errno = 0;
+ ssize_t bytes_written = 0;
+ u8 buf8;
+ u16 buf16;
+ u32 buf32, address;
+
+ sisusb = file->private_data;
+ if (!sisusb)
+ return -ENODEV;
+
+ mutex_lock(&sisusb->lock);
+
+ /* Sanity check */
+ if (!sisusb->present || !sisusb->ready || !sisusb->sisusb_dev) {
+ mutex_unlock(&sisusb->lock);
+ return -ENODEV;
+ }
+
+ if ((*ppos) >= SISUSB_PCI_PSEUDO_IOPORTBASE &&
+ (*ppos) < SISUSB_PCI_PSEUDO_IOPORTBASE + 128) {
+
+ address = (*ppos) - SISUSB_PCI_PSEUDO_IOPORTBASE +
+ SISUSB_PCI_IOPORTBASE;
+
+ /* Write i/o ports
+ * Byte, word and long(32) can be written. As this
+ * emulates outX instructions, the data is expected
+ * in machine-endianness.
+ */
+ switch (count) {
+ case 1:
+ if (get_user(buf8, (u8 __user *)buffer))
+ errno = -EFAULT;
+ else if (sisusb_write_memio_byte(sisusb,
+ SISUSB_TYPE_IO, address, buf8))
+ errno = -EIO;
+ else
+ bytes_written = 1;
+
+ break;
+
+ case 2:
+ if (get_user(buf16, (u16 __user *)buffer))
+ errno = -EFAULT;
+ else if (sisusb_write_memio_word(sisusb,
+ SISUSB_TYPE_IO, address, buf16))
+ errno = -EIO;
+ else
+ bytes_written = 2;
+
+ break;
+
+ case 4:
+ if (get_user(buf32, (u32 __user *)buffer))
+ errno = -EFAULT;
+ else if (sisusb_write_memio_long(sisusb,
+ SISUSB_TYPE_IO, address, buf32))
+ errno = -EIO;
+ else
+ bytes_written = 4;
+
+ break;
+
+ default:
+ errno = -EIO;
+ }
+
+ } else if ((*ppos) >= SISUSB_PCI_PSEUDO_MEMBASE &&
+ (*ppos) < SISUSB_PCI_PSEUDO_MEMBASE +
+ sisusb->vramsize) {
+
+ address = (*ppos) - SISUSB_PCI_PSEUDO_MEMBASE +
+ SISUSB_PCI_MEMBASE;
+
+ /* Write video ram.
+ * Buffer is copied 1:1, therefore, on big-endian
+ * machines, the data must be swapped by userland
+ * in advance (if applicable; no swapping in 8bpp
+ * mode or if YUV data is being transferred).
+ */
+ errno = sisusb_write_mem_bulk(sisusb, address, NULL,
+ count, buffer, 0, &bytes_written);
+
+ if (bytes_written)
+ errno = bytes_written;
+
+ } else if ((*ppos) >= SISUSB_PCI_PSEUDO_MMIOBASE &&
+ (*ppos) < SISUSB_PCI_PSEUDO_MMIOBASE +
+ SISUSB_PCI_MMIOSIZE) {
+
+ address = (*ppos) - SISUSB_PCI_PSEUDO_MMIOBASE +
+ SISUSB_PCI_MMIOBASE;
+
+ /* Write MMIO.
+ * Buffer is copied 1:1, therefore, on big-endian
+ * machines, the data must be swapped by userland
+ * in advance.
+ */
+ errno = sisusb_write_mem_bulk(sisusb, address, NULL,
+ count, buffer, 0, &bytes_written);
+
+ if (bytes_written)
+ errno = bytes_written;
+
+ } else if ((*ppos) >= SISUSB_PCI_PSEUDO_PCIBASE &&
+ (*ppos) <= SISUSB_PCI_PSEUDO_PCIBASE +
+ SISUSB_PCI_PCONFSIZE) {
+
+ if (count != 4) {
+ mutex_unlock(&sisusb->lock);
+ return -EINVAL;
+ }
+
+ address = (*ppos) - SISUSB_PCI_PSEUDO_PCIBASE;
+
+ /* Write PCI config register.
+ * Given value expected in machine endianness.
+ */
+ if (get_user(buf32, (u32 __user *)buffer))
+ errno = -EFAULT;
+ else if (sisusb_write_pci_config(sisusb, address, buf32))
+ errno = -EIO;
+ else
+ bytes_written = 4;
+
+
+ } else {
+
+ /* Error */
+ errno = -EBADFD;
+
+ }
+
+ (*ppos) += bytes_written;
+
+ mutex_unlock(&sisusb->lock);
+
+ return errno ? errno : bytes_written;
+}
+
+static loff_t sisusb_lseek(struct file *file, loff_t offset, int orig)
+{
+ struct sisusb_usb_data *sisusb;
+ loff_t ret;
+
+ sisusb = file->private_data;
+ if (!sisusb)
+ return -ENODEV;
+
+ mutex_lock(&sisusb->lock);
+
+ /* Sanity check */
+ if (!sisusb->present || !sisusb->ready || !sisusb->sisusb_dev) {
+ mutex_unlock(&sisusb->lock);
+ return -ENODEV;
+ }
+
+ ret = no_seek_end_llseek(file, offset, orig);
+
+ mutex_unlock(&sisusb->lock);
+ return ret;
+}
+
+static int sisusb_handle_command(struct sisusb_usb_data *sisusb,
+ struct sisusb_command *y, unsigned long arg)
+{
+ int retval, length;
+ u32 port, address;
+
+ /* All our commands require the device
+ * to be initialized.
+ */
+ if (!sisusb->devinit)
+ return -ENODEV;
+
+ port = y->data3 -
+ SISUSB_PCI_PSEUDO_IOPORTBASE +
+ SISUSB_PCI_IOPORTBASE;
+
+ switch (y->operation) {
+ case SUCMD_GET:
+ retval = sisusb_getidxreg(sisusb, port, y->data0, &y->data1);
+ if (!retval) {
+ if (copy_to_user((void __user *)arg, y, sizeof(*y)))
+ retval = -EFAULT;
+ }
+ break;
+
+ case SUCMD_SET:
+ retval = sisusb_setidxreg(sisusb, port, y->data0, y->data1);
+ break;
+
+ case SUCMD_SETOR:
+ retval = sisusb_setidxregor(sisusb, port, y->data0, y->data1);
+ break;
+
+ case SUCMD_SETAND:
+ retval = sisusb_setidxregand(sisusb, port, y->data0, y->data1);
+ break;
+
+ case SUCMD_SETANDOR:
+ retval = sisusb_setidxregandor(sisusb, port, y->data0,
+ y->data1, y->data2);
+ break;
+
+ case SUCMD_SETMASK:
+ retval = sisusb_setidxregmask(sisusb, port, y->data0,
+ y->data1, y->data2);
+ break;
+
+ case SUCMD_CLRSCR:
+ /* Gfx core must be initialized */
+ if (!sisusb->gfxinit)
+ return -ENODEV;
+
+ length = (y->data0 << 16) | (y->data1 << 8) | y->data2;
+ address = y->data3 - SISUSB_PCI_PSEUDO_MEMBASE +
+ SISUSB_PCI_MEMBASE;
+ retval = sisusb_clear_vram(sisusb, address, length);
+ break;
+
+ case SUCMD_HANDLETEXTMODE:
+ retval = 0;
+#ifdef CONFIG_USB_SISUSBVGA_CON
+ /* Gfx core must be initialized, SiS_Pr must exist */
+ if (!sisusb->gfxinit || !sisusb->SiS_Pr)
+ return -ENODEV;
+
+ switch (y->data0) {
+ case 0:
+ retval = sisusb_reset_text_mode(sisusb, 0);
+ break;
+ case 1:
+ sisusb->textmodedestroyed = 1;
+ break;
+ }
+#endif
+ break;
+
+#ifdef CONFIG_USB_SISUSBVGA_CON
+ case SUCMD_SETMODE:
+ /* Gfx core must be initialized, SiS_Pr must exist */
+ if (!sisusb->gfxinit || !sisusb->SiS_Pr)
+ return -ENODEV;
+
+ retval = 0;
+
+ sisusb->SiS_Pr->IOAddress = SISUSB_PCI_IOPORTBASE + 0x30;
+ sisusb->SiS_Pr->sisusb = (void *)sisusb;
+
+ if (SiSUSBSetMode(sisusb->SiS_Pr, y->data3))
+ retval = -EINVAL;
+
+ break;
+
+ case SUCMD_SETVESAMODE:
+ /* Gfx core must be initialized, SiS_Pr must exist */
+ if (!sisusb->gfxinit || !sisusb->SiS_Pr)
+ return -ENODEV;
+
+ retval = 0;
+
+ sisusb->SiS_Pr->IOAddress = SISUSB_PCI_IOPORTBASE + 0x30;
+ sisusb->SiS_Pr->sisusb = (void *)sisusb;
+
+ if (SiSUSBSetVESAMode(sisusb->SiS_Pr, y->data3))
+ retval = -EINVAL;
+
+ break;
+#endif
+
+ default:
+ retval = -EINVAL;
+ }
+
+ if (retval > 0)
+ retval = -EIO;
+
+ return retval;
+}
+
+static long sisusb_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ struct sisusb_usb_data *sisusb;
+ struct sisusb_info x;
+ struct sisusb_command y;
+ long retval = 0;
+ u32 __user *argp = (u32 __user *)arg;
+
+ sisusb = file->private_data;
+ if (!sisusb)
+ return -ENODEV;
+
+ mutex_lock(&sisusb->lock);
+
+ /* Sanity check */
+ if (!sisusb->present || !sisusb->ready || !sisusb->sisusb_dev) {
+ retval = -ENODEV;
+ goto err_out;
+ }
+
+ switch (cmd) {
+ case SISUSB_GET_CONFIG_SIZE:
+
+ if (put_user(sizeof(x), argp))
+ retval = -EFAULT;
+
+ break;
+
+ case SISUSB_GET_CONFIG:
+
+ x.sisusb_id = SISUSB_ID;
+ x.sisusb_version = SISUSB_VERSION;
+ x.sisusb_revision = SISUSB_REVISION;
+ x.sisusb_patchlevel = SISUSB_PATCHLEVEL;
+ x.sisusb_gfxinit = sisusb->gfxinit;
+ x.sisusb_vrambase = SISUSB_PCI_PSEUDO_MEMBASE;
+ x.sisusb_mmiobase = SISUSB_PCI_PSEUDO_MMIOBASE;
+ x.sisusb_iobase = SISUSB_PCI_PSEUDO_IOPORTBASE;
+ x.sisusb_pcibase = SISUSB_PCI_PSEUDO_PCIBASE;
+ x.sisusb_vramsize = sisusb->vramsize;
+ x.sisusb_minor = sisusb->minor;
+ x.sisusb_fbdevactive = 0;
+#ifdef CONFIG_USB_SISUSBVGA_CON
+ x.sisusb_conactive = sisusb->haveconsole ? 1 : 0;
+#else
+ x.sisusb_conactive = 0;
+#endif
+ memset(x.sisusb_reserved, 0, sizeof(x.sisusb_reserved));
+
+ if (copy_to_user((void __user *)arg, &x, sizeof(x)))
+ retval = -EFAULT;
+
+ break;
+
+ case SISUSB_COMMAND:
+
+ if (copy_from_user(&y, (void __user *)arg, sizeof(y)))
+ retval = -EFAULT;
+ else
+ retval = sisusb_handle_command(sisusb, &y, arg);
+
+ break;
+
+ default:
+ retval = -ENOTTY;
+ break;
+ }
+
+err_out:
+ mutex_unlock(&sisusb->lock);
+ return retval;
+}
+
+#ifdef CONFIG_COMPAT
+static long sisusb_compat_ioctl(struct file *f, unsigned int cmd,
+ unsigned long arg)
+{
+ switch (cmd) {
+ case SISUSB_GET_CONFIG_SIZE:
+ case SISUSB_GET_CONFIG:
+ case SISUSB_COMMAND:
+ return sisusb_ioctl(f, cmd, arg);
+
+ default:
+ return -ENOIOCTLCMD;
+ }
+}
+#endif
+
+static const struct file_operations usb_sisusb_fops = {
+ .owner = THIS_MODULE,
+ .open = sisusb_open,
+ .release = sisusb_release,
+ .read = sisusb_read,
+ .write = sisusb_write,
+ .llseek = sisusb_lseek,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = sisusb_compat_ioctl,
+#endif
+ .unlocked_ioctl = sisusb_ioctl
+};
+
+static struct usb_class_driver usb_sisusb_class = {
+ .name = "sisusbvga%d",
+ .fops = &usb_sisusb_fops,
+ .minor_base = SISUSB_MINOR
+};
+
+static int sisusb_probe(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ struct usb_device *dev = interface_to_usbdev(intf);
+ struct sisusb_usb_data *sisusb;
+ int retval = 0, i;
+ static const u8 ep_addresses[] = {
+ SISUSB_EP_GFX_IN | USB_DIR_IN,
+ SISUSB_EP_GFX_OUT | USB_DIR_OUT,
+ SISUSB_EP_GFX_BULK_OUT | USB_DIR_OUT,
+ SISUSB_EP_GFX_LBULK_OUT | USB_DIR_OUT,
+ SISUSB_EP_BRIDGE_IN | USB_DIR_IN,
+ SISUSB_EP_BRIDGE_OUT | USB_DIR_OUT,
+ 0};
+
+ /* Are the expected endpoints present? */
+ if (!usb_check_bulk_endpoints(intf, ep_addresses)) {
+ dev_err(&intf->dev, "Invalid USB2VGA device\n");
+ return -EINVAL;
+ }
+
+ dev_info(&dev->dev, "USB2VGA dongle found at address %d\n",
+ dev->devnum);
+
+ /* Allocate memory for our private */
+ sisusb = kzalloc(sizeof(*sisusb), GFP_KERNEL);
+ if (!sisusb)
+ return -ENOMEM;
+
+ kref_init(&sisusb->kref);
+
+ mutex_init(&(sisusb->lock));
+
+ sisusb->sisusb_dev = dev;
+ sisusb->vrambase = SISUSB_PCI_MEMBASE;
+ sisusb->mmiobase = SISUSB_PCI_MMIOBASE;
+ sisusb->mmiosize = SISUSB_PCI_MMIOSIZE;
+ sisusb->ioportbase = SISUSB_PCI_IOPORTBASE;
+ /* Everything else is zero */
+
+ /* Register device */
+ retval = usb_register_dev(intf, &usb_sisusb_class);
+ if (retval) {
+ dev_err(&sisusb->sisusb_dev->dev,
+ "Failed to get a minor for device %d\n",
+ dev->devnum);
+ retval = -ENODEV;
+ goto error_1;
+ }
+
+ sisusb->minor = intf->minor;
+
+ /* Allocate buffers */
+ sisusb->ibufsize = SISUSB_IBUF_SIZE;
+ sisusb->ibuf = kmalloc(SISUSB_IBUF_SIZE, GFP_KERNEL);
+ if (!sisusb->ibuf) {
+ retval = -ENOMEM;
+ goto error_2;
+ }
+
+ sisusb->numobufs = 0;
+ sisusb->obufsize = SISUSB_OBUF_SIZE;
+ for (i = 0; i < NUMOBUFS; i++) {
+ sisusb->obuf[i] = kmalloc(SISUSB_OBUF_SIZE, GFP_KERNEL);
+ if (!sisusb->obuf[i]) {
+ if (i == 0) {
+ retval = -ENOMEM;
+ goto error_3;
+ }
+ break;
+ }
+ sisusb->numobufs++;
+ }
+
+ /* Allocate URBs */
+ sisusb->sisurbin = usb_alloc_urb(0, GFP_KERNEL);
+ if (!sisusb->sisurbin) {
+ retval = -ENOMEM;
+ goto error_3;
+ }
+ sisusb->completein = 1;
+
+ for (i = 0; i < sisusb->numobufs; i++) {
+ sisusb->sisurbout[i] = usb_alloc_urb(0, GFP_KERNEL);
+ if (!sisusb->sisurbout[i]) {
+ retval = -ENOMEM;
+ goto error_4;
+ }
+ sisusb->urbout_context[i].sisusb = (void *)sisusb;
+ sisusb->urbout_context[i].urbindex = i;
+ sisusb->urbstatus[i] = 0;
+ }
+
+ dev_info(&sisusb->sisusb_dev->dev, "Allocated %d output buffers\n",
+ sisusb->numobufs);
+
+#ifdef CONFIG_USB_SISUSBVGA_CON
+ /* Allocate our SiS_Pr */
+ sisusb->SiS_Pr = kmalloc(sizeof(struct SiS_Private), GFP_KERNEL);
+ if (!sisusb->SiS_Pr) {
+ retval = -ENOMEM;
+ goto error_4;
+ }
+#endif
+
+ /* Do remaining init stuff */
+
+ init_waitqueue_head(&sisusb->wait_q);
+
+ usb_set_intfdata(intf, sisusb);
+
+ usb_get_dev(sisusb->sisusb_dev);
+
+ sisusb->present = 1;
+
+ if (dev->speed == USB_SPEED_HIGH || dev->speed >= USB_SPEED_SUPER) {
+ int initscreen = 1;
+#ifdef CONFIG_USB_SISUSBVGA_CON
+ if (sisusb_first_vc > 0 && sisusb_last_vc > 0 &&
+ sisusb_first_vc <= sisusb_last_vc &&
+ sisusb_last_vc <= MAX_NR_CONSOLES)
+ initscreen = 0;
+#endif
+ if (sisusb_init_gfxdevice(sisusb, initscreen))
+ dev_err(&sisusb->sisusb_dev->dev,
+ "Failed to early initialize device\n");
+
+ } else
+ dev_info(&sisusb->sisusb_dev->dev,
+ "Not attached to USB 2.0 hub, deferring init\n");
+
+ sisusb->ready = 1;
+
+#ifdef SISUSBENDIANTEST
+ dev_dbg(&sisusb->sisusb_dev->dev, "*** RWTEST ***\n");
+ sisusb_testreadwrite(sisusb);
+ dev_dbg(&sisusb->sisusb_dev->dev, "*** RWTEST END ***\n");
+#endif
+
+#ifdef CONFIG_USB_SISUSBVGA_CON
+ sisusb_console_init(sisusb, sisusb_first_vc, sisusb_last_vc);
+#endif
+
+ return 0;
+
+error_4:
+ sisusb_free_urbs(sisusb);
+error_3:
+ sisusb_free_buffers(sisusb);
+error_2:
+ usb_deregister_dev(intf, &usb_sisusb_class);
+error_1:
+ kfree(sisusb);
+ return retval;
+}
+
+static void sisusb_disconnect(struct usb_interface *intf)
+{
+ struct sisusb_usb_data *sisusb;
+
+ /* This should *not* happen */
+ sisusb = usb_get_intfdata(intf);
+ if (!sisusb)
+ return;
+
+#ifdef CONFIG_USB_SISUSBVGA_CON
+ sisusb_console_exit(sisusb);
+#endif
+
+ usb_deregister_dev(intf, &usb_sisusb_class);
+
+ mutex_lock(&sisusb->lock);
+
+ /* Wait for all URBs to complete and kill them in case (MUST do) */
+ if (!sisusb_wait_all_out_complete(sisusb))
+ sisusb_kill_all_busy(sisusb);
+
+ usb_set_intfdata(intf, NULL);
+
+ sisusb->present = 0;
+ sisusb->ready = 0;
+
+ mutex_unlock(&sisusb->lock);
+
+ /* decrement our usage count */
+ kref_put(&sisusb->kref, sisusb_delete);
+}
+
+static const struct usb_device_id sisusb_table[] = {
+ { USB_DEVICE(0x0711, 0x0550) },
+ { USB_DEVICE(0x0711, 0x0900) },
+ { USB_DEVICE(0x0711, 0x0901) },
+ { USB_DEVICE(0x0711, 0x0902) },
+ { USB_DEVICE(0x0711, 0x0903) },
+ { USB_DEVICE(0x0711, 0x0918) },
+ { USB_DEVICE(0x0711, 0x0920) },
+ { USB_DEVICE(0x0711, 0x0950) },
+ { USB_DEVICE(0x0711, 0x5200) },
+ { USB_DEVICE(0x182d, 0x021c) },
+ { USB_DEVICE(0x182d, 0x0269) },
+ { }
+};
+
+MODULE_DEVICE_TABLE(usb, sisusb_table);
+
+static struct usb_driver sisusb_driver = {
+ .name = "sisusb",
+ .probe = sisusb_probe,
+ .disconnect = sisusb_disconnect,
+ .id_table = sisusb_table,
+};
+
+static int __init usb_sisusb_init(void)
+{
+
+#ifdef CONFIG_USB_SISUSBVGA_CON
+ sisusb_init_concode();
+#endif
+
+ return usb_register(&sisusb_driver);
+}
+
+static void __exit usb_sisusb_exit(void)
+{
+ usb_deregister(&sisusb_driver);
+}
+
+module_init(usb_sisusb_init);
+module_exit(usb_sisusb_exit);
+
+MODULE_AUTHOR("Thomas Winischhofer <thomas@winischhofer.net>");
+MODULE_DESCRIPTION("sisusbvga - Driver for Net2280/SiS315-based USB2VGA dongles");
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/usb/misc/sisusbvga/sisusb.h b/drivers/usb/misc/sisusbvga/sisusb.h
new file mode 100644
index 000000000..c0fb9e1c5
--- /dev/null
+++ b/drivers/usb/misc/sisusbvga/sisusb.h
@@ -0,0 +1,299 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */
+/*
+ * sisusb - usb kernel driver for Net2280/SiS315 based USB2VGA dongles
+ *
+ * Copyright (C) 2005 by Thomas Winischhofer, Vienna, Austria
+ *
+ * If distributed as part of the Linux kernel, this code is licensed under the
+ * terms of the GPL v2.
+ *
+ * Otherwise, the following license terms apply:
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1) Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2) Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3) The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESSED OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Author: Thomas Winischhofer <thomas@winischhofer.net>
+ *
+ */
+
+#ifndef _SISUSB_H_
+#define _SISUSB_H_
+
+#include <linux/mutex.h>
+
+/* Version Information */
+
+#define SISUSB_VERSION 0
+#define SISUSB_REVISION 0
+#define SISUSB_PATCHLEVEL 8
+
+/* Include console and mode switching code? */
+
+#include <linux/console.h>
+#include <linux/vt_kern.h>
+#include "sisusb_struct.h"
+
+/* USB related */
+
+#define SISUSB_MINOR 133 /* official */
+
+/* Size of the sisusb input/output buffers */
+#define SISUSB_IBUF_SIZE 0x01000
+#define SISUSB_OBUF_SIZE 0x10000 /* fixed */
+
+#define NUMOBUFS 8 /* max number of output buffers/output URBs */
+
+/* About endianness:
+ *
+ * 1) I/O ports, PCI config registers. The read/write()
+ * calls emulate inX/outX. Hence, the data is
+ * expected/delivered in machine endiannes by this
+ * driver.
+ * 2) Video memory. The data is copied 1:1. There is
+ * no swapping. Ever. This means for userland that
+ * the data has to be prepared properly. (Hint:
+ * think graphics data format, command queue,
+ * hardware cursor.)
+ * 3) MMIO. Data is copied 1:1. MMIO must be swapped
+ * properly by userland.
+ *
+ */
+
+#ifdef __BIG_ENDIAN
+#define SISUSB_CORRECT_ENDIANNESS_PACKET(p) \
+ do { \
+ p->header = cpu_to_le16(p->header); \
+ p->address = cpu_to_le32(p->address); \
+ p->data = cpu_to_le32(p->data); \
+ } while(0)
+#else
+#define SISUSB_CORRECT_ENDIANNESS_PACKET(p)
+#endif
+
+struct sisusb_usb_data;
+
+struct sisusb_urb_context { /* urb->context for outbound bulk URBs */
+ struct sisusb_usb_data *sisusb;
+ int urbindex;
+ int *actual_length;
+};
+
+struct sisusb_usb_data {
+ struct usb_device *sisusb_dev;
+ struct usb_interface *interface;
+ struct kref kref;
+ wait_queue_head_t wait_q; /* for syncind and timeouts */
+ struct mutex lock; /* general race avoidance */
+ unsigned int ifnum; /* interface number of the USB device */
+ int minor; /* minor (for logging clarity) */
+ int isopen; /* !=0 if open */
+ int present; /* !=0 if device is present on the bus */
+ int ready; /* !=0 if device is ready for userland */
+ int numobufs; /* number of obufs = number of out urbs */
+ char *obuf[NUMOBUFS], *ibuf; /* transfer buffers */
+ int obufsize, ibufsize;
+ struct urb *sisurbout[NUMOBUFS];
+ struct urb *sisurbin;
+ unsigned char urbstatus[NUMOBUFS];
+ unsigned char completein;
+ struct sisusb_urb_context urbout_context[NUMOBUFS];
+ unsigned long flagb0;
+ unsigned long vrambase; /* framebuffer base */
+ unsigned int vramsize; /* framebuffer size (bytes) */
+ unsigned long mmiobase;
+ unsigned int mmiosize;
+ unsigned long ioportbase;
+ unsigned char devinit; /* device initialized? */
+ unsigned char gfxinit; /* graphics core initialized? */
+ unsigned short chipid, chipvendor;
+ unsigned short chiprevision;
+#ifdef CONFIG_USB_SISUSBVGA_CON
+ struct SiS_Private *SiS_Pr;
+ unsigned long scrbuf;
+ unsigned int scrbuf_size;
+ int haveconsole, con_first, con_last;
+ int havethisconsole[MAX_NR_CONSOLES];
+ int textmodedestroyed;
+ unsigned int sisusb_num_columns; /* real number, not vt's idea */
+ int cur_start_addr, con_rolled_over;
+ int sisusb_cursor_loc, bad_cursor_pos;
+ int sisusb_cursor_size_from;
+ int sisusb_cursor_size_to;
+ int current_font_height, current_font_512;
+ int font_backup_size, font_backup_height, font_backup_512;
+ char *font_backup;
+ int font_slot;
+ struct vc_data *sisusb_display_fg;
+ int is_gfx;
+ int con_blanked;
+#endif
+};
+
+#define to_sisusb_dev(d) container_of(d, struct sisusb_usb_data, kref)
+
+/* USB transport related */
+
+/* urbstatus */
+#define SU_URB_BUSY 1
+#define SU_URB_ALLOC 2
+
+/* Endpoints */
+
+#define SISUSB_EP_GFX_IN 0x0e /* gfx std packet out(0e)/in(8e) */
+#define SISUSB_EP_GFX_OUT 0x0e
+
+#define SISUSB_EP_GFX_BULK_OUT 0x01 /* gfx mem bulk out/in */
+#define SISUSB_EP_GFX_BULK_IN 0x02 /* ? 2 is "OUT" ? */
+
+#define SISUSB_EP_GFX_LBULK_OUT 0x03 /* gfx large mem bulk out */
+
+#define SISUSB_EP_UNKNOWN_04 0x04 /* ? 4 is "OUT" ? - unused */
+
+#define SISUSB_EP_BRIDGE_IN 0x0d /* Net2280 out(0d)/in(8d) */
+#define SISUSB_EP_BRIDGE_OUT 0x0d
+
+#define SISUSB_TYPE_MEM 0
+#define SISUSB_TYPE_IO 1
+
+struct sisusb_packet {
+ unsigned short header;
+ u32 address;
+ u32 data;
+} __attribute__ ((__packed__));
+
+#define CLEARPACKET(packet) memset(packet, 0, 10)
+
+/* PCI bridge related */
+
+#define SISUSB_PCI_MEMBASE 0xd0000000
+#define SISUSB_PCI_MMIOBASE 0xe4000000
+#define SISUSB_PCI_IOPORTBASE 0x0000d000
+
+#define SISUSB_PCI_PSEUDO_MEMBASE 0x10000000
+#define SISUSB_PCI_PSEUDO_MMIOBASE 0x20000000
+#define SISUSB_PCI_PSEUDO_IOPORTBASE 0x0000d000
+#define SISUSB_PCI_PSEUDO_PCIBASE 0x00010000
+
+#define SISUSB_PCI_MMIOSIZE (128*1024)
+#define SISUSB_PCI_PCONFSIZE 0x5c
+
+/* graphics core related */
+
+#define AROFFSET 0x40
+#define ARROFFSET 0x41
+#define GROFFSET 0x4e
+#define SROFFSET 0x44
+#define CROFFSET 0x54
+#define MISCROFFSET 0x4c
+#define MISCWOFFSET 0x42
+#define INPUTSTATOFFSET 0x5A
+#define PART1OFFSET 0x04
+#define PART2OFFSET 0x10
+#define PART3OFFSET 0x12
+#define PART4OFFSET 0x14
+#define PART5OFFSET 0x16
+#define CAPTUREOFFSET 0x00
+#define VIDEOOFFSET 0x02
+#define COLREGOFFSET 0x48
+#define PELMASKOFFSET 0x46
+#define VGAENABLE 0x43
+
+#define SISAR SISUSB_PCI_IOPORTBASE + AROFFSET
+#define SISARR SISUSB_PCI_IOPORTBASE + ARROFFSET
+#define SISGR SISUSB_PCI_IOPORTBASE + GROFFSET
+#define SISSR SISUSB_PCI_IOPORTBASE + SROFFSET
+#define SISCR SISUSB_PCI_IOPORTBASE + CROFFSET
+#define SISMISCR SISUSB_PCI_IOPORTBASE + MISCROFFSET
+#define SISMISCW SISUSB_PCI_IOPORTBASE + MISCWOFFSET
+#define SISINPSTAT SISUSB_PCI_IOPORTBASE + INPUTSTATOFFSET
+#define SISPART1 SISUSB_PCI_IOPORTBASE + PART1OFFSET
+#define SISPART2 SISUSB_PCI_IOPORTBASE + PART2OFFSET
+#define SISPART3 SISUSB_PCI_IOPORTBASE + PART3OFFSET
+#define SISPART4 SISUSB_PCI_IOPORTBASE + PART4OFFSET
+#define SISPART5 SISUSB_PCI_IOPORTBASE + PART5OFFSET
+#define SISCAP SISUSB_PCI_IOPORTBASE + CAPTUREOFFSET
+#define SISVID SISUSB_PCI_IOPORTBASE + VIDEOOFFSET
+#define SISCOLIDXR SISUSB_PCI_IOPORTBASE + COLREGOFFSET - 1
+#define SISCOLIDX SISUSB_PCI_IOPORTBASE + COLREGOFFSET
+#define SISCOLDATA SISUSB_PCI_IOPORTBASE + COLREGOFFSET + 1
+#define SISCOL2IDX SISPART5
+#define SISCOL2DATA SISPART5 + 1
+#define SISPEL SISUSB_PCI_IOPORTBASE + PELMASKOFFSET
+#define SISVGAEN SISUSB_PCI_IOPORTBASE + VGAENABLE
+#define SISDACA SISCOLIDX
+#define SISDACD SISCOLDATA
+
+/* ioctl related */
+
+/* Structure argument for SISUSB_GET_INFO ioctl */
+struct sisusb_info {
+ __u32 sisusb_id; /* for identifying sisusb */
+#define SISUSB_ID 0x53495355 /* Identify myself with 'SISU' */
+ __u8 sisusb_version;
+ __u8 sisusb_revision;
+ __u8 sisusb_patchlevel;
+ __u8 sisusb_gfxinit; /* graphics core initialized? */
+
+ __u32 sisusb_vrambase;
+ __u32 sisusb_mmiobase;
+ __u32 sisusb_iobase;
+ __u32 sisusb_pcibase;
+
+ __u32 sisusb_vramsize; /* framebuffer size in bytes */
+
+ __u32 sisusb_minor;
+
+ __u32 sisusb_fbdevactive; /* != 0 if framebuffer device active */
+
+ __u32 sisusb_conactive; /* != 0 if console driver active */
+
+ __u8 sisusb_reserved[28]; /* for future use */
+};
+
+struct sisusb_command {
+ __u8 operation; /* see below */
+ __u8 data0; /* operation dependent */
+ __u8 data1; /* operation dependent */
+ __u8 data2; /* operation dependent */
+ __u32 data3; /* operation dependent */
+ __u32 data4; /* for future use */
+};
+
+#define SUCMD_GET 0x01 /* for all: data0 = index, data3 = port */
+#define SUCMD_SET 0x02 /* data1 = value */
+#define SUCMD_SETOR 0x03 /* data1 = or */
+#define SUCMD_SETAND 0x04 /* data1 = and */
+#define SUCMD_SETANDOR 0x05 /* data1 = and, data2 = or */
+#define SUCMD_SETMASK 0x06 /* data1 = data, data2 = mask */
+
+#define SUCMD_CLRSCR 0x07 /* data0:1:2 = length, data3 = address */
+
+#define SUCMD_HANDLETEXTMODE 0x08 /* Reset/destroy text mode */
+
+#define SUCMD_SETMODE 0x09 /* Set a display mode (data3 = SiS mode) */
+#define SUCMD_SETVESAMODE 0x0a /* Set a display mode (data3 = VESA mode) */
+
+#define SISUSB_COMMAND _IOWR(0xF3,0x3D,struct sisusb_command)
+#define SISUSB_GET_CONFIG_SIZE _IOR(0xF3,0x3E,__u32)
+#define SISUSB_GET_CONFIG _IOR(0xF3,0x3F,struct sisusb_info)
+
+#endif /* SISUSB_H */
diff --git a/drivers/usb/misc/sisusbvga/sisusb_con.c b/drivers/usb/misc/sisusbvga/sisusb_con.c
new file mode 100644
index 000000000..fcb95fb63
--- /dev/null
+++ b/drivers/usb/misc/sisusbvga/sisusb_con.c
@@ -0,0 +1,1496 @@
+// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+/*
+ * sisusb - usb kernel driver for SiS315(E) based USB2VGA dongles
+ *
+ * VGA text mode console part
+ *
+ * Copyright (C) 2005 by Thomas Winischhofer, Vienna, Austria
+ *
+ * If distributed as part of the Linux kernel, this code is licensed under the
+ * terms of the GPL v2.
+ *
+ * Otherwise, the following license terms apply:
+ *
+ * * Redistribution and use in source and binary forms, with or without
+ * * modification, are permitted provided that the following conditions
+ * * are met:
+ * * 1) Redistributions of source code must retain the above copyright
+ * * notice, this list of conditions and the following disclaimer.
+ * * 2) Redistributions in binary form must reproduce the above copyright
+ * * notice, this list of conditions and the following disclaimer in the
+ * * documentation and/or other materials provided with the distribution.
+ * * 3) The name of the author may not be used to endorse or promote products
+ * * derived from this software without specific psisusbr written permission.
+ * *
+ * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESSED OR
+ * * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Author: Thomas Winischhofer <thomas@winischhofer.net>
+ *
+ * Portions based on vgacon.c which are
+ * Created 28 Sep 1997 by Geert Uytterhoeven
+ * Rewritten by Martin Mares <mj@ucw.cz>, July 1998
+ * based on code Copyright (C) 1991, 1992 Linus Torvalds
+ * 1995 Jay Estabrook
+ *
+ * A note on using in_atomic() in here: We can't handle console
+ * calls from non-schedulable context due to our USB-dependend
+ * nature. For now, this driver just ignores any calls if it
+ * detects this state.
+ *
+ */
+
+#include <linux/mutex.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/signal.h>
+#include <linux/fs.h>
+#include <linux/usb.h>
+#include <linux/tty.h>
+#include <linux/console.h>
+#include <linux/string.h>
+#include <linux/kd.h>
+#include <linux/init.h>
+#include <linux/vt_kern.h>
+#include <linux/selection.h>
+#include <linux/spinlock.h>
+#include <linux/kref.h>
+#include <linux/ioport.h>
+#include <linux/interrupt.h>
+#include <linux/vmalloc.h>
+
+#include "sisusb.h"
+#include "sisusb_init.h"
+
+/* vc_data -> sisusb conversion table */
+static struct sisusb_usb_data *mysisusbs[MAX_NR_CONSOLES];
+
+/* Forward declaration */
+static const struct consw sisusb_con;
+
+static inline void
+sisusbcon_memsetw(u16 *s, u16 c, unsigned int count)
+{
+ memset16(s, c, count / 2);
+}
+
+static inline void
+sisusb_initialize(struct sisusb_usb_data *sisusb)
+{
+ /* Reset cursor and start address */
+ if (sisusb_setidxreg(sisusb, SISCR, 0x0c, 0x00))
+ return;
+ if (sisusb_setidxreg(sisusb, SISCR, 0x0d, 0x00))
+ return;
+ if (sisusb_setidxreg(sisusb, SISCR, 0x0e, 0x00))
+ return;
+ sisusb_setidxreg(sisusb, SISCR, 0x0f, 0x00);
+}
+
+static inline void
+sisusbcon_set_start_address(struct sisusb_usb_data *sisusb, struct vc_data *c)
+{
+ sisusb->cur_start_addr = (c->vc_visible_origin - sisusb->scrbuf) / 2;
+
+ sisusb_setidxreg(sisusb, SISCR, 0x0c, (sisusb->cur_start_addr >> 8));
+ sisusb_setidxreg(sisusb, SISCR, 0x0d, (sisusb->cur_start_addr & 0xff));
+}
+
+void
+sisusb_set_cursor(struct sisusb_usb_data *sisusb, unsigned int location)
+{
+ if (sisusb->sisusb_cursor_loc == location)
+ return;
+
+ sisusb->sisusb_cursor_loc = location;
+
+ /* Hardware bug: Text cursor appears twice or not at all
+ * at some positions. Work around it with the cursor skew
+ * bits.
+ */
+
+ if ((location & 0x0007) == 0x0007) {
+ sisusb->bad_cursor_pos = 1;
+ location--;
+ if (sisusb_setidxregandor(sisusb, SISCR, 0x0b, 0x1f, 0x20))
+ return;
+ } else if (sisusb->bad_cursor_pos) {
+ if (sisusb_setidxregand(sisusb, SISCR, 0x0b, 0x1f))
+ return;
+ sisusb->bad_cursor_pos = 0;
+ }
+
+ if (sisusb_setidxreg(sisusb, SISCR, 0x0e, (location >> 8)))
+ return;
+ sisusb_setidxreg(sisusb, SISCR, 0x0f, (location & 0xff));
+}
+
+static inline struct sisusb_usb_data *
+sisusb_get_sisusb(unsigned short console)
+{
+ return mysisusbs[console];
+}
+
+static inline int
+sisusb_sisusb_valid(struct sisusb_usb_data *sisusb)
+{
+ if (!sisusb->present || !sisusb->ready || !sisusb->sisusb_dev)
+ return 0;
+
+ return 1;
+}
+
+static struct sisusb_usb_data *
+sisusb_get_sisusb_lock_and_check(unsigned short console)
+{
+ struct sisusb_usb_data *sisusb;
+
+ /* We can't handle console calls in non-schedulable
+ * context due to our locks and the USB transport.
+ * So we simply ignore them. This should only affect
+ * some calls to printk.
+ */
+ if (in_atomic())
+ return NULL;
+
+ sisusb = sisusb_get_sisusb(console);
+ if (!sisusb)
+ return NULL;
+
+ mutex_lock(&sisusb->lock);
+
+ if (!sisusb_sisusb_valid(sisusb) ||
+ !sisusb->havethisconsole[console]) {
+ mutex_unlock(&sisusb->lock);
+ return NULL;
+ }
+
+ return sisusb;
+}
+
+static int
+sisusb_is_inactive(struct vc_data *c, struct sisusb_usb_data *sisusb)
+{
+ if (sisusb->is_gfx ||
+ sisusb->textmodedestroyed ||
+ c->vc_mode != KD_TEXT)
+ return 1;
+
+ return 0;
+}
+
+/* con_startup console interface routine */
+static const char *
+sisusbcon_startup(void)
+{
+ return "SISUSBCON";
+}
+
+/* con_init console interface routine */
+static void
+sisusbcon_init(struct vc_data *c, int init)
+{
+ struct sisusb_usb_data *sisusb;
+ int cols, rows;
+
+ /* This is called by do_take_over_console(),
+ * ie by us/under our control. It is
+ * only called after text mode and fonts
+ * are set up/restored.
+ */
+
+ sisusb = sisusb_get_sisusb(c->vc_num);
+ if (!sisusb)
+ return;
+
+ mutex_lock(&sisusb->lock);
+
+ if (!sisusb_sisusb_valid(sisusb)) {
+ mutex_unlock(&sisusb->lock);
+ return;
+ }
+
+ c->vc_can_do_color = 1;
+
+ c->vc_complement_mask = 0x7700;
+
+ c->vc_hi_font_mask = sisusb->current_font_512 ? 0x0800 : 0;
+
+ sisusb->haveconsole = 1;
+
+ sisusb->havethisconsole[c->vc_num] = 1;
+
+ /* We only support 640x400 */
+ c->vc_scan_lines = 400;
+
+ c->vc_font.height = sisusb->current_font_height;
+
+ /* We only support width = 8 */
+ cols = 80;
+ rows = c->vc_scan_lines / c->vc_font.height;
+
+ /* Increment usage count for our sisusb.
+ * Doing so saves us from upping/downing
+ * the disconnect semaphore; we can't
+ * lose our sisusb until this is undone
+ * in con_deinit. For all other console
+ * interface functions, it suffices to
+ * use sisusb->lock and do a quick check
+ * of sisusb for device disconnection.
+ */
+ kref_get(&sisusb->kref);
+
+ if (!*c->uni_pagedict_loc)
+ con_set_default_unimap(c);
+
+ mutex_unlock(&sisusb->lock);
+
+ if (init) {
+ c->vc_cols = cols;
+ c->vc_rows = rows;
+ } else
+ vc_resize(c, cols, rows);
+}
+
+/* con_deinit console interface routine */
+static void
+sisusbcon_deinit(struct vc_data *c)
+{
+ struct sisusb_usb_data *sisusb;
+ int i;
+
+ /* This is called by do_take_over_console()
+ * and others, ie not under our control.
+ */
+
+ sisusb = sisusb_get_sisusb(c->vc_num);
+ if (!sisusb)
+ return;
+
+ mutex_lock(&sisusb->lock);
+
+ /* Clear ourselves in mysisusbs */
+ mysisusbs[c->vc_num] = NULL;
+
+ sisusb->havethisconsole[c->vc_num] = 0;
+
+ /* Free our font buffer if all consoles are gone */
+ if (sisusb->font_backup) {
+ for(i = 0; i < MAX_NR_CONSOLES; i++) {
+ if (sisusb->havethisconsole[c->vc_num])
+ break;
+ }
+ if (i == MAX_NR_CONSOLES) {
+ vfree(sisusb->font_backup);
+ sisusb->font_backup = NULL;
+ }
+ }
+
+ mutex_unlock(&sisusb->lock);
+
+ /* decrement the usage count on our sisusb */
+ kref_put(&sisusb->kref, sisusb_delete);
+}
+
+/* interface routine */
+static u8
+sisusbcon_build_attr(struct vc_data *c, u8 color, enum vc_intensity intensity,
+ bool blink, bool underline, bool reverse,
+ bool unused)
+{
+ u8 attr = color;
+
+ if (underline)
+ attr = (attr & 0xf0) | c->vc_ulcolor;
+ else if (intensity == VCI_HALF_BRIGHT)
+ attr = (attr & 0xf0) | c->vc_halfcolor;
+
+ if (reverse)
+ attr = ((attr) & 0x88) |
+ ((((attr) >> 4) |
+ ((attr) << 4)) & 0x77);
+
+ if (blink)
+ attr ^= 0x80;
+
+ if (intensity == VCI_BOLD)
+ attr ^= 0x08;
+
+ return attr;
+}
+
+/* Interface routine */
+static void
+sisusbcon_invert_region(struct vc_data *vc, u16 *p, int count)
+{
+ /* Invert a region. This is called with a pointer
+ * to the console's internal screen buffer. So we
+ * simply do the inversion there and rely on
+ * a call to putc(s) to update the real screen.
+ */
+
+ while (count--) {
+ u16 a = *p;
+
+ *p++ = ((a) & 0x88ff) |
+ (((a) & 0x7000) >> 4) |
+ (((a) & 0x0700) << 4);
+ }
+}
+
+static inline void *sisusb_vaddr(const struct sisusb_usb_data *sisusb,
+ const struct vc_data *c, unsigned int x, unsigned int y)
+{
+ return (u16 *)c->vc_origin + y * sisusb->sisusb_num_columns + x;
+}
+
+static inline unsigned long sisusb_haddr(const struct sisusb_usb_data *sisusb,
+ const struct vc_data *c, unsigned int x, unsigned int y)
+{
+ unsigned long offset = c->vc_origin - sisusb->scrbuf;
+
+ /* 2 bytes per each character */
+ offset += 2 * (y * sisusb->sisusb_num_columns + x);
+
+ return sisusb->vrambase + offset;
+}
+
+/* Interface routine */
+static void
+sisusbcon_putc(struct vc_data *c, int ch, int y, int x)
+{
+ struct sisusb_usb_data *sisusb;
+
+ sisusb = sisusb_get_sisusb_lock_and_check(c->vc_num);
+ if (!sisusb)
+ return;
+
+ /* sisusb->lock is down */
+ if (sisusb_is_inactive(c, sisusb)) {
+ mutex_unlock(&sisusb->lock);
+ return;
+ }
+
+ sisusb_copy_memory(sisusb, sisusb_vaddr(sisusb, c, x, y),
+ sisusb_haddr(sisusb, c, x, y), 2);
+
+ mutex_unlock(&sisusb->lock);
+}
+
+/* Interface routine */
+static void
+sisusbcon_putcs(struct vc_data *c, const unsigned short *s,
+ int count, int y, int x)
+{
+ struct sisusb_usb_data *sisusb;
+
+ sisusb = sisusb_get_sisusb_lock_and_check(c->vc_num);
+ if (!sisusb)
+ return;
+
+ /* sisusb->lock is down */
+
+ /* Need to put the characters into the buffer ourselves,
+ * because the vt does this AFTER calling us.
+ */
+
+ memcpy(sisusb_vaddr(sisusb, c, x, y), s, count * 2);
+
+ if (sisusb_is_inactive(c, sisusb)) {
+ mutex_unlock(&sisusb->lock);
+ return;
+ }
+
+ sisusb_copy_memory(sisusb, sisusb_vaddr(sisusb, c, x, y),
+ sisusb_haddr(sisusb, c, x, y), count * 2);
+
+ mutex_unlock(&sisusb->lock);
+}
+
+/* Interface routine */
+static void
+sisusbcon_clear(struct vc_data *c, int y, int x, int height, int width)
+{
+ struct sisusb_usb_data *sisusb;
+ u16 eattr = c->vc_video_erase_char;
+ int i, length, cols;
+ u16 *dest;
+
+ if (width <= 0 || height <= 0)
+ return;
+
+ sisusb = sisusb_get_sisusb_lock_and_check(c->vc_num);
+ if (!sisusb)
+ return;
+
+ /* sisusb->lock is down */
+
+ /* Need to clear buffer ourselves, because the vt does
+ * this AFTER calling us.
+ */
+
+ dest = sisusb_vaddr(sisusb, c, x, y);
+
+ cols = sisusb->sisusb_num_columns;
+
+ if (width > cols)
+ width = cols;
+
+ if (x == 0 && width >= c->vc_cols) {
+
+ sisusbcon_memsetw(dest, eattr, height * cols * 2);
+
+ } else {
+
+ for (i = height; i > 0; i--, dest += cols)
+ sisusbcon_memsetw(dest, eattr, width * 2);
+
+ }
+
+ if (sisusb_is_inactive(c, sisusb)) {
+ mutex_unlock(&sisusb->lock);
+ return;
+ }
+
+ length = ((height * cols) - x - (cols - width - x)) * 2;
+
+
+ sisusb_copy_memory(sisusb, sisusb_vaddr(sisusb, c, x, y),
+ sisusb_haddr(sisusb, c, x, y), length);
+
+ mutex_unlock(&sisusb->lock);
+}
+
+/* interface routine */
+static int
+sisusbcon_switch(struct vc_data *c)
+{
+ struct sisusb_usb_data *sisusb;
+ int length;
+
+ /* Returnvalue 0 means we have fully restored screen,
+ * and vt doesn't need to call do_update_region().
+ * Returnvalue != 0 naturally means the opposite.
+ */
+
+ sisusb = sisusb_get_sisusb_lock_and_check(c->vc_num);
+ if (!sisusb)
+ return 0;
+
+ /* sisusb->lock is down */
+
+ /* Don't write to screen if in gfx mode */
+ if (sisusb_is_inactive(c, sisusb)) {
+ mutex_unlock(&sisusb->lock);
+ return 0;
+ }
+
+ /* That really should not happen. It would mean we are
+ * being called while the vc is using its private buffer
+ * as origin.
+ */
+ if (c->vc_origin == (unsigned long)c->vc_screenbuf) {
+ mutex_unlock(&sisusb->lock);
+ dev_dbg(&sisusb->sisusb_dev->dev, "ASSERT ORIGIN != SCREENBUF!\n");
+ return 0;
+ }
+
+ /* Check that we don't copy too much */
+ length = min((int)c->vc_screenbuf_size,
+ (int)(sisusb->scrbuf + sisusb->scrbuf_size - c->vc_origin));
+
+ /* Restore the screen contents */
+ memcpy((u16 *)c->vc_origin, (u16 *)c->vc_screenbuf, length);
+
+ sisusb_copy_memory(sisusb, (u8 *)c->vc_origin,
+ sisusb_haddr(sisusb, c, 0, 0), length);
+
+ mutex_unlock(&sisusb->lock);
+
+ return 0;
+}
+
+/* interface routine */
+static void
+sisusbcon_save_screen(struct vc_data *c)
+{
+ struct sisusb_usb_data *sisusb;
+ int length;
+
+ /* Save the current screen contents to vc's private
+ * buffer.
+ */
+
+ sisusb = sisusb_get_sisusb_lock_and_check(c->vc_num);
+ if (!sisusb)
+ return;
+
+ /* sisusb->lock is down */
+
+ if (sisusb_is_inactive(c, sisusb)) {
+ mutex_unlock(&sisusb->lock);
+ return;
+ }
+
+ /* Check that we don't copy too much */
+ length = min((int)c->vc_screenbuf_size,
+ (int)(sisusb->scrbuf + sisusb->scrbuf_size - c->vc_origin));
+
+ /* Save the screen contents to vc's private buffer */
+ memcpy((u16 *)c->vc_screenbuf, (u16 *)c->vc_origin, length);
+
+ mutex_unlock(&sisusb->lock);
+}
+
+/* interface routine */
+static void
+sisusbcon_set_palette(struct vc_data *c, const unsigned char *table)
+{
+ struct sisusb_usb_data *sisusb;
+ int i, j;
+
+ /* Return value not used by vt */
+
+ if (!con_is_visible(c))
+ return;
+
+ sisusb = sisusb_get_sisusb_lock_and_check(c->vc_num);
+ if (!sisusb)
+ return;
+
+ /* sisusb->lock is down */
+
+ if (sisusb_is_inactive(c, sisusb)) {
+ mutex_unlock(&sisusb->lock);
+ return;
+ }
+
+ for (i = j = 0; i < 16; i++) {
+ if (sisusb_setreg(sisusb, SISCOLIDX, table[i]))
+ break;
+ if (sisusb_setreg(sisusb, SISCOLDATA, c->vc_palette[j++] >> 2))
+ break;
+ if (sisusb_setreg(sisusb, SISCOLDATA, c->vc_palette[j++] >> 2))
+ break;
+ if (sisusb_setreg(sisusb, SISCOLDATA, c->vc_palette[j++] >> 2))
+ break;
+ }
+
+ mutex_unlock(&sisusb->lock);
+}
+
+/* interface routine */
+static int
+sisusbcon_blank(struct vc_data *c, int blank, int mode_switch)
+{
+ struct sisusb_usb_data *sisusb;
+ u8 sr1, cr17, pmreg, cr63;
+ int ret = 0;
+
+ sisusb = sisusb_get_sisusb_lock_and_check(c->vc_num);
+ if (!sisusb)
+ return 0;
+
+ /* sisusb->lock is down */
+
+ if (mode_switch)
+ sisusb->is_gfx = blank ? 1 : 0;
+
+ if (sisusb_is_inactive(c, sisusb)) {
+ mutex_unlock(&sisusb->lock);
+ return 0;
+ }
+
+ switch (blank) {
+
+ case 1: /* Normal blanking: Clear screen */
+ case -1:
+ sisusbcon_memsetw((u16 *)c->vc_origin,
+ c->vc_video_erase_char,
+ c->vc_screenbuf_size);
+ sisusb_copy_memory(sisusb, (u8 *)c->vc_origin,
+ sisusb_haddr(sisusb, c, 0, 0),
+ c->vc_screenbuf_size);
+ sisusb->con_blanked = 1;
+ ret = 1;
+ break;
+
+ default: /* VESA blanking */
+ switch (blank) {
+ case 0: /* Unblank */
+ sr1 = 0x00;
+ cr17 = 0x80;
+ pmreg = 0x00;
+ cr63 = 0x00;
+ ret = 1;
+ sisusb->con_blanked = 0;
+ break;
+ case VESA_VSYNC_SUSPEND + 1:
+ sr1 = 0x20;
+ cr17 = 0x80;
+ pmreg = 0x80;
+ cr63 = 0x40;
+ break;
+ case VESA_HSYNC_SUSPEND + 1:
+ sr1 = 0x20;
+ cr17 = 0x80;
+ pmreg = 0x40;
+ cr63 = 0x40;
+ break;
+ case VESA_POWERDOWN + 1:
+ sr1 = 0x20;
+ cr17 = 0x00;
+ pmreg = 0xc0;
+ cr63 = 0x40;
+ break;
+ default:
+ mutex_unlock(&sisusb->lock);
+ return -EINVAL;
+ }
+
+ sisusb_setidxregandor(sisusb, SISSR, 0x01, ~0x20, sr1);
+ sisusb_setidxregandor(sisusb, SISCR, 0x17, 0x7f, cr17);
+ sisusb_setidxregandor(sisusb, SISSR, 0x1f, 0x3f, pmreg);
+ sisusb_setidxregandor(sisusb, SISCR, 0x63, 0xbf, cr63);
+
+ }
+
+ mutex_unlock(&sisusb->lock);
+
+ return ret;
+}
+
+/* interface routine */
+static void
+sisusbcon_scrolldelta(struct vc_data *c, int lines)
+{
+ struct sisusb_usb_data *sisusb;
+
+ sisusb = sisusb_get_sisusb_lock_and_check(c->vc_num);
+ if (!sisusb)
+ return;
+
+ /* sisusb->lock is down */
+
+ if (sisusb_is_inactive(c, sisusb)) {
+ mutex_unlock(&sisusb->lock);
+ return;
+ }
+
+ vc_scrolldelta_helper(c, lines, sisusb->con_rolled_over,
+ (void *)sisusb->scrbuf, sisusb->scrbuf_size);
+
+ sisusbcon_set_start_address(sisusb, c);
+
+ mutex_unlock(&sisusb->lock);
+}
+
+/* Interface routine */
+static void
+sisusbcon_cursor(struct vc_data *c, int mode)
+{
+ struct sisusb_usb_data *sisusb;
+ int from, to, baseline;
+
+ sisusb = sisusb_get_sisusb_lock_and_check(c->vc_num);
+ if (!sisusb)
+ return;
+
+ /* sisusb->lock is down */
+
+ if (sisusb_is_inactive(c, sisusb)) {
+ mutex_unlock(&sisusb->lock);
+ return;
+ }
+
+ if (c->vc_origin != c->vc_visible_origin) {
+ c->vc_visible_origin = c->vc_origin;
+ sisusbcon_set_start_address(sisusb, c);
+ }
+
+ if (mode == CM_ERASE) {
+ sisusb_setidxregor(sisusb, SISCR, 0x0a, 0x20);
+ sisusb->sisusb_cursor_size_to = -1;
+ mutex_unlock(&sisusb->lock);
+ return;
+ }
+
+ sisusb_set_cursor(sisusb, (c->vc_pos - sisusb->scrbuf) / 2);
+
+ baseline = c->vc_font.height - (c->vc_font.height < 10 ? 1 : 2);
+
+ switch (CUR_SIZE(c->vc_cursor_type)) {
+ case CUR_BLOCK: from = 1;
+ to = c->vc_font.height;
+ break;
+ case CUR_TWO_THIRDS: from = c->vc_font.height / 3;
+ to = baseline;
+ break;
+ case CUR_LOWER_HALF: from = c->vc_font.height / 2;
+ to = baseline;
+ break;
+ case CUR_LOWER_THIRD: from = (c->vc_font.height * 2) / 3;
+ to = baseline;
+ break;
+ case CUR_NONE: from = 31;
+ to = 30;
+ break;
+ default:
+ case CUR_UNDERLINE: from = baseline - 1;
+ to = baseline;
+ break;
+ }
+
+ if (sisusb->sisusb_cursor_size_from != from ||
+ sisusb->sisusb_cursor_size_to != to) {
+
+ sisusb_setidxreg(sisusb, SISCR, 0x0a, from);
+ sisusb_setidxregandor(sisusb, SISCR, 0x0b, 0xe0, to);
+
+ sisusb->sisusb_cursor_size_from = from;
+ sisusb->sisusb_cursor_size_to = to;
+ }
+
+ mutex_unlock(&sisusb->lock);
+}
+
+static bool
+sisusbcon_scroll_area(struct vc_data *c, struct sisusb_usb_data *sisusb,
+ unsigned int t, unsigned int b, enum con_scroll dir,
+ unsigned int lines)
+{
+ int cols = sisusb->sisusb_num_columns;
+ int length = ((b - t) * cols) * 2;
+ u16 eattr = c->vc_video_erase_char;
+
+ /* sisusb->lock is down */
+
+ /* Scroll an area which does not match the
+ * visible screen's dimensions. This needs
+ * to be done separately, as it does not
+ * use hardware panning.
+ */
+
+ switch (dir) {
+
+ case SM_UP:
+ memmove(sisusb_vaddr(sisusb, c, 0, t),
+ sisusb_vaddr(sisusb, c, 0, t + lines),
+ (b - t - lines) * cols * 2);
+ sisusbcon_memsetw(sisusb_vaddr(sisusb, c, 0, b - lines),
+ eattr, lines * cols * 2);
+ break;
+
+ case SM_DOWN:
+ memmove(sisusb_vaddr(sisusb, c, 0, t + lines),
+ sisusb_vaddr(sisusb, c, 0, t),
+ (b - t - lines) * cols * 2);
+ sisusbcon_memsetw(sisusb_vaddr(sisusb, c, 0, t), eattr,
+ lines * cols * 2);
+ break;
+ }
+
+ sisusb_copy_memory(sisusb, sisusb_vaddr(sisusb, c, 0, t),
+ sisusb_haddr(sisusb, c, 0, t), length);
+
+ mutex_unlock(&sisusb->lock);
+
+ return true;
+}
+
+/* Interface routine */
+static bool
+sisusbcon_scroll(struct vc_data *c, unsigned int t, unsigned int b,
+ enum con_scroll dir, unsigned int lines)
+{
+ struct sisusb_usb_data *sisusb;
+ u16 eattr = c->vc_video_erase_char;
+ int copyall = 0;
+ unsigned long oldorigin;
+ unsigned int delta = lines * c->vc_size_row;
+
+ /* Returning != 0 means we have done the scrolling successfully.
+ * Returning 0 makes vt do the scrolling on its own.
+ * Note that con_scroll is only called if the console is
+ * visible. In that case, the origin should be our buffer,
+ * not the vt's private one.
+ */
+
+ if (!lines)
+ return true;
+
+ sisusb = sisusb_get_sisusb_lock_and_check(c->vc_num);
+ if (!sisusb)
+ return false;
+
+ /* sisusb->lock is down */
+
+ if (sisusb_is_inactive(c, sisusb)) {
+ mutex_unlock(&sisusb->lock);
+ return false;
+ }
+
+ /* Special case */
+ if (t || b != c->vc_rows)
+ return sisusbcon_scroll_area(c, sisusb, t, b, dir, lines);
+
+ if (c->vc_origin != c->vc_visible_origin) {
+ c->vc_visible_origin = c->vc_origin;
+ sisusbcon_set_start_address(sisusb, c);
+ }
+
+ /* limit amount to maximum realistic size */
+ if (lines > c->vc_rows)
+ lines = c->vc_rows;
+
+ oldorigin = c->vc_origin;
+
+ switch (dir) {
+
+ case SM_UP:
+
+ if (c->vc_scr_end + delta >=
+ sisusb->scrbuf + sisusb->scrbuf_size) {
+ memcpy((u16 *)sisusb->scrbuf,
+ (u16 *)(oldorigin + delta),
+ c->vc_screenbuf_size - delta);
+ c->vc_origin = sisusb->scrbuf;
+ sisusb->con_rolled_over = oldorigin - sisusb->scrbuf;
+ copyall = 1;
+ } else
+ c->vc_origin += delta;
+
+ sisusbcon_memsetw(
+ (u16 *)(c->vc_origin + c->vc_screenbuf_size - delta),
+ eattr, delta);
+
+ break;
+
+ case SM_DOWN:
+
+ if (oldorigin - delta < sisusb->scrbuf) {
+ memmove((void *)sisusb->scrbuf + sisusb->scrbuf_size -
+ c->vc_screenbuf_size + delta,
+ (u16 *)oldorigin,
+ c->vc_screenbuf_size - delta);
+ c->vc_origin = sisusb->scrbuf +
+ sisusb->scrbuf_size -
+ c->vc_screenbuf_size;
+ sisusb->con_rolled_over = 0;
+ copyall = 1;
+ } else
+ c->vc_origin -= delta;
+
+ c->vc_scr_end = c->vc_origin + c->vc_screenbuf_size;
+
+ scr_memsetw((u16 *)(c->vc_origin), eattr, delta);
+
+ break;
+ }
+
+ if (copyall)
+ sisusb_copy_memory(sisusb,
+ (u8 *)c->vc_origin,
+ sisusb_haddr(sisusb, c, 0, 0),
+ c->vc_screenbuf_size);
+ else if (dir == SM_UP)
+ sisusb_copy_memory(sisusb,
+ (u8 *)c->vc_origin + c->vc_screenbuf_size - delta,
+ sisusb_haddr(sisusb, c, 0, 0) +
+ c->vc_screenbuf_size - delta,
+ delta);
+ else
+ sisusb_copy_memory(sisusb,
+ (u8 *)c->vc_origin,
+ sisusb_haddr(sisusb, c, 0, 0),
+ delta);
+
+ c->vc_scr_end = c->vc_origin + c->vc_screenbuf_size;
+ c->vc_visible_origin = c->vc_origin;
+
+ sisusbcon_set_start_address(sisusb, c);
+
+ c->vc_pos = c->vc_pos - oldorigin + c->vc_origin;
+
+ mutex_unlock(&sisusb->lock);
+
+ return true;
+}
+
+/* Interface routine */
+static int
+sisusbcon_set_origin(struct vc_data *c)
+{
+ struct sisusb_usb_data *sisusb;
+
+ /* Returning != 0 means we were successful.
+ * Returning 0 will vt make to use its own
+ * screenbuffer as the origin.
+ */
+
+ sisusb = sisusb_get_sisusb_lock_and_check(c->vc_num);
+ if (!sisusb)
+ return 0;
+
+ /* sisusb->lock is down */
+
+ if (sisusb_is_inactive(c, sisusb) || sisusb->con_blanked) {
+ mutex_unlock(&sisusb->lock);
+ return 0;
+ }
+
+ c->vc_origin = c->vc_visible_origin = sisusb->scrbuf;
+
+ sisusbcon_set_start_address(sisusb, c);
+
+ sisusb->con_rolled_over = 0;
+
+ mutex_unlock(&sisusb->lock);
+
+ return true;
+}
+
+/* Interface routine */
+static int
+sisusbcon_resize(struct vc_data *c, unsigned int newcols, unsigned int newrows,
+ unsigned int user)
+{
+ struct sisusb_usb_data *sisusb;
+ int fh;
+
+ sisusb = sisusb_get_sisusb_lock_and_check(c->vc_num);
+ if (!sisusb)
+ return -ENODEV;
+
+ fh = sisusb->current_font_height;
+
+ mutex_unlock(&sisusb->lock);
+
+ /* We are quite unflexible as regards resizing. The vt code
+ * handles sizes where the line length isn't equal the pitch
+ * quite badly. As regards the rows, our panning tricks only
+ * work well if the number of rows equals the visible number
+ * of rows.
+ */
+
+ if (newcols != 80 || c->vc_scan_lines / fh != newrows)
+ return -EINVAL;
+
+ return 0;
+}
+
+int
+sisusbcon_do_font_op(struct sisusb_usb_data *sisusb, int set, int slot,
+ u8 *arg, int cmapsz, int ch512, int dorecalc,
+ struct vc_data *c, int fh, int uplock)
+{
+ int font_select = 0x00, i, err = 0;
+ u32 offset = 0;
+ u8 dummy;
+
+ /* sisusb->lock is down */
+
+ /*
+ * The default font is kept in slot 0.
+ * A user font is loaded in slot 2 (256 ch)
+ * or 2+3 (512 ch).
+ */
+
+ if ((slot != 0 && slot != 2) || !fh) {
+ if (uplock)
+ mutex_unlock(&sisusb->lock);
+ return -EINVAL;
+ }
+
+ if (set)
+ sisusb->font_slot = slot;
+
+ /* Default font is always 256 */
+ if (slot == 0)
+ ch512 = 0;
+ else
+ offset = 4 * cmapsz;
+
+ font_select = (slot == 0) ? 0x00 : (ch512 ? 0x0e : 0x0a);
+
+ err |= sisusb_setidxreg(sisusb, SISSR, 0x00, 0x01); /* Reset */
+ err |= sisusb_setidxreg(sisusb, SISSR, 0x02, 0x04); /* Write to plane 2 */
+ err |= sisusb_setidxreg(sisusb, SISSR, 0x04, 0x07); /* Memory mode a0-bf */
+ err |= sisusb_setidxreg(sisusb, SISSR, 0x00, 0x03); /* Reset */
+
+ if (err)
+ goto font_op_error;
+
+ err |= sisusb_setidxreg(sisusb, SISGR, 0x04, 0x03); /* Select plane read 2 */
+ err |= sisusb_setidxreg(sisusb, SISGR, 0x05, 0x00); /* Disable odd/even */
+ err |= sisusb_setidxreg(sisusb, SISGR, 0x06, 0x00); /* Address range a0-bf */
+
+ if (err)
+ goto font_op_error;
+
+ if (arg) {
+ if (set)
+ for (i = 0; i < cmapsz; i++) {
+ err |= sisusb_writeb(sisusb,
+ sisusb->vrambase + offset + i,
+ arg[i]);
+ if (err)
+ break;
+ }
+ else
+ for (i = 0; i < cmapsz; i++) {
+ err |= sisusb_readb(sisusb,
+ sisusb->vrambase + offset + i,
+ &arg[i]);
+ if (err)
+ break;
+ }
+
+ /*
+ * In 512-character mode, the character map is not contiguous if
+ * we want to remain EGA compatible -- which we do
+ */
+
+ if (ch512) {
+ if (set)
+ for (i = 0; i < cmapsz; i++) {
+ err |= sisusb_writeb(sisusb,
+ sisusb->vrambase + offset +
+ (2 * cmapsz) + i,
+ arg[cmapsz + i]);
+ if (err)
+ break;
+ }
+ else
+ for (i = 0; i < cmapsz; i++) {
+ err |= sisusb_readb(sisusb,
+ sisusb->vrambase + offset +
+ (2 * cmapsz) + i,
+ &arg[cmapsz + i]);
+ if (err)
+ break;
+ }
+ }
+ }
+
+ if (err)
+ goto font_op_error;
+
+ err |= sisusb_setidxreg(sisusb, SISSR, 0x00, 0x01); /* Reset */
+ err |= sisusb_setidxreg(sisusb, SISSR, 0x02, 0x03); /* Write to planes 0+1 */
+ err |= sisusb_setidxreg(sisusb, SISSR, 0x04, 0x03); /* Memory mode a0-bf */
+ if (set)
+ sisusb_setidxreg(sisusb, SISSR, 0x03, font_select);
+ err |= sisusb_setidxreg(sisusb, SISSR, 0x00, 0x03); /* Reset end */
+
+ if (err)
+ goto font_op_error;
+
+ err |= sisusb_setidxreg(sisusb, SISGR, 0x04, 0x00); /* Select plane read 0 */
+ err |= sisusb_setidxreg(sisusb, SISGR, 0x05, 0x10); /* Enable odd/even */
+ err |= sisusb_setidxreg(sisusb, SISGR, 0x06, 0x06); /* Address range b8-bf */
+
+ if (err)
+ goto font_op_error;
+
+ if ((set) && (ch512 != sisusb->current_font_512)) {
+
+ /* Font is shared among all our consoles.
+ * And so is the hi_font_mask.
+ */
+ for (i = 0; i < MAX_NR_CONSOLES; i++) {
+ struct vc_data *d = vc_cons[i].d;
+ if (d && d->vc_sw == &sisusb_con)
+ d->vc_hi_font_mask = ch512 ? 0x0800 : 0;
+ }
+
+ sisusb->current_font_512 = ch512;
+
+ /* color plane enable register:
+ 256-char: enable intensity bit
+ 512-char: disable intensity bit */
+ sisusb_getreg(sisusb, SISINPSTAT, &dummy);
+ sisusb_setreg(sisusb, SISAR, 0x12);
+ sisusb_setreg(sisusb, SISAR, ch512 ? 0x07 : 0x0f);
+
+ sisusb_getreg(sisusb, SISINPSTAT, &dummy);
+ sisusb_setreg(sisusb, SISAR, 0x20);
+ sisusb_getreg(sisusb, SISINPSTAT, &dummy);
+ }
+
+ if (dorecalc) {
+
+ /*
+ * Adjust the screen to fit a font of a certain height
+ */
+
+ unsigned char ovr, vde, fsr;
+ int rows = 0, maxscan = 0;
+
+ if (c) {
+
+ /* Number of video rows */
+ rows = c->vc_scan_lines / fh;
+ /* Scan lines to actually display-1 */
+ maxscan = rows * fh - 1;
+
+ /*printk(KERN_DEBUG "sisusb recalc rows %d maxscan %d fh %d sl %d\n",
+ rows, maxscan, fh, c->vc_scan_lines);*/
+
+ sisusb_getidxreg(sisusb, SISCR, 0x07, &ovr);
+ vde = maxscan & 0xff;
+ ovr = (ovr & 0xbd) |
+ ((maxscan & 0x100) >> 7) |
+ ((maxscan & 0x200) >> 3);
+ sisusb_setidxreg(sisusb, SISCR, 0x07, ovr);
+ sisusb_setidxreg(sisusb, SISCR, 0x12, vde);
+
+ }
+
+ sisusb_getidxreg(sisusb, SISCR, 0x09, &fsr);
+ fsr = (fsr & 0xe0) | (fh - 1);
+ sisusb_setidxreg(sisusb, SISCR, 0x09, fsr);
+ sisusb->current_font_height = fh;
+
+ sisusb->sisusb_cursor_size_from = -1;
+ sisusb->sisusb_cursor_size_to = -1;
+
+ }
+
+ if (uplock)
+ mutex_unlock(&sisusb->lock);
+
+ if (dorecalc && c) {
+ int rows = c->vc_scan_lines / fh;
+
+ /* Now adjust our consoles' size */
+
+ for (i = 0; i < MAX_NR_CONSOLES; i++) {
+ struct vc_data *vc = vc_cons[i].d;
+
+ if (vc && vc->vc_sw == &sisusb_con) {
+ if (con_is_visible(vc)) {
+ vc->vc_sw->con_cursor(vc, CM_DRAW);
+ }
+ vc->vc_font.height = fh;
+ vc_resize(vc, 0, rows);
+ }
+ }
+ }
+
+ return 0;
+
+font_op_error:
+ if (uplock)
+ mutex_unlock(&sisusb->lock);
+
+ return -EIO;
+}
+
+/* Interface routine */
+static int
+sisusbcon_font_set(struct vc_data *c, struct console_font *font,
+ unsigned int flags)
+{
+ struct sisusb_usb_data *sisusb;
+ unsigned charcount = font->charcount;
+
+ if (font->width != 8 || (charcount != 256 && charcount != 512))
+ return -EINVAL;
+
+ sisusb = sisusb_get_sisusb_lock_and_check(c->vc_num);
+ if (!sisusb)
+ return -ENODEV;
+
+ /* sisusb->lock is down */
+
+ /* Save the user-provided font into a buffer. This
+ * is used for restoring text mode after quitting
+ * from X and for the con_getfont routine.
+ */
+ if (sisusb->font_backup) {
+ if (sisusb->font_backup_size < charcount) {
+ vfree(sisusb->font_backup);
+ sisusb->font_backup = NULL;
+ }
+ }
+
+ if (!sisusb->font_backup)
+ sisusb->font_backup = vmalloc(array_size(charcount, 32));
+
+ if (sisusb->font_backup) {
+ memcpy(sisusb->font_backup, font->data, array_size(charcount, 32));
+ sisusb->font_backup_size = charcount;
+ sisusb->font_backup_height = font->height;
+ sisusb->font_backup_512 = (charcount == 512) ? 1 : 0;
+ }
+
+ /* do_font_op ups sisusb->lock */
+
+ return sisusbcon_do_font_op(sisusb, 1, 2, font->data,
+ 8192, (charcount == 512),
+ (!(flags & KD_FONT_FLAG_DONT_RECALC)) ? 1 : 0,
+ c, font->height, 1);
+}
+
+/* Interface routine */
+static int
+sisusbcon_font_get(struct vc_data *c, struct console_font *font)
+{
+ struct sisusb_usb_data *sisusb;
+
+ sisusb = sisusb_get_sisusb_lock_and_check(c->vc_num);
+ if (!sisusb)
+ return -ENODEV;
+
+ /* sisusb->lock is down */
+
+ font->width = 8;
+ font->height = c->vc_font.height;
+ font->charcount = 256;
+
+ if (!font->data) {
+ mutex_unlock(&sisusb->lock);
+ return 0;
+ }
+
+ if (!sisusb->font_backup) {
+ mutex_unlock(&sisusb->lock);
+ return -ENODEV;
+ }
+
+ /* Copy 256 chars only, like vgacon */
+ memcpy(font->data, sisusb->font_backup, 256 * 32);
+
+ mutex_unlock(&sisusb->lock);
+
+ return 0;
+}
+
+/*
+ * The console `switch' structure for the sisusb console
+ */
+
+static const struct consw sisusb_con = {
+ .owner = THIS_MODULE,
+ .con_startup = sisusbcon_startup,
+ .con_init = sisusbcon_init,
+ .con_deinit = sisusbcon_deinit,
+ .con_clear = sisusbcon_clear,
+ .con_putc = sisusbcon_putc,
+ .con_putcs = sisusbcon_putcs,
+ .con_cursor = sisusbcon_cursor,
+ .con_scroll = sisusbcon_scroll,
+ .con_switch = sisusbcon_switch,
+ .con_blank = sisusbcon_blank,
+ .con_font_set = sisusbcon_font_set,
+ .con_font_get = sisusbcon_font_get,
+ .con_set_palette = sisusbcon_set_palette,
+ .con_scrolldelta = sisusbcon_scrolldelta,
+ .con_build_attr = sisusbcon_build_attr,
+ .con_invert_region = sisusbcon_invert_region,
+ .con_set_origin = sisusbcon_set_origin,
+ .con_save_screen = sisusbcon_save_screen,
+ .con_resize = sisusbcon_resize,
+};
+
+/* Our very own dummy console driver */
+
+static const char *sisusbdummycon_startup(void)
+{
+ return "SISUSBVGADUMMY";
+}
+
+static void sisusbdummycon_init(struct vc_data *vc, int init)
+{
+ vc->vc_can_do_color = 1;
+ if (init) {
+ vc->vc_cols = 80;
+ vc->vc_rows = 25;
+ } else
+ vc_resize(vc, 80, 25);
+}
+
+static void sisusbdummycon_deinit(struct vc_data *vc) { }
+static void sisusbdummycon_clear(struct vc_data *vc, int sy, int sx,
+ int height, int width) { }
+static void sisusbdummycon_putc(struct vc_data *vc, int c, int ypos,
+ int xpos) { }
+static void sisusbdummycon_putcs(struct vc_data *vc, const unsigned short *s,
+ int count, int ypos, int xpos) { }
+static void sisusbdummycon_cursor(struct vc_data *vc, int mode) { }
+
+static bool sisusbdummycon_scroll(struct vc_data *vc, unsigned int top,
+ unsigned int bottom, enum con_scroll dir,
+ unsigned int lines)
+{
+ return false;
+}
+
+static int sisusbdummycon_switch(struct vc_data *vc)
+{
+ return 0;
+}
+
+static int sisusbdummycon_blank(struct vc_data *vc, int blank, int mode_switch)
+{
+ return 0;
+}
+
+static const struct consw sisusb_dummy_con = {
+ .owner = THIS_MODULE,
+ .con_startup = sisusbdummycon_startup,
+ .con_init = sisusbdummycon_init,
+ .con_deinit = sisusbdummycon_deinit,
+ .con_clear = sisusbdummycon_clear,
+ .con_putc = sisusbdummycon_putc,
+ .con_putcs = sisusbdummycon_putcs,
+ .con_cursor = sisusbdummycon_cursor,
+ .con_scroll = sisusbdummycon_scroll,
+ .con_switch = sisusbdummycon_switch,
+ .con_blank = sisusbdummycon_blank,
+};
+
+int
+sisusb_console_init(struct sisusb_usb_data *sisusb, int first, int last)
+{
+ int i, ret;
+
+ mutex_lock(&sisusb->lock);
+
+ /* Erm.. that should not happen */
+ if (sisusb->haveconsole || !sisusb->SiS_Pr) {
+ mutex_unlock(&sisusb->lock);
+ return 1;
+ }
+
+ sisusb->con_first = first;
+ sisusb->con_last = last;
+
+ if (first > last ||
+ first > MAX_NR_CONSOLES ||
+ last > MAX_NR_CONSOLES) {
+ mutex_unlock(&sisusb->lock);
+ return 1;
+ }
+
+ /* If gfxcore not initialized or no consoles given, quit graciously */
+ if (!sisusb->gfxinit || first < 1 || last < 1) {
+ mutex_unlock(&sisusb->lock);
+ return 0;
+ }
+
+ sisusb->sisusb_cursor_loc = -1;
+ sisusb->sisusb_cursor_size_from = -1;
+ sisusb->sisusb_cursor_size_to = -1;
+
+ /* Set up text mode (and upload default font) */
+ if (sisusb_reset_text_mode(sisusb, 1)) {
+ mutex_unlock(&sisusb->lock);
+ dev_err(&sisusb->sisusb_dev->dev, "Failed to set up text mode\n");
+ return 1;
+ }
+
+ /* Initialize some gfx registers */
+ sisusb_initialize(sisusb);
+
+ for (i = first - 1; i <= last - 1; i++) {
+ /* Save sisusb for our interface routines */
+ mysisusbs[i] = sisusb;
+ }
+
+ /* Initial console setup */
+ sisusb->sisusb_num_columns = 80;
+
+ /* Use a 32K buffer (matches b8000-bffff area) */
+ sisusb->scrbuf_size = 32 * 1024;
+
+ /* Allocate screen buffer */
+ if (!(sisusb->scrbuf = (unsigned long)vmalloc(sisusb->scrbuf_size))) {
+ mutex_unlock(&sisusb->lock);
+ dev_err(&sisusb->sisusb_dev->dev, "Failed to allocate screen buffer\n");
+ return 1;
+ }
+
+ mutex_unlock(&sisusb->lock);
+
+ /* Now grab the desired console(s) */
+ console_lock();
+ ret = do_take_over_console(&sisusb_con, first - 1, last - 1, 0);
+ console_unlock();
+ if (!ret)
+ sisusb->haveconsole = 1;
+ else {
+ for (i = first - 1; i <= last - 1; i++)
+ mysisusbs[i] = NULL;
+ }
+
+ return ret;
+}
+
+void
+sisusb_console_exit(struct sisusb_usb_data *sisusb)
+{
+ int i;
+
+ /* This is called if the device is disconnected
+ * and while disconnect and lock semaphores
+ * are up. This should be save because we
+ * can't lose our sisusb any other way but by
+ * disconnection (and hence, the disconnect
+ * sema is for protecting all other access
+ * functions from disconnection, not the
+ * other way round).
+ */
+
+ /* Now what do we do in case of disconnection:
+ * One alternative would be to simply call
+ * give_up_console(). Nah, not a good idea.
+ * give_up_console() is obviously buggy as it
+ * only discards the consw pointer from the
+ * driver_map, but doesn't adapt vc->vc_sw
+ * of the affected consoles. Hence, the next
+ * call to any of the console functions will
+ * eventually take a trip to oops county.
+ * Also, give_up_console for some reason
+ * doesn't decrement our module refcount.
+ * Instead, we switch our consoles to a private
+ * dummy console. This, of course, keeps our
+ * refcount up as well, but it works perfectly.
+ */
+
+ if (sisusb->haveconsole) {
+ for (i = 0; i < MAX_NR_CONSOLES; i++)
+ if (sisusb->havethisconsole[i]) {
+ console_lock();
+ do_take_over_console(&sisusb_dummy_con, i, i, 0);
+ console_unlock();
+ /* At this point, con_deinit for all our
+ * consoles is executed by do_take_over_console().
+ */
+ }
+ sisusb->haveconsole = 0;
+ }
+
+ vfree((void *)sisusb->scrbuf);
+ sisusb->scrbuf = 0;
+
+ vfree(sisusb->font_backup);
+ sisusb->font_backup = NULL;
+}
+
+void __init sisusb_init_concode(void)
+{
+ int i;
+
+ for (i = 0; i < MAX_NR_CONSOLES; i++)
+ mysisusbs[i] = NULL;
+}
diff --git a/drivers/usb/misc/sisusbvga/sisusb_init.c b/drivers/usb/misc/sisusbvga/sisusb_init.c
new file mode 100644
index 000000000..7c11198d5
--- /dev/null
+++ b/drivers/usb/misc/sisusbvga/sisusb_init.c
@@ -0,0 +1,955 @@
+// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+/*
+ * sisusb - usb kernel driver for SiS315(E) based USB2VGA dongles
+ *
+ * Display mode initializing code
+ *
+ * Copyright (C) 2001-2005 by Thomas Winischhofer, Vienna, Austria
+ *
+ * If distributed as part of the Linux kernel, this code is licensed under the
+ * terms of the GPL v2.
+ *
+ * Otherwise, the following license terms apply:
+ *
+ * * Redistribution and use in source and binary forms, with or without
+ * * modification, are permitted provided that the following conditions
+ * * are met:
+ * * 1) Redistributions of source code must retain the above copyright
+ * * notice, this list of conditions and the following disclaimer.
+ * * 2) Redistributions in binary form must reproduce the above copyright
+ * * notice, this list of conditions and the following disclaimer in the
+ * * documentation and/or other materials provided with the distribution.
+ * * 3) The name of the author may not be used to endorse or promote products
+ * * derived from this software without specific prior written permission.
+ * *
+ * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Author: Thomas Winischhofer <thomas@winischhofer.net>
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/poll.h>
+#include <linux/spinlock.h>
+
+#include "sisusb.h"
+#include "sisusb_init.h"
+#include "sisusb_tables.h"
+
+/*********************************************/
+/* POINTER INITIALIZATION */
+/*********************************************/
+
+static void SiSUSB_InitPtr(struct SiS_Private *SiS_Pr)
+{
+ SiS_Pr->SiS_ModeResInfo = SiSUSB_ModeResInfo;
+ SiS_Pr->SiS_StandTable = SiSUSB_StandTable;
+
+ SiS_Pr->SiS_SModeIDTable = SiSUSB_SModeIDTable;
+ SiS_Pr->SiS_EModeIDTable = SiSUSB_EModeIDTable;
+ SiS_Pr->SiS_RefIndex = SiSUSB_RefIndex;
+ SiS_Pr->SiS_CRT1Table = SiSUSB_CRT1Table;
+
+ SiS_Pr->SiS_VCLKData = SiSUSB_VCLKData;
+}
+
+/*********************************************/
+/* HELPER: SetReg, GetReg */
+/*********************************************/
+
+static void
+SiS_SetReg(struct SiS_Private *SiS_Pr, unsigned long port,
+ unsigned short index, unsigned short data)
+{
+ sisusb_setidxreg(SiS_Pr->sisusb, port, index, data);
+}
+
+static void
+SiS_SetRegByte(struct SiS_Private *SiS_Pr, unsigned long port,
+ unsigned short data)
+{
+ sisusb_setreg(SiS_Pr->sisusb, port, data);
+}
+
+static unsigned char
+SiS_GetReg(struct SiS_Private *SiS_Pr, unsigned long port, unsigned short index)
+{
+ u8 data;
+
+ sisusb_getidxreg(SiS_Pr->sisusb, port, index, &data);
+
+ return data;
+}
+
+static unsigned char
+SiS_GetRegByte(struct SiS_Private *SiS_Pr, unsigned long port)
+{
+ u8 data;
+
+ sisusb_getreg(SiS_Pr->sisusb, port, &data);
+
+ return data;
+}
+
+static void
+SiS_SetRegANDOR(struct SiS_Private *SiS_Pr, unsigned long port,
+ unsigned short index, unsigned short DataAND,
+ unsigned short DataOR)
+{
+ sisusb_setidxregandor(SiS_Pr->sisusb, port, index, DataAND, DataOR);
+}
+
+static void
+SiS_SetRegAND(struct SiS_Private *SiS_Pr, unsigned long port,
+ unsigned short index, unsigned short DataAND)
+{
+ sisusb_setidxregand(SiS_Pr->sisusb, port, index, DataAND);
+}
+
+static void
+SiS_SetRegOR(struct SiS_Private *SiS_Pr, unsigned long port,
+ unsigned short index, unsigned short DataOR)
+{
+ sisusb_setidxregor(SiS_Pr->sisusb, port, index, DataOR);
+}
+
+/*********************************************/
+/* HELPER: DisplayOn, DisplayOff */
+/*********************************************/
+
+static void SiS_DisplayOn(struct SiS_Private *SiS_Pr)
+{
+ SiS_SetRegAND(SiS_Pr, SiS_Pr->SiS_P3c4, 0x01, 0xDF);
+}
+
+/*********************************************/
+/* HELPER: Init Port Addresses */
+/*********************************************/
+
+static void SiSUSBRegInit(struct SiS_Private *SiS_Pr, unsigned long BaseAddr)
+{
+ SiS_Pr->SiS_P3c4 = BaseAddr + 0x14;
+ SiS_Pr->SiS_P3d4 = BaseAddr + 0x24;
+ SiS_Pr->SiS_P3c0 = BaseAddr + 0x10;
+ SiS_Pr->SiS_P3ce = BaseAddr + 0x1e;
+ SiS_Pr->SiS_P3c2 = BaseAddr + 0x12;
+ SiS_Pr->SiS_P3ca = BaseAddr + 0x1a;
+ SiS_Pr->SiS_P3c6 = BaseAddr + 0x16;
+ SiS_Pr->SiS_P3c7 = BaseAddr + 0x17;
+ SiS_Pr->SiS_P3c8 = BaseAddr + 0x18;
+ SiS_Pr->SiS_P3c9 = BaseAddr + 0x19;
+ SiS_Pr->SiS_P3cb = BaseAddr + 0x1b;
+ SiS_Pr->SiS_P3cc = BaseAddr + 0x1c;
+ SiS_Pr->SiS_P3cd = BaseAddr + 0x1d;
+ SiS_Pr->SiS_P3da = BaseAddr + 0x2a;
+ SiS_Pr->SiS_Part1Port = BaseAddr + SIS_CRT2_PORT_04;
+}
+
+/*********************************************/
+/* HELPER: GetSysFlags */
+/*********************************************/
+
+static void SiS_GetSysFlags(struct SiS_Private *SiS_Pr)
+{
+ SiS_Pr->SiS_MyCR63 = 0x63;
+}
+
+/*********************************************/
+/* HELPER: Init PCI & Engines */
+/*********************************************/
+
+static void SiSInitPCIetc(struct SiS_Private *SiS_Pr)
+{
+ SiS_SetReg(SiS_Pr, SiS_Pr->SiS_P3c4, 0x20, 0xa1);
+ /* - Enable 2D (0x40)
+ * - Enable 3D (0x02)
+ * - Enable 3D vertex command fetch (0x10)
+ * - Enable 3D command parser (0x08)
+ * - Enable 3D G/L transformation engine (0x80)
+ */
+ SiS_SetRegOR(SiS_Pr, SiS_Pr->SiS_P3c4, 0x1E, 0xDA);
+}
+
+/*********************************************/
+/* HELPER: SET SEGMENT REGISTERS */
+/*********************************************/
+
+static void SiS_SetSegRegLower(struct SiS_Private *SiS_Pr, unsigned short value)
+{
+ unsigned short temp;
+
+ value &= 0x00ff;
+ temp = SiS_GetRegByte(SiS_Pr, SiS_Pr->SiS_P3cb) & 0xf0;
+ temp |= (value >> 4);
+ SiS_SetRegByte(SiS_Pr, SiS_Pr->SiS_P3cb, temp);
+ temp = SiS_GetRegByte(SiS_Pr, SiS_Pr->SiS_P3cd) & 0xf0;
+ temp |= (value & 0x0f);
+ SiS_SetRegByte(SiS_Pr, SiS_Pr->SiS_P3cd, temp);
+}
+
+static void SiS_SetSegRegUpper(struct SiS_Private *SiS_Pr, unsigned short value)
+{
+ unsigned short temp;
+
+ value &= 0x00ff;
+ temp = SiS_GetRegByte(SiS_Pr, SiS_Pr->SiS_P3cb) & 0x0f;
+ temp |= (value & 0xf0);
+ SiS_SetRegByte(SiS_Pr, SiS_Pr->SiS_P3cb, temp);
+ temp = SiS_GetRegByte(SiS_Pr, SiS_Pr->SiS_P3cd) & 0x0f;
+ temp |= (value << 4);
+ SiS_SetRegByte(SiS_Pr, SiS_Pr->SiS_P3cd, temp);
+}
+
+static void SiS_SetSegmentReg(struct SiS_Private *SiS_Pr, unsigned short value)
+{
+ SiS_SetSegRegLower(SiS_Pr, value);
+ SiS_SetSegRegUpper(SiS_Pr, value);
+}
+
+static void SiS_ResetSegmentReg(struct SiS_Private *SiS_Pr)
+{
+ SiS_SetSegmentReg(SiS_Pr, 0);
+}
+
+static void
+SiS_SetSegmentRegOver(struct SiS_Private *SiS_Pr, unsigned short value)
+{
+ unsigned short temp = value >> 8;
+
+ temp &= 0x07;
+ temp |= (temp << 4);
+ SiS_SetReg(SiS_Pr, SiS_Pr->SiS_P3c4, 0x1d, temp);
+ SiS_SetSegmentReg(SiS_Pr, value);
+}
+
+static void SiS_ResetSegmentRegOver(struct SiS_Private *SiS_Pr)
+{
+ SiS_SetSegmentRegOver(SiS_Pr, 0);
+}
+
+static void SiS_ResetSegmentRegisters(struct SiS_Private *SiS_Pr)
+{
+ SiS_ResetSegmentReg(SiS_Pr);
+ SiS_ResetSegmentRegOver(SiS_Pr);
+}
+
+/*********************************************/
+/* HELPER: SearchModeID */
+/*********************************************/
+
+static int
+SiS_SearchModeID(struct SiS_Private *SiS_Pr, unsigned short *ModeNo,
+ unsigned short *ModeIdIndex)
+{
+ if ((*ModeNo) <= 0x13) {
+
+ if ((*ModeNo) != 0x03)
+ return 0;
+
+ (*ModeIdIndex) = 0;
+
+ } else {
+
+ for (*ModeIdIndex = 0;; (*ModeIdIndex)++) {
+
+ if (SiS_Pr->SiS_EModeIDTable[*ModeIdIndex].Ext_ModeID ==
+ (*ModeNo))
+ break;
+
+ if (SiS_Pr->SiS_EModeIDTable[*ModeIdIndex].Ext_ModeID ==
+ 0xFF)
+ return 0;
+ }
+
+ }
+
+ return 1;
+}
+
+/*********************************************/
+/* HELPER: ENABLE CRT1 */
+/*********************************************/
+
+static void SiS_HandleCRT1(struct SiS_Private *SiS_Pr)
+{
+ /* Enable CRT1 gating */
+ SiS_SetRegAND(SiS_Pr, SiS_Pr->SiS_P3d4, SiS_Pr->SiS_MyCR63, 0xbf);
+}
+
+/*********************************************/
+/* HELPER: GetColorDepth */
+/*********************************************/
+
+static unsigned short
+SiS_GetColorDepth(struct SiS_Private *SiS_Pr, unsigned short ModeNo,
+ unsigned short ModeIdIndex)
+{
+ static const unsigned short ColorDepth[6] = { 1, 2, 4, 4, 6, 8 };
+ unsigned short modeflag;
+ short index;
+
+ if (ModeNo <= 0x13) {
+ modeflag = SiS_Pr->SiS_SModeIDTable[ModeIdIndex].St_ModeFlag;
+ } else {
+ modeflag = SiS_Pr->SiS_EModeIDTable[ModeIdIndex].Ext_ModeFlag;
+ }
+
+ index = (modeflag & ModeTypeMask) - ModeEGA;
+ if (index < 0)
+ index = 0;
+ return ColorDepth[index];
+}
+
+/*********************************************/
+/* HELPER: GetOffset */
+/*********************************************/
+
+static unsigned short
+SiS_GetOffset(struct SiS_Private *SiS_Pr, unsigned short ModeNo,
+ unsigned short ModeIdIndex, unsigned short rrti)
+{
+ unsigned short xres, temp, colordepth, infoflag;
+
+ infoflag = SiS_Pr->SiS_RefIndex[rrti].Ext_InfoFlag;
+ xres = SiS_Pr->SiS_RefIndex[rrti].XRes;
+
+ colordepth = SiS_GetColorDepth(SiS_Pr, ModeNo, ModeIdIndex);
+
+ temp = xres / 16;
+
+ if (infoflag & InterlaceMode)
+ temp <<= 1;
+
+ temp *= colordepth;
+
+ if (xres % 16)
+ temp += (colordepth >> 1);
+
+ return temp;
+}
+
+/*********************************************/
+/* SEQ */
+/*********************************************/
+
+static void
+SiS_SetSeqRegs(struct SiS_Private *SiS_Pr, unsigned short StandTableIndex)
+{
+ unsigned char SRdata;
+ int i;
+
+ SiS_SetReg(SiS_Pr, SiS_Pr->SiS_P3c4, 0x00, 0x03);
+
+ SRdata = SiS_Pr->SiS_StandTable[StandTableIndex].SR[0] | 0x20;
+ SiS_SetReg(SiS_Pr, SiS_Pr->SiS_P3c4, 0x01, SRdata);
+
+ for (i = 2; i <= 4; i++) {
+ SRdata = SiS_Pr->SiS_StandTable[StandTableIndex].SR[i - 1];
+ SiS_SetReg(SiS_Pr, SiS_Pr->SiS_P3c4, i, SRdata);
+ }
+}
+
+/*********************************************/
+/* MISC */
+/*********************************************/
+
+static void
+SiS_SetMiscRegs(struct SiS_Private *SiS_Pr, unsigned short StandTableIndex)
+{
+ unsigned char Miscdata = SiS_Pr->SiS_StandTable[StandTableIndex].MISC;
+
+ SiS_SetRegByte(SiS_Pr, SiS_Pr->SiS_P3c2, Miscdata);
+}
+
+/*********************************************/
+/* CRTC */
+/*********************************************/
+
+static void
+SiS_SetCRTCRegs(struct SiS_Private *SiS_Pr, unsigned short StandTableIndex)
+{
+ unsigned char CRTCdata;
+ unsigned short i;
+
+ SiS_SetRegAND(SiS_Pr, SiS_Pr->SiS_P3d4, 0x11, 0x7f);
+
+ for (i = 0; i <= 0x18; i++) {
+ CRTCdata = SiS_Pr->SiS_StandTable[StandTableIndex].CRTC[i];
+ SiS_SetReg(SiS_Pr, SiS_Pr->SiS_P3d4, i, CRTCdata);
+ }
+}
+
+/*********************************************/
+/* ATT */
+/*********************************************/
+
+static void
+SiS_SetATTRegs(struct SiS_Private *SiS_Pr, unsigned short StandTableIndex)
+{
+ unsigned char ARdata;
+ unsigned short i;
+
+ for (i = 0; i <= 0x13; i++) {
+ ARdata = SiS_Pr->SiS_StandTable[StandTableIndex].ATTR[i];
+ SiS_GetRegByte(SiS_Pr, SiS_Pr->SiS_P3da);
+ SiS_SetRegByte(SiS_Pr, SiS_Pr->SiS_P3c0, i);
+ SiS_SetRegByte(SiS_Pr, SiS_Pr->SiS_P3c0, ARdata);
+ }
+ SiS_GetRegByte(SiS_Pr, SiS_Pr->SiS_P3da);
+ SiS_SetRegByte(SiS_Pr, SiS_Pr->SiS_P3c0, 0x14);
+ SiS_SetRegByte(SiS_Pr, SiS_Pr->SiS_P3c0, 0x00);
+
+ SiS_GetRegByte(SiS_Pr, SiS_Pr->SiS_P3da);
+ SiS_SetRegByte(SiS_Pr, SiS_Pr->SiS_P3c0, 0x20);
+ SiS_GetRegByte(SiS_Pr, SiS_Pr->SiS_P3da);
+}
+
+/*********************************************/
+/* GRC */
+/*********************************************/
+
+static void
+SiS_SetGRCRegs(struct SiS_Private *SiS_Pr, unsigned short StandTableIndex)
+{
+ unsigned char GRdata;
+ unsigned short i;
+
+ for (i = 0; i <= 0x08; i++) {
+ GRdata = SiS_Pr->SiS_StandTable[StandTableIndex].GRC[i];
+ SiS_SetReg(SiS_Pr, SiS_Pr->SiS_P3ce, i, GRdata);
+ }
+
+ if (SiS_Pr->SiS_ModeType > ModeVGA) {
+ /* 256 color disable */
+ SiS_SetRegAND(SiS_Pr, SiS_Pr->SiS_P3ce, 0x05, 0xBF);
+ }
+}
+
+/*********************************************/
+/* CLEAR EXTENDED REGISTERS */
+/*********************************************/
+
+static void SiS_ClearExt1Regs(struct SiS_Private *SiS_Pr, unsigned short ModeNo)
+{
+ int i;
+
+ for (i = 0x0A; i <= 0x0E; i++) {
+ SiS_SetReg(SiS_Pr, SiS_Pr->SiS_P3c4, i, 0x00);
+ }
+
+ SiS_SetRegAND(SiS_Pr, SiS_Pr->SiS_P3c4, 0x37, 0xFE);
+}
+
+/*********************************************/
+/* Get rate index */
+/*********************************************/
+
+static unsigned short
+SiS_GetRatePtr(struct SiS_Private *SiS_Pr, unsigned short ModeNo,
+ unsigned short ModeIdIndex)
+{
+ unsigned short rrti, i, index, temp;
+
+ if (ModeNo <= 0x13)
+ return 0xFFFF;
+
+ index = SiS_GetReg(SiS_Pr, SiS_Pr->SiS_P3d4, 0x33) & 0x0F;
+ if (index > 0)
+ index--;
+
+ rrti = SiS_Pr->SiS_EModeIDTable[ModeIdIndex].REFindex;
+ ModeNo = SiS_Pr->SiS_RefIndex[rrti].ModeID;
+
+ i = 0;
+ do {
+ if (SiS_Pr->SiS_RefIndex[rrti + i].ModeID != ModeNo)
+ break;
+
+ temp =
+ SiS_Pr->SiS_RefIndex[rrti + i].Ext_InfoFlag & ModeTypeMask;
+ if (temp < SiS_Pr->SiS_ModeType)
+ break;
+
+ i++;
+ index--;
+ } while (index != 0xFFFF);
+
+ i--;
+
+ return (rrti + i);
+}
+
+/*********************************************/
+/* SYNC */
+/*********************************************/
+
+static void SiS_SetCRT1Sync(struct SiS_Private *SiS_Pr, unsigned short rrti)
+{
+ unsigned short sync = SiS_Pr->SiS_RefIndex[rrti].Ext_InfoFlag >> 8;
+ sync &= 0xC0;
+ sync |= 0x2f;
+ SiS_SetRegByte(SiS_Pr, SiS_Pr->SiS_P3c2, sync);
+}
+
+/*********************************************/
+/* CRTC/2 */
+/*********************************************/
+
+static void
+SiS_SetCRT1CRTC(struct SiS_Private *SiS_Pr, unsigned short ModeNo,
+ unsigned short ModeIdIndex, unsigned short rrti)
+{
+ unsigned char index;
+ unsigned short temp, i, j, modeflag;
+
+ SiS_SetRegAND(SiS_Pr, SiS_Pr->SiS_P3d4, 0x11, 0x7f);
+
+ modeflag = SiS_Pr->SiS_EModeIDTable[ModeIdIndex].Ext_ModeFlag;
+
+ index = SiS_Pr->SiS_RefIndex[rrti].Ext_CRT1CRTC;
+
+ for (i = 0, j = 0; i <= 7; i++, j++) {
+ SiS_SetReg(SiS_Pr, SiS_Pr->SiS_P3d4, j,
+ SiS_Pr->SiS_CRT1Table[index].CR[i]);
+ }
+ for (j = 0x10; i <= 10; i++, j++) {
+ SiS_SetReg(SiS_Pr, SiS_Pr->SiS_P3d4, j,
+ SiS_Pr->SiS_CRT1Table[index].CR[i]);
+ }
+ for (j = 0x15; i <= 12; i++, j++) {
+ SiS_SetReg(SiS_Pr, SiS_Pr->SiS_P3d4, j,
+ SiS_Pr->SiS_CRT1Table[index].CR[i]);
+ }
+ for (j = 0x0A; i <= 15; i++, j++) {
+ SiS_SetReg(SiS_Pr, SiS_Pr->SiS_P3c4, j,
+ SiS_Pr->SiS_CRT1Table[index].CR[i]);
+ }
+
+ temp = SiS_Pr->SiS_CRT1Table[index].CR[16] & 0xE0;
+ SiS_SetReg(SiS_Pr, SiS_Pr->SiS_P3c4, 0x0E, temp);
+
+ temp = ((SiS_Pr->SiS_CRT1Table[index].CR[16]) & 0x01) << 5;
+ if (modeflag & DoubleScanMode)
+ temp |= 0x80;
+ SiS_SetRegANDOR(SiS_Pr, SiS_Pr->SiS_P3d4, 0x09, 0x5F, temp);
+
+ if (SiS_Pr->SiS_ModeType > ModeVGA)
+ SiS_SetReg(SiS_Pr, SiS_Pr->SiS_P3d4, 0x14, 0x4F);
+}
+
+/*********************************************/
+/* OFFSET & PITCH */
+/*********************************************/
+/* (partly overruled by SetPitch() in XF86) */
+/*********************************************/
+
+static void
+SiS_SetCRT1Offset(struct SiS_Private *SiS_Pr, unsigned short ModeNo,
+ unsigned short ModeIdIndex, unsigned short rrti)
+{
+ unsigned short du = SiS_GetOffset(SiS_Pr, ModeNo, ModeIdIndex, rrti);
+ unsigned short infoflag = SiS_Pr->SiS_RefIndex[rrti].Ext_InfoFlag;
+ unsigned short temp;
+
+ temp = (du >> 8) & 0x0f;
+ SiS_SetRegANDOR(SiS_Pr, SiS_Pr->SiS_P3c4, 0x0E, 0xF0, temp);
+
+ SiS_SetReg(SiS_Pr, SiS_Pr->SiS_P3d4, 0x13, (du & 0xFF));
+
+ if (infoflag & InterlaceMode)
+ du >>= 1;
+
+ du <<= 5;
+ temp = (du >> 8) & 0xff;
+ if (du & 0xff)
+ temp++;
+ temp++;
+ SiS_SetReg(SiS_Pr, SiS_Pr->SiS_P3c4, 0x10, temp);
+}
+
+/*********************************************/
+/* VCLK */
+/*********************************************/
+
+static void
+SiS_SetCRT1VCLK(struct SiS_Private *SiS_Pr, unsigned short ModeNo,
+ unsigned short rrti)
+{
+ unsigned short index = SiS_Pr->SiS_RefIndex[rrti].Ext_CRTVCLK;
+ unsigned short clka = SiS_Pr->SiS_VCLKData[index].SR2B;
+ unsigned short clkb = SiS_Pr->SiS_VCLKData[index].SR2C;
+
+ SiS_SetRegAND(SiS_Pr, SiS_Pr->SiS_P3c4, 0x31, 0xCF);
+
+ SiS_SetReg(SiS_Pr, SiS_Pr->SiS_P3c4, 0x2B, clka);
+ SiS_SetReg(SiS_Pr, SiS_Pr->SiS_P3c4, 0x2C, clkb);
+ SiS_SetReg(SiS_Pr, SiS_Pr->SiS_P3c4, 0x2D, 0x01);
+}
+
+/*********************************************/
+/* FIFO */
+/*********************************************/
+
+static void
+SiS_SetCRT1FIFO_310(struct SiS_Private *SiS_Pr, unsigned short ModeNo,
+ unsigned short mi)
+{
+ unsigned short modeflag = SiS_Pr->SiS_EModeIDTable[mi].Ext_ModeFlag;
+
+ /* disable auto-threshold */
+ SiS_SetRegAND(SiS_Pr, SiS_Pr->SiS_P3c4, 0x3D, 0xFE);
+
+ SiS_SetReg(SiS_Pr, SiS_Pr->SiS_P3c4, 0x08, 0xAE);
+ SiS_SetRegAND(SiS_Pr, SiS_Pr->SiS_P3c4, 0x09, 0xF0);
+
+ if (ModeNo <= 0x13)
+ return;
+
+ if ((!(modeflag & DoubleScanMode)) || (!(modeflag & HalfDCLK))) {
+ SiS_SetReg(SiS_Pr, SiS_Pr->SiS_P3c4, 0x08, 0x34);
+ SiS_SetRegOR(SiS_Pr, SiS_Pr->SiS_P3c4, 0x3D, 0x01);
+ }
+}
+
+/*********************************************/
+/* MODE REGISTERS */
+/*********************************************/
+
+static void
+SiS_SetVCLKState(struct SiS_Private *SiS_Pr, unsigned short ModeNo,
+ unsigned short rrti)
+{
+ unsigned short data = 0, VCLK = 0, index = 0;
+
+ if (ModeNo > 0x13) {
+ index = SiS_Pr->SiS_RefIndex[rrti].Ext_CRTVCLK;
+ VCLK = SiS_Pr->SiS_VCLKData[index].CLOCK;
+ }
+
+ if (VCLK >= 166)
+ data |= 0x0c;
+ SiS_SetRegANDOR(SiS_Pr, SiS_Pr->SiS_P3c4, 0x32, 0xf3, data);
+
+ if (VCLK >= 166)
+ SiS_SetRegAND(SiS_Pr, SiS_Pr->SiS_P3c4, 0x1f, 0xe7);
+
+ /* DAC speed */
+ data = 0x03;
+ if (VCLK >= 260)
+ data = 0x00;
+ else if (VCLK >= 160)
+ data = 0x01;
+ else if (VCLK >= 135)
+ data = 0x02;
+
+ SiS_SetRegANDOR(SiS_Pr, SiS_Pr->SiS_P3c4, 0x07, 0xF8, data);
+}
+
+static void
+SiS_SetCRT1ModeRegs(struct SiS_Private *SiS_Pr, unsigned short ModeNo,
+ unsigned short ModeIdIndex, unsigned short rrti)
+{
+ unsigned short data, infoflag = 0, modeflag;
+
+ if (ModeNo <= 0x13)
+ modeflag = SiS_Pr->SiS_SModeIDTable[ModeIdIndex].St_ModeFlag;
+ else {
+ modeflag = SiS_Pr->SiS_EModeIDTable[ModeIdIndex].Ext_ModeFlag;
+ infoflag = SiS_Pr->SiS_RefIndex[rrti].Ext_InfoFlag;
+ }
+
+ /* Disable DPMS */
+ SiS_SetRegAND(SiS_Pr, SiS_Pr->SiS_P3c4, 0x1F, 0x3F);
+
+ data = 0;
+ if (ModeNo > 0x13) {
+ if (SiS_Pr->SiS_ModeType > ModeEGA) {
+ data |= 0x02;
+ data |= ((SiS_Pr->SiS_ModeType - ModeVGA) << 2);
+ }
+ if (infoflag & InterlaceMode)
+ data |= 0x20;
+ }
+ SiS_SetRegANDOR(SiS_Pr, SiS_Pr->SiS_P3c4, 0x06, 0xC0, data);
+
+ data = 0;
+ if (infoflag & InterlaceMode) {
+ /* data = (Hsync / 8) - ((Htotal / 8) / 2) + 3 */
+ unsigned short hrs =
+ (SiS_GetReg(SiS_Pr, SiS_Pr->SiS_P3d4, 0x04) |
+ ((SiS_GetReg(SiS_Pr, SiS_Pr->SiS_P3c4, 0x0b) & 0xc0) << 2))
+ - 3;
+ unsigned short hto =
+ (SiS_GetReg(SiS_Pr, SiS_Pr->SiS_P3d4, 0x00) |
+ ((SiS_GetReg(SiS_Pr, SiS_Pr->SiS_P3c4, 0x0b) & 0x03) << 8))
+ + 5;
+ data = hrs - (hto >> 1) + 3;
+ }
+ SiS_SetReg(SiS_Pr, SiS_Pr->SiS_P3d4, 0x19, (data & 0xFF));
+ SiS_SetRegANDOR(SiS_Pr, SiS_Pr->SiS_P3d4, 0x1a, 0xFC, (data >> 8));
+
+ if (modeflag & HalfDCLK)
+ SiS_SetRegOR(SiS_Pr, SiS_Pr->SiS_P3c4, 0x01, 0x08);
+
+ data = 0;
+ if (modeflag & LineCompareOff)
+ data = 0x08;
+ SiS_SetRegANDOR(SiS_Pr, SiS_Pr->SiS_P3c4, 0x0F, 0xB7, data);
+
+ if ((SiS_Pr->SiS_ModeType == ModeEGA) && (ModeNo > 0x13))
+ SiS_SetRegOR(SiS_Pr, SiS_Pr->SiS_P3c4, 0x0F, 0x40);
+
+ SiS_SetRegAND(SiS_Pr, SiS_Pr->SiS_P3c4, 0x31, 0xfb);
+
+ data = 0x60;
+ if (SiS_Pr->SiS_ModeType != ModeText) {
+ data ^= 0x60;
+ if (SiS_Pr->SiS_ModeType != ModeEGA)
+ data ^= 0xA0;
+ }
+ SiS_SetRegANDOR(SiS_Pr, SiS_Pr->SiS_P3c4, 0x21, 0x1F, data);
+
+ SiS_SetVCLKState(SiS_Pr, ModeNo, rrti);
+
+ if (SiS_GetReg(SiS_Pr, SiS_Pr->SiS_P3d4, 0x31) & 0x40)
+ SiS_SetReg(SiS_Pr, SiS_Pr->SiS_P3d4, 0x52, 0x2c);
+ else
+ SiS_SetReg(SiS_Pr, SiS_Pr->SiS_P3d4, 0x52, 0x6c);
+}
+
+/*********************************************/
+/* LOAD DAC */
+/*********************************************/
+
+static void
+SiS_WriteDAC(struct SiS_Private *SiS_Pr, unsigned long DACData,
+ unsigned short shiftflag, unsigned short dl, unsigned short ah,
+ unsigned short al, unsigned short dh)
+{
+ unsigned short d1, d2, d3;
+
+ switch (dl) {
+ case 0:
+ d1 = dh;
+ d2 = ah;
+ d3 = al;
+ break;
+ case 1:
+ d1 = ah;
+ d2 = al;
+ d3 = dh;
+ break;
+ default:
+ d1 = al;
+ d2 = dh;
+ d3 = ah;
+ }
+ SiS_SetRegByte(SiS_Pr, DACData, (d1 << shiftflag));
+ SiS_SetRegByte(SiS_Pr, DACData, (d2 << shiftflag));
+ SiS_SetRegByte(SiS_Pr, DACData, (d3 << shiftflag));
+}
+
+static void
+SiS_LoadDAC(struct SiS_Private *SiS_Pr, unsigned short ModeNo,
+ unsigned short mi)
+{
+ unsigned short data, data2, time, i, j, k, m, n, o;
+ unsigned short si, di, bx, sf;
+ unsigned long DACAddr, DACData;
+ const unsigned char *table = NULL;
+
+ if (ModeNo < 0x13)
+ data = SiS_Pr->SiS_SModeIDTable[mi].St_ModeFlag;
+ else
+ data = SiS_Pr->SiS_EModeIDTable[mi].Ext_ModeFlag;
+
+ data &= DACInfoFlag;
+
+ j = time = 64;
+ if (data == 0x00)
+ table = SiS_MDA_DAC;
+ else if (data == 0x08)
+ table = SiS_CGA_DAC;
+ else if (data == 0x10)
+ table = SiS_EGA_DAC;
+ else {
+ j = 16;
+ time = 256;
+ table = SiS_VGA_DAC;
+ }
+
+ DACAddr = SiS_Pr->SiS_P3c8;
+ DACData = SiS_Pr->SiS_P3c9;
+ sf = 0;
+ SiS_SetRegByte(SiS_Pr, SiS_Pr->SiS_P3c6, 0xFF);
+
+ SiS_SetRegByte(SiS_Pr, DACAddr, 0x00);
+
+ for (i = 0; i < j; i++) {
+ data = table[i];
+ for (k = 0; k < 3; k++) {
+ data2 = 0;
+ if (data & 0x01)
+ data2 += 0x2A;
+ if (data & 0x02)
+ data2 += 0x15;
+ SiS_SetRegByte(SiS_Pr, DACData, (data2 << sf));
+ data >>= 2;
+ }
+ }
+
+ if (time == 256) {
+ for (i = 16; i < 32; i++) {
+ data = table[i] << sf;
+ for (k = 0; k < 3; k++)
+ SiS_SetRegByte(SiS_Pr, DACData, data);
+ }
+ si = 32;
+ for (m = 0; m < 9; m++) {
+ di = si;
+ bx = si + 4;
+ for (n = 0; n < 3; n++) {
+ for (o = 0; o < 5; o++) {
+ SiS_WriteDAC(SiS_Pr, DACData, sf, n,
+ table[di], table[bx],
+ table[si]);
+ si++;
+ }
+ si -= 2;
+ for (o = 0; o < 3; o++) {
+ SiS_WriteDAC(SiS_Pr, DACData, sf, n,
+ table[di], table[si],
+ table[bx]);
+ si--;
+ }
+ }
+ si += 5;
+ }
+ }
+}
+
+/*********************************************/
+/* SET CRT1 REGISTER GROUP */
+/*********************************************/
+
+static void
+SiS_SetCRT1Group(struct SiS_Private *SiS_Pr, unsigned short ModeNo,
+ unsigned short ModeIdIndex)
+{
+ unsigned short StandTableIndex, rrti;
+
+ SiS_Pr->SiS_CRT1Mode = ModeNo;
+
+ if (ModeNo <= 0x13)
+ StandTableIndex = 0;
+ else
+ StandTableIndex = 1;
+
+ SiS_ResetSegmentRegisters(SiS_Pr);
+ SiS_SetSeqRegs(SiS_Pr, StandTableIndex);
+ SiS_SetMiscRegs(SiS_Pr, StandTableIndex);
+ SiS_SetCRTCRegs(SiS_Pr, StandTableIndex);
+ SiS_SetATTRegs(SiS_Pr, StandTableIndex);
+ SiS_SetGRCRegs(SiS_Pr, StandTableIndex);
+ SiS_ClearExt1Regs(SiS_Pr, ModeNo);
+
+ rrti = SiS_GetRatePtr(SiS_Pr, ModeNo, ModeIdIndex);
+
+ if (rrti != 0xFFFF) {
+ SiS_SetCRT1Sync(SiS_Pr, rrti);
+ SiS_SetCRT1CRTC(SiS_Pr, ModeNo, ModeIdIndex, rrti);
+ SiS_SetCRT1Offset(SiS_Pr, ModeNo, ModeIdIndex, rrti);
+ SiS_SetCRT1VCLK(SiS_Pr, ModeNo, rrti);
+ }
+
+ SiS_SetCRT1FIFO_310(SiS_Pr, ModeNo, ModeIdIndex);
+
+ SiS_SetCRT1ModeRegs(SiS_Pr, ModeNo, ModeIdIndex, rrti);
+
+ SiS_LoadDAC(SiS_Pr, ModeNo, ModeIdIndex);
+
+ SiS_DisplayOn(SiS_Pr);
+}
+
+/*********************************************/
+/* SiSSetMode() */
+/*********************************************/
+
+int SiSUSBSetMode(struct SiS_Private *SiS_Pr, unsigned short ModeNo)
+{
+ unsigned short ModeIdIndex;
+ unsigned long BaseAddr = SiS_Pr->IOAddress;
+
+ SiSUSB_InitPtr(SiS_Pr);
+ SiSUSBRegInit(SiS_Pr, BaseAddr);
+ SiS_GetSysFlags(SiS_Pr);
+
+ if (!(SiS_SearchModeID(SiS_Pr, &ModeNo, &ModeIdIndex)))
+ return 0;
+
+ SiS_SetReg(SiS_Pr, SiS_Pr->SiS_P3c4, 0x05, 0x86);
+
+ SiSInitPCIetc(SiS_Pr);
+
+ ModeNo &= 0x7f;
+
+ SiS_Pr->SiS_ModeType =
+ SiS_Pr->SiS_EModeIDTable[ModeIdIndex].Ext_ModeFlag & ModeTypeMask;
+
+ SiS_Pr->SiS_SetFlag = LowModeTests;
+
+ /* Set mode on CRT1 */
+ SiS_SetCRT1Group(SiS_Pr, ModeNo, ModeIdIndex);
+
+ SiS_HandleCRT1(SiS_Pr);
+
+ SiS_DisplayOn(SiS_Pr);
+ SiS_SetRegByte(SiS_Pr, SiS_Pr->SiS_P3c6, 0xFF);
+
+ /* Store mode number */
+ SiS_SetReg(SiS_Pr, SiS_Pr->SiS_P3d4, 0x34, ModeNo);
+
+ return 1;
+}
+
+int SiSUSBSetVESAMode(struct SiS_Private *SiS_Pr, unsigned short VModeNo)
+{
+ unsigned short ModeNo = 0;
+ int i;
+
+ SiSUSB_InitPtr(SiS_Pr);
+
+ if (VModeNo == 0x03) {
+
+ ModeNo = 0x03;
+
+ } else {
+
+ i = 0;
+ do {
+
+ if (SiS_Pr->SiS_EModeIDTable[i].Ext_VESAID == VModeNo) {
+ ModeNo = SiS_Pr->SiS_EModeIDTable[i].Ext_ModeID;
+ break;
+ }
+
+ } while (SiS_Pr->SiS_EModeIDTable[i++].Ext_ModeID != 0xff);
+
+ }
+
+ if (!ModeNo)
+ return 0;
+
+ return SiSUSBSetMode(SiS_Pr, ModeNo);
+}
diff --git a/drivers/usb/misc/sisusbvga/sisusb_init.h b/drivers/usb/misc/sisusbvga/sisusb_init.h
new file mode 100644
index 000000000..b5cd77ae9
--- /dev/null
+++ b/drivers/usb/misc/sisusbvga/sisusb_init.h
@@ -0,0 +1,180 @@
+/* SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) */
+/* $XFree86$ */
+/* $XdotOrg$ */
+/*
+ * Data and prototypes for init.c
+ *
+ * Copyright (C) 2001-2005 by Thomas Winischhofer, Vienna, Austria
+ *
+ * If distributed as part of the Linux kernel, the following license terms
+ * apply:
+ *
+ * * 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; either version 2 of the named License,
+ * * or any later version.
+ * *
+ * * This program is distributed in the hope that it will be useful,
+ * * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * * GNU General Public License for more details.
+ * *
+ * * You should have received a copy of the GNU General Public License
+ * * along with this program; if not, write to the Free Software
+ * * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA
+ *
+ * Otherwise, the following license terms apply:
+ *
+ * * Redistribution and use in source and binary forms, with or without
+ * * modification, are permitted provided that the following conditions
+ * * are met:
+ * * 1) Redistributions of source code must retain the above copyright
+ * * notice, this list of conditions and the following disclaimer.
+ * * 2) Redistributions in binary form must reproduce the above copyright
+ * * notice, this list of conditions and the following disclaimer in the
+ * * documentation and/or other materials provided with the distribution.
+ * * 3) The name of the author may not be used to endorse or promote products
+ * * derived from this software without specific prior written permission.
+ * *
+ * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Author: Thomas Winischhofer <thomas@winischhofer.net>
+ *
+ */
+
+#ifndef _SISUSB_INIT_H_
+#define _SISUSB_INIT_H_
+
+/* SiS_ModeType */
+#define ModeText 0x00
+#define ModeCGA 0x01
+#define ModeEGA 0x02
+#define ModeVGA 0x03
+#define Mode15Bpp 0x04
+#define Mode16Bpp 0x05
+#define Mode24Bpp 0x06
+#define Mode32Bpp 0x07
+
+#define ModeTypeMask 0x07
+#define IsTextMode 0x07
+
+#define DACInfoFlag 0x0018
+#define MemoryInfoFlag 0x01E0
+#define MemorySizeShift 5
+
+/* modeflag */
+#define Charx8Dot 0x0200
+#define LineCompareOff 0x0400
+#define CRT2Mode 0x0800
+#define HalfDCLK 0x1000
+#define NoSupportSimuTV 0x2000
+#define NoSupportLCDScale 0x4000 /* SiS bridge: No scaling possible (no matter what panel) */
+#define DoubleScanMode 0x8000
+
+/* Infoflag */
+#define SupportTV 0x0008
+#define SupportTV1024 0x0800
+#define SupportCHTV 0x0800
+#define Support64048060Hz 0x0800 /* Special for 640x480 LCD */
+#define SupportHiVision 0x0010
+#define SupportYPbPr750p 0x1000
+#define SupportLCD 0x0020
+#define SupportRAMDAC2 0x0040 /* All (<= 100Mhz) */
+#define SupportRAMDAC2_135 0x0100 /* All except DH (<= 135Mhz) */
+#define SupportRAMDAC2_162 0x0200 /* B, C (<= 162Mhz) */
+#define SupportRAMDAC2_202 0x0400 /* C (<= 202Mhz) */
+#define InterlaceMode 0x0080
+#define SyncPP 0x0000
+#define SyncPN 0x4000
+#define SyncNP 0x8000
+#define SyncNN 0xc000
+
+/* SetFlag */
+#define ProgrammingCRT2 0x0001
+#define LowModeTests 0x0002
+#define LCDVESATiming 0x0008
+#define EnableLVDSDDA 0x0010
+#define SetDispDevSwitchFlag 0x0020
+#define CheckWinDos 0x0040
+#define SetDOSMode 0x0080
+
+/* Index in ModeResInfo table */
+#define SIS_RI_320x200 0
+#define SIS_RI_320x240 1
+#define SIS_RI_320x400 2
+#define SIS_RI_400x300 3
+#define SIS_RI_512x384 4
+#define SIS_RI_640x400 5
+#define SIS_RI_640x480 6
+#define SIS_RI_800x600 7
+#define SIS_RI_1024x768 8
+#define SIS_RI_1280x1024 9
+#define SIS_RI_1600x1200 10
+#define SIS_RI_1920x1440 11
+#define SIS_RI_2048x1536 12
+#define SIS_RI_720x480 13
+#define SIS_RI_720x576 14
+#define SIS_RI_1280x960 15
+#define SIS_RI_800x480 16
+#define SIS_RI_1024x576 17
+#define SIS_RI_1280x720 18
+#define SIS_RI_856x480 19
+#define SIS_RI_1280x768 20
+#define SIS_RI_1400x1050 21
+#define SIS_RI_1152x864 22 /* Up to here SiS conforming */
+#define SIS_RI_848x480 23
+#define SIS_RI_1360x768 24
+#define SIS_RI_1024x600 25
+#define SIS_RI_1152x768 26
+#define SIS_RI_768x576 27
+#define SIS_RI_1360x1024 28
+#define SIS_RI_1680x1050 29
+#define SIS_RI_1280x800 30
+#define SIS_RI_1920x1080 31
+#define SIS_RI_960x540 32
+#define SIS_RI_960x600 33
+
+#define SIS_VIDEO_CAPTURE 0x00 - 0x30
+#define SIS_VIDEO_PLAYBACK 0x02 - 0x30
+#define SIS_CRT2_PORT_04 0x04 - 0x30
+
+int SiSUSBSetMode(struct SiS_Private *SiS_Pr, unsigned short ModeNo);
+int SiSUSBSetVESAMode(struct SiS_Private *SiS_Pr, unsigned short VModeNo);
+
+extern int sisusb_setreg(struct sisusb_usb_data *sisusb, u32 port, u8 data);
+extern int sisusb_getreg(struct sisusb_usb_data *sisusb, u32 port, u8 * data);
+extern int sisusb_setidxreg(struct sisusb_usb_data *sisusb, u32 port,
+ u8 index, u8 data);
+extern int sisusb_getidxreg(struct sisusb_usb_data *sisusb, u32 port,
+ u8 index, u8 * data);
+extern int sisusb_setidxregandor(struct sisusb_usb_data *sisusb, u32 port,
+ u8 idx, u8 myand, u8 myor);
+extern int sisusb_setidxregor(struct sisusb_usb_data *sisusb, u32 port,
+ u8 index, u8 myor);
+extern int sisusb_setidxregand(struct sisusb_usb_data *sisusb, u32 port,
+ u8 idx, u8 myand);
+
+void sisusb_delete(struct kref *kref);
+int sisusb_writeb(struct sisusb_usb_data *sisusb, u32 adr, u8 data);
+int sisusb_readb(struct sisusb_usb_data *sisusb, u32 adr, u8 * data);
+int sisusb_copy_memory(struct sisusb_usb_data *sisusb, u8 *src,
+ u32 dest, int length);
+int sisusb_reset_text_mode(struct sisusb_usb_data *sisusb, int init);
+int sisusbcon_do_font_op(struct sisusb_usb_data *sisusb, int set, int slot,
+ u8 * arg, int cmapsz, int ch512, int dorecalc,
+ struct vc_data *c, int fh, int uplock);
+void sisusb_set_cursor(struct sisusb_usb_data *sisusb, unsigned int location);
+int sisusb_console_init(struct sisusb_usb_data *sisusb, int first, int last);
+void sisusb_console_exit(struct sisusb_usb_data *sisusb);
+void sisusb_init_concode(void);
+
+#endif
diff --git a/drivers/usb/misc/sisusbvga/sisusb_struct.h b/drivers/usb/misc/sisusbvga/sisusb_struct.h
new file mode 100644
index 000000000..a86032a26
--- /dev/null
+++ b/drivers/usb/misc/sisusbvga/sisusb_struct.h
@@ -0,0 +1,162 @@
+/* SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) */
+/*
+ * General structure definitions for universal mode switching modules
+ *
+ * Copyright (C) 2001-2005 by Thomas Winischhofer, Vienna, Austria
+ *
+ * If distributed as part of the Linux kernel, the following license terms
+ * apply:
+ *
+ * * 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; either version 2 of the named License,
+ * * or any later version.
+ * *
+ * * This program is distributed in the hope that it will be useful,
+ * * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * * GNU General Public License for more details.
+ * *
+ * * You should have received a copy of the GNU General Public License
+ * * along with this program; if not, write to the Free Software
+ * * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA
+ *
+ * Otherwise, the following license terms apply:
+ *
+ * * Redistribution and use in source and binary forms, with or without
+ * * modification, are permitted provided that the following conditions
+ * * are met:
+ * * 1) Redistributions of source code must retain the above copyright
+ * * notice, this list of conditions and the following disclaimer.
+ * * 2) Redistributions in binary form must reproduce the above copyright
+ * * notice, this list of conditions and the following disclaimer in the
+ * * documentation and/or other materials provided with the distribution.
+ * * 3) The name of the author may not be used to endorse or promote products
+ * * derived from this software without specific prior written permission.
+ * *
+ * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Author: Thomas Winischhofer <thomas@winischhofer.net>
+ *
+ */
+
+#ifndef _SISUSB_STRUCT_H_
+#define _SISUSB_STRUCT_H_
+
+struct SiS_St {
+ unsigned char St_ModeID;
+ unsigned short St_ModeFlag;
+ unsigned char St_StTableIndex;
+ unsigned char St_CRT2CRTC;
+ unsigned char St_ResInfo;
+ unsigned char VB_StTVFlickerIndex;
+ unsigned char VB_StTVEdgeIndex;
+ unsigned char VB_StTVYFilterIndex;
+ unsigned char St_PDC;
+};
+
+struct SiS_StandTable {
+ unsigned char CRT_COLS;
+ unsigned char ROWS;
+ unsigned char CHAR_HEIGHT;
+ unsigned short CRT_LEN;
+ unsigned char SR[4];
+ unsigned char MISC;
+ unsigned char CRTC[0x19];
+ unsigned char ATTR[0x14];
+ unsigned char GRC[9];
+};
+
+struct SiS_StResInfo_S {
+ unsigned short HTotal;
+ unsigned short VTotal;
+};
+
+struct SiS_Ext {
+ unsigned char Ext_ModeID;
+ unsigned short Ext_ModeFlag;
+ unsigned short Ext_VESAID;
+ unsigned char Ext_RESINFO;
+ unsigned char VB_ExtTVFlickerIndex;
+ unsigned char VB_ExtTVEdgeIndex;
+ unsigned char VB_ExtTVYFilterIndex;
+ unsigned char VB_ExtTVYFilterIndexROM661;
+ unsigned char REFindex;
+ signed char ROMMODEIDX661;
+};
+
+struct SiS_Ext2 {
+ unsigned short Ext_InfoFlag;
+ unsigned char Ext_CRT1CRTC;
+ unsigned char Ext_CRTVCLK;
+ unsigned char Ext_CRT2CRTC;
+ unsigned char Ext_CRT2CRTC_NS;
+ unsigned char ModeID;
+ unsigned short XRes;
+ unsigned short YRes;
+ unsigned char Ext_PDC;
+ unsigned char Ext_FakeCRT2CRTC;
+ unsigned char Ext_FakeCRT2Clk;
+};
+
+struct SiS_CRT1Table {
+ unsigned char CR[17];
+};
+
+struct SiS_VCLKData {
+ unsigned char SR2B, SR2C;
+ unsigned short CLOCK;
+};
+
+struct SiS_ModeResInfo {
+ unsigned short HTotal;
+ unsigned short VTotal;
+ unsigned char XChar;
+ unsigned char YChar;
+};
+
+struct SiS_Private {
+ void *sisusb;
+
+ unsigned long IOAddress;
+
+ unsigned long SiS_P3c4;
+ unsigned long SiS_P3d4;
+ unsigned long SiS_P3c0;
+ unsigned long SiS_P3ce;
+ unsigned long SiS_P3c2;
+ unsigned long SiS_P3ca;
+ unsigned long SiS_P3c6;
+ unsigned long SiS_P3c7;
+ unsigned long SiS_P3c8;
+ unsigned long SiS_P3c9;
+ unsigned long SiS_P3cb;
+ unsigned long SiS_P3cc;
+ unsigned long SiS_P3cd;
+ unsigned long SiS_P3da;
+ unsigned long SiS_Part1Port;
+
+ unsigned char SiS_MyCR63;
+ unsigned short SiS_CRT1Mode;
+ unsigned short SiS_ModeType;
+ unsigned short SiS_SetFlag;
+
+ const struct SiS_StandTable *SiS_StandTable;
+ const struct SiS_St *SiS_SModeIDTable;
+ const struct SiS_Ext *SiS_EModeIDTable;
+ const struct SiS_Ext2 *SiS_RefIndex;
+ const struct SiS_CRT1Table *SiS_CRT1Table;
+ const struct SiS_VCLKData *SiS_VCLKData;
+ const struct SiS_ModeResInfo *SiS_ModeResInfo;
+};
+
+#endif
diff --git a/drivers/usb/misc/sisusbvga/sisusb_tables.h b/drivers/usb/misc/sisusbvga/sisusb_tables.h
new file mode 100644
index 000000000..56972f1ec
--- /dev/null
+++ b/drivers/usb/misc/sisusbvga/sisusb_tables.h
@@ -0,0 +1,688 @@
+/* SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause) */
+/* $XFree86$ */
+/* $XdotOrg$ */
+/*
+ * Data tables for init.c
+ *
+ * Copyright (C) 2001-2005 by Thomas Winischhofer, Vienna, Austria
+ *
+ * If distributed as part of the Linux kernel, the following license terms
+ * apply:
+ *
+ * * 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; either version 2 of the named License,
+ * * or any later version.
+ * *
+ * * This program is distributed in the hope that it will be useful,
+ * * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * * GNU General Public License for more details.
+ * *
+ * * You should have received a copy of the GNU General Public License
+ * * along with this program; if not, write to the Free Software
+ * * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA
+ *
+ * Otherwise, the following license terms apply:
+ *
+ * * Redistribution and use in source and binary forms, with or without
+ * * modification, are permitted provided that the following conditions
+ * * are met:
+ * * 1) Redistributions of source code must retain the above copyright
+ * * notice, this list of conditions and the following disclaimer.
+ * * 2) Redistributions in binary form must reproduce the above copyright
+ * * notice, this list of conditions and the following disclaimer in the
+ * * documentation and/or other materials provided with the distribution.
+ * * 3) The name of the author may not be used to endorse or promote products
+ * * derived from this software without specific prior written permission.
+ * *
+ * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Author: Thomas Winischhofer <thomas@winischhofer.net>
+ *
+ */
+
+#ifndef _SISUSB_TABLES_H_
+#define _SISUSB_TABLES_H_
+
+static const unsigned char SiS_MDA_DAC[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15,
+ 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15,
+ 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15,
+ 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15, 0x15,
+ 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F
+};
+
+static const unsigned char SiS_CGA_DAC[] = {
+ 0x00, 0x10, 0x04, 0x14, 0x01, 0x11, 0x09, 0x15,
+ 0x00, 0x10, 0x04, 0x14, 0x01, 0x11, 0x09, 0x15,
+ 0x2A, 0x3A, 0x2E, 0x3E, 0x2B, 0x3B, 0x2F, 0x3F,
+ 0x2A, 0x3A, 0x2E, 0x3E, 0x2B, 0x3B, 0x2F, 0x3F,
+ 0x00, 0x10, 0x04, 0x14, 0x01, 0x11, 0x09, 0x15,
+ 0x00, 0x10, 0x04, 0x14, 0x01, 0x11, 0x09, 0x15,
+ 0x2A, 0x3A, 0x2E, 0x3E, 0x2B, 0x3B, 0x2F, 0x3F,
+ 0x2A, 0x3A, 0x2E, 0x3E, 0x2B, 0x3B, 0x2F, 0x3F
+};
+
+static const unsigned char SiS_EGA_DAC[] = {
+ 0x00, 0x10, 0x04, 0x14, 0x01, 0x11, 0x05, 0x15,
+ 0x20, 0x30, 0x24, 0x34, 0x21, 0x31, 0x25, 0x35,
+ 0x08, 0x18, 0x0C, 0x1C, 0x09, 0x19, 0x0D, 0x1D,
+ 0x28, 0x38, 0x2C, 0x3C, 0x29, 0x39, 0x2D, 0x3D,
+ 0x02, 0x12, 0x06, 0x16, 0x03, 0x13, 0x07, 0x17,
+ 0x22, 0x32, 0x26, 0x36, 0x23, 0x33, 0x27, 0x37,
+ 0x0A, 0x1A, 0x0E, 0x1E, 0x0B, 0x1B, 0x0F, 0x1F,
+ 0x2A, 0x3A, 0x2E, 0x3E, 0x2B, 0x3B, 0x2F, 0x3F
+};
+
+static const unsigned char SiS_VGA_DAC[] = {
+ 0x00, 0x10, 0x04, 0x14, 0x01, 0x11, 0x09, 0x15,
+ 0x2A, 0x3A, 0x2E, 0x3E, 0x2B, 0x3B, 0x2F, 0x3F,
+ 0x00, 0x05, 0x08, 0x0B, 0x0E, 0x11, 0x14, 0x18,
+ 0x1C, 0x20, 0x24, 0x28, 0x2D, 0x32, 0x38, 0x3F,
+ 0x00, 0x10, 0x1F, 0x2F, 0x3F, 0x1F, 0x27, 0x2F,
+ 0x37, 0x3F, 0x2D, 0x31, 0x36, 0x3A, 0x3F, 0x00,
+ 0x07, 0x0E, 0x15, 0x1C, 0x0E, 0x11, 0x15, 0x18,
+ 0x1C, 0x14, 0x16, 0x18, 0x1A, 0x1C, 0x00, 0x04,
+ 0x08, 0x0C, 0x10, 0x08, 0x0A, 0x0C, 0x0E, 0x10,
+ 0x0B, 0x0C, 0x0D, 0x0F, 0x10
+};
+
+static const struct SiS_St SiSUSB_SModeIDTable[] = {
+ {0x03, 0x0010, 0x18, 0x02, 0x02, 0x00, 0x01, 0x03, 0x40},
+ {0xff, 0x0000, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
+};
+
+static const struct SiS_ModeResInfo SiSUSB_ModeResInfo[] = {
+ {320, 200, 8, 8}, /* 0x00 */
+ {320, 240, 8, 8}, /* 0x01 */
+ {320, 400, 8, 8}, /* 0x02 */
+ {400, 300, 8, 8}, /* 0x03 */
+ {512, 384, 8, 8}, /* 0x04 */
+ {640, 400, 8, 16}, /* 0x05 */
+ {640, 480, 8, 16}, /* 0x06 */
+ {800, 600, 8, 16}, /* 0x07 */
+ {1024, 768, 8, 16}, /* 0x08 */
+ {1280, 1024, 8, 16}, /* 0x09 */
+ {1600, 1200, 8, 16}, /* 0x0a */
+ {1920, 1440, 8, 16}, /* 0x0b */
+ {2048, 1536, 8, 16}, /* 0x0c */
+ {720, 480, 8, 16}, /* 0x0d */
+ {720, 576, 8, 16}, /* 0x0e */
+ {1280, 960, 8, 16}, /* 0x0f */
+ {800, 480, 8, 16}, /* 0x10 */
+ {1024, 576, 8, 16}, /* 0x11 */
+ {1280, 720, 8, 16}, /* 0x12 */
+ {856, 480, 8, 16}, /* 0x13 */
+ {1280, 768, 8, 16}, /* 0x14 */
+ {1400, 1050, 8, 16}, /* 0x15 */
+ {1152, 864, 8, 16}, /* 0x16 */
+ {848, 480, 8, 16}, /* 0x17 */
+ {1360, 768, 8, 16}, /* 0x18 */
+ {1024, 600, 8, 16}, /* 0x19 */
+ {1152, 768, 8, 16}, /* 0x1a */
+ {768, 576, 8, 16}, /* 0x1b */
+ {1360, 1024, 8, 16}, /* 0x1c */
+ {1680, 1050, 8, 16}, /* 0x1d */
+ {1280, 800, 8, 16}, /* 0x1e */
+ {1920, 1080, 8, 16}, /* 0x1f */
+ {960, 540, 8, 16}, /* 0x20 */
+ {960, 600, 8, 16} /* 0x21 */
+};
+
+static const struct SiS_StandTable SiSUSB_StandTable[] = {
+ /* MD_3_400 - mode 0x03 - 400 */
+ {
+ 0x50, 0x18, 0x10, 0x1000,
+ {0x00, 0x03, 0x00, 0x02},
+ 0x67,
+ {0x5f, 0x4f, 0x50, 0x82, 0x55, 0x81, 0xbf, 0x1f,
+ 0x00, 0x4f, 0x0d, 0x0e, 0x00, 0x00, 0x00, 0x00,
+ 0x9c, 0x8e, 0x8f, 0x28, 0x1f, 0x96, 0xb9, 0xa3,
+ 0xff},
+ {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x14, 0x07,
+ 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f,
+ 0x0c, 0x00, 0x0f, 0x08},
+ {0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x0e, 0x00, 0xff}
+ },
+ /* Generic for VGA and higher */
+ {
+ 0x00, 0x00, 0x00, 0x0000,
+ {0x01, 0x0f, 0x00, 0x0e},
+ 0x23,
+ {0x5f, 0x4f, 0x50, 0x82, 0x54, 0x80, 0x0b, 0x3e,
+ 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xea, 0x8c, 0xdf, 0x28, 0x40, 0xe7, 0x04, 0xa3,
+ 0xff},
+ {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+ 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
+ 0x01, 0x00, 0x00, 0x00},
+ {0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x05, 0x0f, 0xff}
+ }
+};
+
+static const struct SiS_Ext SiSUSB_EModeIDTable[] = {
+ {0x2e, 0x0a1b, 0x0101, SIS_RI_640x480, 0x00, 0x00, 0x05, 0x05, 0x08, 2}, /* 640x480x8 */
+ {0x2f, 0x0a1b, 0x0100, SIS_RI_640x400, 0x00, 0x00, 0x05, 0x05, 0x10, 0}, /* 640x400x8 */
+ {0x30, 0x2a1b, 0x0103, SIS_RI_800x600, 0x00, 0x00, 0x07, 0x06, 0x00, 3}, /* 800x600x8 */
+ {0x31, 0x4a1b, 0x0000, SIS_RI_720x480, 0x00, 0x00, 0x06, 0x06, 0x11, -1}, /* 720x480x8 */
+ {0x32, 0x4a1b, 0x0000, SIS_RI_720x576, 0x00, 0x00, 0x06, 0x06, 0x12, -1}, /* 720x576x8 */
+ {0x33, 0x4a1d, 0x0000, SIS_RI_720x480, 0x00, 0x00, 0x06, 0x06, 0x11, -1}, /* 720x480x16 */
+ {0x34, 0x6a1d, 0x0000, SIS_RI_720x576, 0x00, 0x00, 0x06, 0x06, 0x12, -1}, /* 720x576x16 */
+ {0x35, 0x4a1f, 0x0000, SIS_RI_720x480, 0x00, 0x00, 0x06, 0x06, 0x11, -1}, /* 720x480x32 */
+ {0x36, 0x6a1f, 0x0000, SIS_RI_720x576, 0x00, 0x00, 0x06, 0x06, 0x12, -1}, /* 720x576x32 */
+ {0x38, 0x0a1b, 0x0105, SIS_RI_1024x768, 0x00, 0x00, 0x08, 0x07, 0x13, 4}, /* 1024x768x8 */
+ {0x3a, 0x0e3b, 0x0107, SIS_RI_1280x1024, 0x00, 0x00, 0x00, 0x00, 0x2f, 8}, /* 1280x1024x8 */
+ {0x41, 0x9a1d, 0x010e, SIS_RI_320x200, 0x00, 0x00, 0x04, 0x04, 0x1a, 0}, /* 320x200x16 */
+ {0x44, 0x0a1d, 0x0111, SIS_RI_640x480, 0x00, 0x00, 0x05, 0x05, 0x08, 2}, /* 640x480x16 */
+ {0x47, 0x2a1d, 0x0114, SIS_RI_800x600, 0x00, 0x00, 0x07, 0x06, 0x00, 3}, /* 800x600x16 */
+ {0x4a, 0x0a3d, 0x0117, SIS_RI_1024x768, 0x00, 0x00, 0x08, 0x07, 0x13, 4}, /* 1024x768x16 */
+ {0x4d, 0x0e7d, 0x011a, SIS_RI_1280x1024, 0x00, 0x00, 0x00, 0x00, 0x2f, 8}, /* 1280x1024x16 */
+ {0x50, 0x9a1b, 0x0132, SIS_RI_320x240, 0x00, 0x00, 0x04, 0x04, 0x1b, 2}, /* 320x240x8 */
+ {0x51, 0xba1b, 0x0133, SIS_RI_400x300, 0x00, 0x00, 0x07, 0x07, 0x1c, 3}, /* 400x300x8 */
+ {0x52, 0xba1b, 0x0134, SIS_RI_512x384, 0x00, 0x00, 0x00, 0x00, 0x1d, 4}, /* 512x384x8 */
+ {0x56, 0x9a1d, 0x0135, SIS_RI_320x240, 0x00, 0x00, 0x04, 0x04, 0x1b, 2}, /* 320x240x16 */
+ {0x57, 0xba1d, 0x0136, SIS_RI_400x300, 0x00, 0x00, 0x07, 0x07, 0x1c, 3}, /* 400x300x16 */
+ {0x58, 0xba1d, 0x0137, SIS_RI_512x384, 0x00, 0x00, 0x00, 0x00, 0x1d, 4}, /* 512x384x16 */
+ {0x59, 0x9a1b, 0x0138, SIS_RI_320x200, 0x00, 0x00, 0x04, 0x04, 0x1a, 0}, /* 320x200x8 */
+ {0x5c, 0xba1f, 0x0000, SIS_RI_512x384, 0x00, 0x00, 0x00, 0x00, 0x1d, 4}, /* 512x384x32 */
+ {0x5d, 0x0a1d, 0x0139, SIS_RI_640x400, 0x00, 0x00, 0x05, 0x07, 0x10, 0}, /* 640x400x16 */
+ {0x5e, 0x0a1f, 0x0000, SIS_RI_640x400, 0x00, 0x00, 0x05, 0x07, 0x10, 0}, /* 640x400x32 */
+ {0x62, 0x0a3f, 0x013a, SIS_RI_640x480, 0x00, 0x00, 0x05, 0x05, 0x08, 2}, /* 640x480x32 */
+ {0x63, 0x2a3f, 0x013b, SIS_RI_800x600, 0x00, 0x00, 0x07, 0x06, 0x00, 3}, /* 800x600x32 */
+ {0x64, 0x0a7f, 0x013c, SIS_RI_1024x768, 0x00, 0x00, 0x08, 0x07, 0x13, 4}, /* 1024x768x32 */
+ {0x65, 0x0eff, 0x013d, SIS_RI_1280x1024, 0x00, 0x00, 0x00, 0x00, 0x2f, 8}, /* 1280x1024x32 */
+ {0x70, 0x6a1b, 0x0000, SIS_RI_800x480, 0x00, 0x00, 0x07, 0x07, 0x1e, -1}, /* 800x480x8 */
+ {0x71, 0x4a1b, 0x0000, SIS_RI_1024x576, 0x00, 0x00, 0x00, 0x00, 0x21, -1}, /* 1024x576x8 */
+ {0x74, 0x4a1d, 0x0000, SIS_RI_1024x576, 0x00, 0x00, 0x00, 0x00, 0x21, -1}, /* 1024x576x16 */
+ {0x75, 0x0a3d, 0x0000, SIS_RI_1280x720, 0x00, 0x00, 0x00, 0x00, 0x24, 5}, /* 1280x720x16 */
+ {0x76, 0x6a1f, 0x0000, SIS_RI_800x480, 0x00, 0x00, 0x07, 0x07, 0x1e, -1}, /* 800x480x32 */
+ {0x77, 0x4a1f, 0x0000, SIS_RI_1024x576, 0x00, 0x00, 0x00, 0x00, 0x21, -1}, /* 1024x576x32 */
+ {0x78, 0x0a3f, 0x0000, SIS_RI_1280x720, 0x00, 0x00, 0x00, 0x00, 0x24, 5}, /* 1280x720x32 */
+ {0x79, 0x0a3b, 0x0000, SIS_RI_1280x720, 0x00, 0x00, 0x00, 0x00, 0x24, 5}, /* 1280x720x8 */
+ {0x7a, 0x6a1d, 0x0000, SIS_RI_800x480, 0x00, 0x00, 0x07, 0x07, 0x1e, -1}, /* 800x480x16 */
+ {0x23, 0x0e3b, 0x0000, SIS_RI_1280x768, 0x00, 0x00, 0x00, 0x00, 0x27, 6}, /* 1280x768x8 */
+ {0x24, 0x0e7d, 0x0000, SIS_RI_1280x768, 0x00, 0x00, 0x00, 0x00, 0x27, 6}, /* 1280x768x16 */
+ {0x25, 0x0eff, 0x0000, SIS_RI_1280x768, 0x00, 0x00, 0x00, 0x00, 0x27, 6}, /* 1280x768x32 */
+ {0x39, 0x6a1b, 0x0000, SIS_RI_848x480, 0x00, 0x00, 0x00, 0x00, 0x28, -1}, /* 848x480 */
+ {0x3b, 0x6a3d, 0x0000, SIS_RI_848x480, 0x00, 0x00, 0x00, 0x00, 0x28,
+ -1},
+ {0x3e, 0x6a7f, 0x0000, SIS_RI_848x480, 0x00, 0x00, 0x00, 0x00, 0x28,
+ -1},
+ {0x3f, 0x6a1b, 0x0000, SIS_RI_856x480, 0x00, 0x00, 0x00, 0x00, 0x2a, -1}, /* 856x480 */
+ {0x42, 0x6a3d, 0x0000, SIS_RI_856x480, 0x00, 0x00, 0x00, 0x00, 0x2a,
+ -1},
+ {0x45, 0x6a7f, 0x0000, SIS_RI_856x480, 0x00, 0x00, 0x00, 0x00, 0x2a,
+ -1},
+ {0x4f, 0x9a1f, 0x0000, SIS_RI_320x200, 0x00, 0x00, 0x04, 0x04, 0x1a, 0}, /* 320x200x32 */
+ {0x53, 0x9a1f, 0x0000, SIS_RI_320x240, 0x00, 0x00, 0x04, 0x04, 0x1b, 2}, /* 320x240x32 */
+ {0x54, 0xba1f, 0x0000, SIS_RI_400x300, 0x00, 0x00, 0x07, 0x07, 0x1c, 3}, /* 400x300x32 */
+ {0x5f, 0x6a1b, 0x0000, SIS_RI_768x576, 0x00, 0x00, 0x06, 0x06, 0x2c, -1}, /* 768x576 */
+ {0x60, 0x6a1d, 0x0000, SIS_RI_768x576, 0x00, 0x00, 0x06, 0x06, 0x2c,
+ -1},
+ {0x61, 0x6a3f, 0x0000, SIS_RI_768x576, 0x00, 0x00, 0x06, 0x06, 0x2c,
+ -1},
+ {0x1d, 0x6a1b, 0x0000, SIS_RI_960x540, 0x00, 0x00, 0x00, 0x00, 0x2d, -1}, /* 960x540 */
+ {0x1e, 0x6a3d, 0x0000, SIS_RI_960x540, 0x00, 0x00, 0x00, 0x00, 0x2d,
+ -1},
+ {0x1f, 0x6a7f, 0x0000, SIS_RI_960x540, 0x00, 0x00, 0x00, 0x00, 0x2d,
+ -1},
+ {0x20, 0x6a1b, 0x0000, SIS_RI_960x600, 0x00, 0x00, 0x00, 0x00, 0x2e, -1}, /* 960x600 */
+ {0x21, 0x6a3d, 0x0000, SIS_RI_960x600, 0x00, 0x00, 0x00, 0x00, 0x2e,
+ -1},
+ {0x22, 0x6a7f, 0x0000, SIS_RI_960x600, 0x00, 0x00, 0x00, 0x00, 0x2e,
+ -1},
+ {0x29, 0x4e1b, 0x0000, SIS_RI_1152x864, 0x00, 0x00, 0x00, 0x00, 0x33, -1}, /* 1152x864 */
+ {0x2a, 0x4e3d, 0x0000, SIS_RI_1152x864, 0x00, 0x00, 0x00, 0x00, 0x33,
+ -1},
+ {0x2b, 0x4e7f, 0x0000, SIS_RI_1152x864, 0x00, 0x00, 0x00, 0x00, 0x33,
+ -1},
+ {0xff, 0x0000, 0x0000, 0, 0x00, 0x00, 0x00, 0x00, 0x00, -1}
+};
+
+static const struct SiS_Ext2 SiSUSB_RefIndex[] = {
+ {0x085f, 0x0d, 0x03, 0x05, 0x05, 0x30, 800, 600, 0x40, 0x00, 0x00}, /* 0x0 */
+ {0x0067, 0x0e, 0x04, 0x05, 0x05, 0x30, 800, 600, 0x40, 0x00, 0x00}, /* 0x1 */
+ {0x0067, 0x0f, 0x08, 0x48, 0x05, 0x30, 800, 600, 0x40, 0x00, 0x00}, /* 0x2 */
+ {0x0067, 0x10, 0x07, 0x8b, 0x05, 0x30, 800, 600, 0x40, 0x00, 0x00}, /* 0x3 */
+ {0x0047, 0x11, 0x0a, 0x00, 0x05, 0x30, 800, 600, 0x40, 0x00, 0x00}, /* 0x4 */
+ {0x0047, 0x12, 0x0d, 0x00, 0x05, 0x30, 800, 600, 0x40, 0x00, 0x00}, /* 0x5 */
+ {0x0047, 0x13, 0x13, 0x00, 0x05, 0x30, 800, 600, 0x20, 0x00, 0x00}, /* 0x6 */
+ {0x0107, 0x14, 0x1c, 0x00, 0x05, 0x30, 800, 600, 0x20, 0x00, 0x00}, /* 0x7 */
+ {0xc85f, 0x05, 0x00, 0x04, 0x04, 0x2e, 640, 480, 0x40, 0x00, 0x00}, /* 0x8 */
+ {0xc067, 0x06, 0x02, 0x04, 0x04, 0x2e, 640, 480, 0x40, 0x00, 0x00}, /* 0x9 */
+ {0xc067, 0x07, 0x02, 0x47, 0x04, 0x2e, 640, 480, 0x40, 0x00, 0x00}, /* 0xa */
+ {0xc067, 0x08, 0x03, 0x8a, 0x04, 0x2e, 640, 480, 0x40, 0x00, 0x00}, /* 0xb */
+ {0xc047, 0x09, 0x05, 0x00, 0x04, 0x2e, 640, 480, 0x40, 0x00, 0x00}, /* 0xc */
+ {0xc047, 0x0a, 0x09, 0x00, 0x04, 0x2e, 640, 480, 0x40, 0x00, 0x00}, /* 0xd */
+ {0xc047, 0x0b, 0x0e, 0x00, 0x04, 0x2e, 640, 480, 0x40, 0x00, 0x00}, /* 0xe */
+ {0xc047, 0x0c, 0x15, 0x00, 0x04, 0x2e, 640, 480, 0x40, 0x00, 0x00}, /* 0xf */
+ {0x487f, 0x04, 0x00, 0x00, 0x00, 0x2f, 640, 400, 0x30, 0x55, 0x6e}, /* 0x10 */
+ {0xc06f, 0x3c, 0x01, 0x06, 0x13, 0x31, 720, 480, 0x30, 0x00, 0x00}, /* 0x11 */
+ {0x006f, 0x3d, 0x6f, 0x06, 0x14, 0x32, 720, 576, 0x30, 0x00, 0x00}, /* 0x12 (6f was 03) */
+ {0x0087, 0x15, 0x06, 0x00, 0x06, 0x38, 1024, 768, 0x30, 0x00, 0x00}, /* 0x13 */
+ {0xc877, 0x16, 0x0b, 0x06, 0x06, 0x38, 1024, 768, 0x20, 0x00, 0x00}, /* 0x14 */
+ {0xc067, 0x17, 0x0f, 0x49, 0x06, 0x38, 1024, 768, 0x20, 0x00, 0x00}, /* 0x15 */
+ {0x0067, 0x18, 0x11, 0x00, 0x06, 0x38, 1024, 768, 0x20, 0x00, 0x00}, /* 0x16 */
+ {0x0047, 0x19, 0x16, 0x8c, 0x06, 0x38, 1024, 768, 0x20, 0x00, 0x00}, /* 0x17 */
+ {0x0107, 0x1a, 0x1b, 0x00, 0x06, 0x38, 1024, 768, 0x10, 0x00, 0x00}, /* 0x18 */
+ {0x0107, 0x1b, 0x1f, 0x00, 0x06, 0x38, 1024, 768, 0x10, 0x00, 0x00}, /* 0x19 */
+ {0x407f, 0x00, 0x00, 0x00, 0x00, 0x41, 320, 200, 0x30, 0x56, 0x4e}, /* 0x1a */
+ {0xc07f, 0x01, 0x00, 0x04, 0x04, 0x50, 320, 240, 0x30, 0x00, 0x00}, /* 0x1b */
+ {0x007f, 0x02, 0x04, 0x05, 0x05, 0x51, 400, 300, 0x30, 0x00, 0x00}, /* 0x1c */
+ {0xc077, 0x03, 0x0b, 0x06, 0x06, 0x52, 512, 384, 0x30, 0x00, 0x00}, /* 0x1d */
+ {0x0077, 0x32, 0x40, 0x08, 0x18, 0x70, 800, 480, 0x30, 0x00, 0x00}, /* 0x1e */
+ {0x0047, 0x33, 0x07, 0x08, 0x18, 0x70, 800, 480, 0x30, 0x00, 0x00}, /* 0x1f */
+ {0x0047, 0x34, 0x0a, 0x08, 0x18, 0x70, 800, 480, 0x30, 0x00, 0x00}, /* 0x20 */
+ {0x0077, 0x35, 0x0b, 0x09, 0x19, 0x71, 1024, 576, 0x30, 0x00, 0x00}, /* 0x21 */
+ {0x0047, 0x36, 0x11, 0x09, 0x19, 0x71, 1024, 576, 0x30, 0x00, 0x00}, /* 0x22 */
+ {0x0047, 0x37, 0x16, 0x09, 0x19, 0x71, 1024, 576, 0x30, 0x00, 0x00}, /* 0x23 */
+ {0x1137, 0x38, 0x19, 0x0a, 0x0c, 0x75, 1280, 720, 0x30, 0x00, 0x00}, /* 0x24 */
+ {0x1107, 0x39, 0x1e, 0x0a, 0x0c, 0x75, 1280, 720, 0x30, 0x00, 0x00}, /* 0x25 */
+ {0x1307, 0x3a, 0x20, 0x0a, 0x0c, 0x75, 1280, 720, 0x30, 0x00, 0x00}, /* 0x26 */
+ {0x0077, 0x42, 0x5b, 0x08, 0x11, 0x23, 1280, 768, 0x30, 0x00, 0x00}, /* 0x27 */
+ {0x0087, 0x45, 0x57, 0x00, 0x16, 0x39, 848, 480, 0x30, 0x00, 0x00}, /* 0x28 38Hzi */
+ {0xc067, 0x46, 0x55, 0x0b, 0x16, 0x39, 848, 480, 0x30, 0x00, 0x00}, /* 0x29 848x480-60Hz */
+ {0x0087, 0x47, 0x57, 0x00, 0x17, 0x3f, 856, 480, 0x30, 0x00, 0x00}, /* 0x2a 856x480-38Hzi */
+ {0xc067, 0x48, 0x57, 0x00, 0x17, 0x3f, 856, 480, 0x30, 0x00, 0x00}, /* 0x2b 856x480-60Hz */
+ {0x006f, 0x4d, 0x71, 0x06, 0x15, 0x5f, 768, 576, 0x30, 0x00, 0x00}, /* 0x2c 768x576-56Hz */
+ {0x0067, 0x52, 0x6a, 0x00, 0x1c, 0x1d, 960, 540, 0x30, 0x00, 0x00}, /* 0x2d 960x540 60Hz */
+ {0x0077, 0x53, 0x6b, 0x0b, 0x1d, 0x20, 960, 600, 0x30, 0x00, 0x00}, /* 0x2e 960x600 60Hz */
+ {0x0087, 0x1c, 0x11, 0x00, 0x07, 0x3a, 1280, 1024, 0x30, 0x00, 0x00}, /* 0x2f */
+ {0x0137, 0x1d, 0x19, 0x07, 0x07, 0x3a, 1280, 1024, 0x00, 0x00, 0x00}, /* 0x30 */
+ {0x0107, 0x1e, 0x1e, 0x00, 0x07, 0x3a, 1280, 1024, 0x00, 0x00, 0x00}, /* 0x31 */
+ {0x0207, 0x1f, 0x20, 0x00, 0x07, 0x3a, 1280, 1024, 0x00, 0x00, 0x00}, /* 0x32 */
+ {0x0127, 0x54, 0x6d, 0x00, 0x1a, 0x29, 1152, 864, 0x30, 0x00, 0x00}, /* 0x33 1152x864-60Hz */
+ {0x0127, 0x44, 0x19, 0x00, 0x1a, 0x29, 1152, 864, 0x30, 0x00, 0x00}, /* 0x34 1152x864-75Hz */
+ {0x0127, 0x4a, 0x1e, 0x00, 0x1a, 0x29, 1152, 864, 0x30, 0x00, 0x00}, /* 0x35 1152x864-85Hz */
+ {0xffff, 0x00, 0x00, 0x00, 0x00, 0x00, 0, 0, 0, 0x00, 0x00}
+};
+
+static const struct SiS_CRT1Table SiSUSB_CRT1Table[] = {
+ {{0x2d, 0x27, 0x28, 0x90, 0x2c, 0x80, 0xbf, 0x1f,
+ 0x9c, 0x8e, 0x8f, 0x96, 0xb9, 0x30, 0x00, 0x00,
+ 0x00}}, /* 0x0 */
+ {{0x2d, 0x27, 0x28, 0x90, 0x2c, 0x80, 0x0b, 0x3e,
+ 0xe9, 0x8b, 0xdf, 0xe7, 0x04, 0x00, 0x00, 0x00,
+ 0x00}}, /* 0x1 */
+ {{0x3d, 0x31, 0x31, 0x81, 0x37, 0x1f, 0x72, 0xf0,
+ 0x58, 0x8c, 0x57, 0x57, 0x73, 0x20, 0x00, 0x05,
+ 0x01}}, /* 0x2 */
+ {{0x4f, 0x3f, 0x3f, 0x93, 0x45, 0x0d, 0x24, 0xf5,
+ 0x02, 0x88, 0xff, 0xff, 0x25, 0x10, 0x00, 0x01,
+ 0x01}}, /* 0x3 */
+ {{0x5f, 0x4f, 0x50, 0x82, 0x55, 0x81, 0xbf, 0x1f,
+ 0x9c, 0x8e, 0x8f, 0x96, 0xb9, 0x30, 0x00, 0x05,
+ 0x00}}, /* 0x4 */
+ {{0x5f, 0x4f, 0x4f, 0x83, 0x55, 0x81, 0x0b, 0x3e,
+ 0xe9, 0x8b, 0xdf, 0xe8, 0x0c, 0x00, 0x00, 0x05,
+ 0x00}}, /* 0x5 */
+ {{0x63, 0x4f, 0x4f, 0x87, 0x56, 0x9b, 0x06, 0x3e,
+ 0xe8, 0x8a, 0xdf, 0xe7, 0x07, 0x00, 0x00, 0x01,
+ 0x00}}, /* 0x6 */
+ {{0x64, 0x4f, 0x4f, 0x88, 0x55, 0x9d, 0xf2, 0x1f,
+ 0xe0, 0x83, 0xdf, 0xdf, 0xf3, 0x10, 0x00, 0x01,
+ 0x00}}, /* 0x7 */
+ {{0x63, 0x4f, 0x4f, 0x87, 0x5a, 0x81, 0xfb, 0x1f,
+ 0xe0, 0x83, 0xdf, 0xdf, 0xfc, 0x10, 0x00, 0x05,
+ 0x00}}, /* 0x8 */
+ {{0x65, 0x4f, 0x4f, 0x89, 0x58, 0x80, 0xfb, 0x1f,
+ 0xe0, 0x83, 0xdf, 0xdf, 0xfc, 0x10, 0x00, 0x05,
+ 0x61}}, /* 0x9 */
+ {{0x65, 0x4f, 0x4f, 0x89, 0x58, 0x80, 0x01, 0x3e,
+ 0xe0, 0x83, 0xdf, 0xdf, 0x02, 0x00, 0x00, 0x05,
+ 0x61}}, /* 0xa */
+ {{0x67, 0x4f, 0x4f, 0x8b, 0x58, 0x81, 0x0d, 0x3e,
+ 0xe0, 0x83, 0xdf, 0xdf, 0x0e, 0x00, 0x00, 0x05,
+ 0x61}}, /* 0xb */
+ {{0x65, 0x4f, 0x4f, 0x89, 0x57, 0x9f, 0xfb, 0x1f,
+ 0xe6, 0x8a, 0xdf, 0xdf, 0xfc, 0x10, 0x00, 0x01,
+ 0x00}}, /* 0xc */
+ {{0x7b, 0x63, 0x63, 0x9f, 0x6a, 0x93, 0x6f, 0xf0,
+ 0x58, 0x8a, 0x57, 0x57, 0x70, 0x20, 0x00, 0x05,
+ 0x01}}, /* 0xd */
+ {{0x7f, 0x63, 0x63, 0x83, 0x6c, 0x1c, 0x72, 0xf0,
+ 0x58, 0x8c, 0x57, 0x57, 0x73, 0x20, 0x00, 0x06,
+ 0x01}}, /* 0xe */
+ {{0x7d, 0x63, 0x63, 0x81, 0x6e, 0x1d, 0x98, 0xf0,
+ 0x7c, 0x82, 0x57, 0x57, 0x99, 0x00, 0x00, 0x06,
+ 0x01}}, /* 0xf */
+ {{0x7f, 0x63, 0x63, 0x83, 0x69, 0x13, 0x6f, 0xf0,
+ 0x58, 0x8b, 0x57, 0x57, 0x70, 0x20, 0x00, 0x06,
+ 0x01}}, /* 0x10 */
+ {{0x7e, 0x63, 0x63, 0x82, 0x6b, 0x13, 0x75, 0xf0,
+ 0x58, 0x8b, 0x57, 0x57, 0x76, 0x20, 0x00, 0x06,
+ 0x01}}, /* 0x11 */
+ {{0x81, 0x63, 0x63, 0x85, 0x6d, 0x18, 0x7a, 0xf0,
+ 0x58, 0x8b, 0x57, 0x57, 0x7b, 0x20, 0x00, 0x06,
+ 0x61}}, /* 0x12 */
+ {{0x83, 0x63, 0x63, 0x87, 0x6e, 0x19, 0x81, 0xf0,
+ 0x58, 0x8b, 0x57, 0x57, 0x82, 0x20, 0x00, 0x06,
+ 0x61}}, /* 0x13 */
+ {{0x85, 0x63, 0x63, 0x89, 0x6f, 0x1a, 0x91, 0xf0,
+ 0x58, 0x8b, 0x57, 0x57, 0x92, 0x20, 0x00, 0x06,
+ 0x61}}, /* 0x14 */
+ {{0x99, 0x7f, 0x7f, 0x9d, 0x84, 0x1a, 0x96, 0x1f,
+ 0x7f, 0x83, 0x7f, 0x7f, 0x97, 0x10, 0x00, 0x02,
+ 0x00}}, /* 0x15 */
+ {{0xa3, 0x7f, 0x7f, 0x87, 0x86, 0x97, 0x24, 0xf5,
+ 0x02, 0x88, 0xff, 0xff, 0x25, 0x10, 0x00, 0x02,
+ 0x01}}, /* 0x16 */
+ {{0xa1, 0x7f, 0x7f, 0x85, 0x86, 0x97, 0x24, 0xf5,
+ 0x02, 0x88, 0xff, 0xff, 0x25, 0x10, 0x00, 0x02,
+ 0x01}}, /* 0x17 */
+ {{0x9f, 0x7f, 0x7f, 0x83, 0x85, 0x91, 0x1e, 0xf5,
+ 0x00, 0x83, 0xff, 0xff, 0x1f, 0x10, 0x00, 0x02,
+ 0x01}}, /* 0x18 */
+ {{0xa7, 0x7f, 0x7f, 0x8b, 0x89, 0x95, 0x26, 0xf5,
+ 0x00, 0x83, 0xff, 0xff, 0x27, 0x10, 0x00, 0x02,
+ 0x01}}, /* 0x19 */
+ {{0xa9, 0x7f, 0x7f, 0x8d, 0x8c, 0x9a, 0x2c, 0xf5,
+ 0x00, 0x83, 0xff, 0xff, 0x2d, 0x14, 0x00, 0x02,
+ 0x62}}, /* 0x1a */
+ {{0xab, 0x7f, 0x7f, 0x8f, 0x8d, 0x9b, 0x35, 0xf5,
+ 0x00, 0x83, 0xff, 0xff, 0x36, 0x14, 0x00, 0x02,
+ 0x62}}, /* 0x1b */
+ {{0xcf, 0x9f, 0x9f, 0x93, 0xb2, 0x01, 0x14, 0xba,
+ 0x00, 0x83, 0xff, 0xff, 0x15, 0x00, 0x00, 0x03,
+ 0x00}}, /* 0x1c */
+ {{0xce, 0x9f, 0x9f, 0x92, 0xa9, 0x17, 0x28, 0x5a,
+ 0x00, 0x83, 0xff, 0xff, 0x29, 0x09, 0x00, 0x07,
+ 0x01}}, /* 0x1d */
+ {{0xce, 0x9f, 0x9f, 0x92, 0xa5, 0x17, 0x28, 0x5a,
+ 0x00, 0x83, 0xff, 0xff, 0x29, 0x09, 0x00, 0x07,
+ 0x01}}, /* 0x1e */
+ {{0xd3, 0x9f, 0x9f, 0x97, 0xab, 0x1f, 0x2e, 0x5a,
+ 0x00, 0x83, 0xff, 0xff, 0x2f, 0x09, 0x00, 0x07,
+ 0x01}}, /* 0x1f */
+ {{0x09, 0xc7, 0xc7, 0x8d, 0xd3, 0x0b, 0xe0, 0x10,
+ 0xb0, 0x83, 0xaf, 0xaf, 0xe1, 0x2f, 0x01, 0x04,
+ 0x00}}, /* 0x20 */
+ {{0x09, 0xc7, 0xc7, 0x8d, 0xd3, 0x0b, 0xe0, 0x10,
+ 0xb0, 0x83, 0xaf, 0xaf, 0xe1, 0x2f, 0x01, 0x04,
+ 0x00}}, /* 0x21 */
+ {{0x09, 0xc7, 0xc7, 0x8d, 0xd3, 0x0b, 0xe0, 0x10,
+ 0xb0, 0x83, 0xaf, 0xaf, 0xe1, 0x2f, 0x01, 0x04,
+ 0x00}}, /* 0x22 */
+ {{0x09, 0xc7, 0xc7, 0x8d, 0xd3, 0x0b, 0xe0, 0x10,
+ 0xb0, 0x83, 0xaf, 0xaf, 0xe1, 0x2f, 0x01, 0x04,
+ 0x00}}, /* 0x23 */
+ {{0x09, 0xc7, 0xc7, 0x8d, 0xd3, 0x0b, 0xe0, 0x10,
+ 0xb0, 0x83, 0xaf, 0xaf, 0xe1, 0x2f, 0x01, 0x04,
+ 0x00}}, /* 0x24 */
+ {{0x09, 0xc7, 0xc7, 0x8d, 0xd3, 0x0b, 0xe0, 0x10,
+ 0xb0, 0x83, 0xaf, 0xaf, 0xe1, 0x2f, 0x01, 0x04,
+ 0x00}}, /* 0x25 */
+ {{0x09, 0xc7, 0xc7, 0x8d, 0xd3, 0x0b, 0xe0, 0x10,
+ 0xb0, 0x83, 0xaf, 0xaf, 0xe1, 0x2f, 0x01, 0x04,
+ 0x00}}, /* 0x26 */
+ {{0x40, 0xef, 0xef, 0x84, 0x03, 0x1d, 0xda, 0x1f,
+ 0xa0, 0x83, 0x9f, 0x9f, 0xdb, 0x1f, 0x41, 0x01,
+ 0x00}}, /* 0x27 */
+ {{0x43, 0xef, 0xef, 0x87, 0x06, 0x00, 0xd4, 0x1f,
+ 0xa0, 0x83, 0x9f, 0x9f, 0xd5, 0x1f, 0x41, 0x05,
+ 0x63}}, /* 0x28 */
+ {{0x45, 0xef, 0xef, 0x89, 0x07, 0x01, 0xd9, 0x1f,
+ 0xa0, 0x83, 0x9f, 0x9f, 0xda, 0x1f, 0x41, 0x05,
+ 0x63}}, /* 0x29 */
+ {{0x40, 0xef, 0xef, 0x84, 0x03, 0x1d, 0xda, 0x1f,
+ 0xa0, 0x83, 0x9f, 0x9f, 0xdb, 0x1f, 0x41, 0x01,
+ 0x00}}, /* 0x2a */
+ {{0x40, 0xef, 0xef, 0x84, 0x03, 0x1d, 0xda, 0x1f,
+ 0xa0, 0x83, 0x9f, 0x9f, 0xdb, 0x1f, 0x41, 0x01,
+ 0x00}}, /* 0x2b */
+ {{0x40, 0xef, 0xef, 0x84, 0x03, 0x1d, 0xda, 0x1f,
+ 0xa0, 0x83, 0x9f, 0x9f, 0xdb, 0x1f, 0x41, 0x01,
+ 0x00}}, /* 0x2c */
+ {{0x59, 0xff, 0xff, 0x9d, 0x17, 0x13, 0x33, 0xba,
+ 0x00, 0x83, 0xff, 0xff, 0x34, 0x0f, 0x41, 0x05,
+ 0x44}}, /* 0x2d */
+ {{0x5b, 0xff, 0xff, 0x9f, 0x18, 0x14, 0x38, 0xba,
+ 0x00, 0x83, 0xff, 0xff, 0x39, 0x0f, 0x41, 0x05,
+ 0x44}}, /* 0x2e */
+ {{0x5b, 0xff, 0xff, 0x9f, 0x18, 0x14, 0x3d, 0xba,
+ 0x00, 0x83, 0xff, 0xff, 0x3e, 0x0f, 0x41, 0x05,
+ 0x44}}, /* 0x2f */
+ {{0x5d, 0xff, 0xff, 0x81, 0x19, 0x95, 0x41, 0xba,
+ 0x00, 0x84, 0xff, 0xff, 0x42, 0x0f, 0x41, 0x05,
+ 0x44}}, /* 0x30 */
+ {{0x55, 0xff, 0xff, 0x99, 0x0d, 0x0c, 0x3e, 0xba,
+ 0x00, 0x84, 0xff, 0xff, 0x3f, 0x0f, 0x41, 0x05,
+ 0x00}}, /* 0x31 */
+ {{0x7f, 0x63, 0x63, 0x83, 0x6c, 0x1c, 0x72, 0xba,
+ 0x27, 0x8b, 0xdf, 0xdf, 0x73, 0x00, 0x00, 0x06,
+ 0x01}}, /* 0x32 */
+ {{0x7f, 0x63, 0x63, 0x83, 0x69, 0x13, 0x6f, 0xba,
+ 0x26, 0x89, 0xdf, 0xdf, 0x6f, 0x00, 0x00, 0x06,
+ 0x01}}, /* 0x33 */
+ {{0x7f, 0x63, 0x63, 0x82, 0x6b, 0x13, 0x75, 0xba,
+ 0x29, 0x8c, 0xdf, 0xdf, 0x75, 0x00, 0x00, 0x06,
+ 0x01}}, /* 0x34 */
+ {{0xa3, 0x7f, 0x7f, 0x87, 0x86, 0x97, 0x24, 0xf1,
+ 0xaf, 0x85, 0x3f, 0x3f, 0x25, 0x30, 0x00, 0x02,
+ 0x01}}, /* 0x35 */
+ {{0x9f, 0x7f, 0x7f, 0x83, 0x85, 0x91, 0x1e, 0xf1,
+ 0xad, 0x81, 0x3f, 0x3f, 0x1f, 0x30, 0x00, 0x02,
+ 0x01}}, /* 0x36 */
+ {{0xa7, 0x7f, 0x7f, 0x88, 0x89, 0x95, 0x26, 0xf1,
+ 0xb1, 0x85, 0x3f, 0x3f, 0x27, 0x30, 0x00, 0x02,
+ 0x01}}, /* 0x37 */
+ {{0xce, 0x9f, 0x9f, 0x92, 0xa9, 0x17, 0x28, 0xc4,
+ 0x7a, 0x8e, 0xcf, 0xcf, 0x29, 0x21, 0x00, 0x07,
+ 0x01}}, /* 0x38 */
+ {{0xce, 0x9f, 0x9f, 0x92, 0xa5, 0x17, 0x28, 0xd4,
+ 0x7a, 0x8e, 0xcf, 0xcf, 0x29, 0x21, 0x00, 0x07,
+ 0x01}}, /* 0x39 */
+ {{0xd3, 0x9f, 0x9f, 0x97, 0xab, 0x1f, 0x2e, 0xd4,
+ 0x7d, 0x81, 0xcf, 0xcf, 0x2f, 0x21, 0x00, 0x07,
+ 0x01}}, /* 0x3a */
+ {{0xdc, 0x9f, 0x9f, 0x80, 0xaf, 0x9d, 0xe6, 0xff,
+ 0xc0, 0x83, 0xbf, 0xbf, 0xe7, 0x10, 0x00, 0x07,
+ 0x01}}, /* 0x3b */
+ {{0x6b, 0x59, 0x59, 0x8f, 0x5e, 0x8c, 0x0b, 0x3e,
+ 0xe9, 0x8b, 0xdf, 0xe7, 0x04, 0x00, 0x00, 0x05,
+ 0x00}}, /* 0x3c */
+ {{0x6d, 0x59, 0x59, 0x91, 0x60, 0x89, 0x53, 0xf0,
+ 0x41, 0x84, 0x3f, 0x3f, 0x54, 0x00, 0x00, 0x05,
+ 0x41}}, /* 0x3d */
+ {{0x86, 0x6a, 0x6a, 0x8a, 0x74, 0x06, 0x8c, 0x15,
+ 0x4f, 0x83, 0xef, 0xef, 0x8d, 0x30, 0x00, 0x02,
+ 0x00}}, /* 0x3e */
+ {{0x81, 0x6a, 0x6a, 0x85, 0x70, 0x00, 0x0f, 0x3e,
+ 0xeb, 0x8e, 0xdf, 0xdf, 0x10, 0x00, 0x00, 0x02,
+ 0x00}}, /* 0x3f */
+ {{0xa3, 0x7f, 0x7f, 0x87, 0x86, 0x97, 0x1e, 0xf1,
+ 0xae, 0x85, 0x57, 0x57, 0x1f, 0x30, 0x00, 0x02,
+ 0x01}}, /* 0x40 */
+ {{0xa3, 0x7f, 0x7f, 0x87, 0x86, 0x97, 0x24, 0xf5,
+ 0x02, 0x88, 0xff, 0xff, 0x25, 0x10, 0x00, 0x02,
+ 0x01}}, /* 0x41 */
+ {{0xce, 0x9f, 0x9f, 0x92, 0xa9, 0x17, 0x20, 0xf5,
+ 0x03, 0x88, 0xff, 0xff, 0x21, 0x10, 0x00, 0x07,
+ 0x01}}, /* 0x42 */
+ {{0xe6, 0xae, 0xae, 0x8a, 0xbd, 0x90, 0x3d, 0x10,
+ 0x1a, 0x8d, 0x19, 0x19, 0x3e, 0x2f, 0x00, 0x03,
+ 0x00}}, /* 0x43 */
+ {{0xc3, 0x8f, 0x8f, 0x87, 0x9b, 0x0b, 0x82, 0xef,
+ 0x60, 0x83, 0x5f, 0x5f, 0x83, 0x10, 0x00, 0x07,
+ 0x01}}, /* 0x44 */
+ {{0x86, 0x69, 0x69, 0x8A, 0x74, 0x06, 0x8C, 0x15,
+ 0x4F, 0x83, 0xEF, 0xEF, 0x8D, 0x30, 0x00, 0x02,
+ 0x00}}, /* 0x45 */
+ {{0x83, 0x69, 0x69, 0x87, 0x6f, 0x1d, 0x03, 0x3E,
+ 0xE5, 0x8d, 0xDF, 0xe4, 0x04, 0x00, 0x00, 0x06,
+ 0x00}}, /* 0x46 */
+ {{0x86, 0x6A, 0x6A, 0x8A, 0x74, 0x06, 0x8C, 0x15,
+ 0x4F, 0x83, 0xEF, 0xEF, 0x8D, 0x30, 0x00, 0x02,
+ 0x00}}, /* 0x47 */
+ {{0x81, 0x6A, 0x6A, 0x85, 0x70, 0x00, 0x0F, 0x3E,
+ 0xEB, 0x8E, 0xDF, 0xDF, 0x10, 0x00, 0x00, 0x02,
+ 0x00}}, /* 0x48 */
+ {{0xdd, 0xa9, 0xa9, 0x81, 0xb4, 0x97, 0x26, 0xfd,
+ 0x01, 0x8d, 0xff, 0x00, 0x27, 0x10, 0x00, 0x03,
+ 0x01}}, /* 0x49 */
+ {{0xd9, 0x8f, 0x8f, 0x9d, 0xba, 0x0a, 0x8a, 0xff,
+ 0x60, 0x8b, 0x5f, 0x5f, 0x8b, 0x10, 0x00, 0x03,
+ 0x01}}, /* 0x4a */
+ {{0xea, 0xae, 0xae, 0x8e, 0xba, 0x82, 0x40, 0x10,
+ 0x1b, 0x87, 0x19, 0x1a, 0x41, 0x0f, 0x00, 0x03,
+ 0x00}}, /* 0x4b */
+ {{0xd3, 0x9f, 0x9f, 0x97, 0xab, 0x1f, 0xf1, 0xff,
+ 0xc0, 0x83, 0xbf, 0xbf, 0xf2, 0x10, 0x00, 0x07,
+ 0x01}}, /* 0x4c */
+ {{0x75, 0x5f, 0x5f, 0x99, 0x66, 0x90, 0x53, 0xf0,
+ 0x41, 0x84, 0x3f, 0x3f, 0x54, 0x00, 0x00, 0x05,
+ 0x41}},
+ {{0x2d, 0x27, 0x28, 0x90, 0x2c, 0x80, 0x0b, 0x3e,
+ 0xe9, 0x8b, 0xdf, 0xe7, 0x04, 0x00, 0x00, 0x00,
+ 0x00}}, /* 0x4e */
+ {{0xcd, 0x9f, 0x9f, 0x91, 0xab, 0x1c, 0x3a, 0xff,
+ 0x20, 0x83, 0x1f, 0x1f, 0x3b, 0x10, 0x00, 0x07,
+ 0x21}}, /* 0x4f */
+ {{0x15, 0xd1, 0xd1, 0x99, 0xe2, 0x19, 0x3d, 0x10,
+ 0x1a, 0x8d, 0x19, 0x19, 0x3e, 0x2f, 0x01, 0x0c,
+ 0x20}}, /* 0x50 */
+ {{0x0e, 0xef, 0xef, 0x92, 0xfe, 0x03, 0x30, 0xf0,
+ 0x1e, 0x83, 0x1b, 0x1c, 0x31, 0x00, 0x01, 0x00,
+ 0x61}}, /* 0x51 */
+ {{0x85, 0x77, 0x77, 0x89, 0x7d, 0x01, 0x31, 0xf0,
+ 0x1e, 0x84, 0x1b, 0x1c, 0x32, 0x00, 0x00, 0x02,
+ 0x41}}, /* 0x52 */
+ {{0x87, 0x77, 0x77, 0x8b, 0x81, 0x0b, 0x68, 0xf0,
+ 0x5a, 0x80, 0x57, 0x57, 0x69, 0x00, 0x00, 0x02,
+ 0x01}}, /* 0x53 */
+ {{0xcd, 0x8f, 0x8f, 0x91, 0x9b, 0x1b, 0x7a, 0xff,
+ 0x64, 0x8c, 0x5f, 0x62, 0x7b, 0x10, 0x00, 0x07,
+ 0x41}} /* 0x54 */
+};
+
+static const struct SiS_VCLKData SiSUSB_VCLKData[] = {
+ {0x1b, 0xe1, 25}, /* 0x00 */
+ {0x4e, 0xe4, 28}, /* 0x01 */
+ {0x57, 0xe4, 31}, /* 0x02 */
+ {0xc3, 0xc8, 36}, /* 0x03 */
+ {0x42, 0xe2, 40}, /* 0x04 */
+ {0xfe, 0xcd, 43}, /* 0x05 */
+ {0x5d, 0xc4, 44}, /* 0x06 */
+ {0x52, 0xe2, 49}, /* 0x07 */
+ {0x53, 0xe2, 50}, /* 0x08 */
+ {0x74, 0x67, 52}, /* 0x09 */
+ {0x6d, 0x66, 56}, /* 0x0a */
+ {0x5a, 0x64, 65}, /* 0x0b */
+ {0x46, 0x44, 67}, /* 0x0c */
+ {0xb1, 0x46, 68}, /* 0x0d */
+ {0xd3, 0x4a, 72}, /* 0x0e */
+ {0x29, 0x61, 75}, /* 0x0f */
+ {0x6e, 0x46, 76}, /* 0x10 */
+ {0x2b, 0x61, 78}, /* 0x11 */
+ {0x31, 0x42, 79}, /* 0x12 */
+ {0xab, 0x44, 83}, /* 0x13 */
+ {0x46, 0x25, 84}, /* 0x14 */
+ {0x78, 0x29, 86}, /* 0x15 */
+ {0x62, 0x44, 94}, /* 0x16 */
+ {0x2b, 0x41, 104}, /* 0x17 */
+ {0x3a, 0x23, 105}, /* 0x18 */
+ {0x70, 0x44, 108}, /* 0x19 */
+ {0x3c, 0x23, 109}, /* 0x1a */
+ {0x5e, 0x43, 113}, /* 0x1b */
+ {0xbc, 0x44, 116}, /* 0x1c */
+ {0xe0, 0x46, 132}, /* 0x1d */
+ {0x54, 0x42, 135}, /* 0x1e */
+ {0xea, 0x2a, 139}, /* 0x1f */
+ {0x41, 0x22, 157}, /* 0x20 */
+ {0x70, 0x24, 162}, /* 0x21 */
+ {0x30, 0x21, 175}, /* 0x22 */
+ {0x4e, 0x22, 189}, /* 0x23 */
+ {0xde, 0x26, 194}, /* 0x24 */
+ {0x62, 0x06, 202}, /* 0x25 */
+ {0x3f, 0x03, 229}, /* 0x26 */
+ {0xb8, 0x06, 234}, /* 0x27 */
+ {0x34, 0x02, 253}, /* 0x28 */
+ {0x58, 0x04, 255}, /* 0x29 */
+ {0x24, 0x01, 265}, /* 0x2a */
+ {0x9b, 0x02, 267}, /* 0x2b */
+ {0x70, 0x05, 270}, /* 0x2c */
+ {0x25, 0x01, 272}, /* 0x2d */
+ {0x9c, 0x02, 277}, /* 0x2e */
+ {0x27, 0x01, 286}, /* 0x2f */
+ {0x3c, 0x02, 291}, /* 0x30 */
+ {0xef, 0x0a, 292}, /* 0x31 */
+ {0xf6, 0x0a, 310}, /* 0x32 */
+ {0x95, 0x01, 315}, /* 0x33 */
+ {0xf0, 0x09, 324}, /* 0x34 */
+ {0xfe, 0x0a, 331}, /* 0x35 */
+ {0xf3, 0x09, 332}, /* 0x36 */
+ {0xea, 0x08, 340}, /* 0x37 */
+ {0xe8, 0x07, 376}, /* 0x38 */
+ {0xde, 0x06, 389}, /* 0x39 */
+ {0x52, 0x2a, 54}, /* 0x3a 301 TV */
+ {0x52, 0x6a, 27}, /* 0x3b 301 TV */
+ {0x62, 0x24, 70}, /* 0x3c 301 TV */
+ {0x62, 0x64, 70}, /* 0x3d 301 TV */
+ {0xa8, 0x4c, 30}, /* 0x3e 301 TV */
+ {0x20, 0x26, 33}, /* 0x3f 301 TV */
+ {0x31, 0xc2, 39}, /* 0x40 */
+ {0x60, 0x36, 30}, /* 0x41 Chrontel */
+ {0x40, 0x4a, 28}, /* 0x42 Chrontel */
+ {0x9f, 0x46, 44}, /* 0x43 Chrontel */
+ {0x97, 0x2c, 26}, /* 0x44 */
+ {0x44, 0xe4, 25}, /* 0x45 Chrontel */
+ {0x7e, 0x32, 47}, /* 0x46 Chrontel */
+ {0x8a, 0x24, 31}, /* 0x47 Chrontel */
+ {0x97, 0x2c, 26}, /* 0x48 Chrontel */
+ {0xce, 0x3c, 39}, /* 0x49 */
+ {0x52, 0x4a, 36}, /* 0x4a Chrontel */
+ {0x34, 0x61, 95}, /* 0x4b */
+ {0x78, 0x27, 108}, /* 0x4c - was 102 */
+ {0x66, 0x43, 123}, /* 0x4d Modes 0x26-0x28 (1400x1050) */
+ {0x41, 0x4e, 21}, /* 0x4e */
+ {0xa1, 0x4a, 29}, /* 0x4f Chrontel */
+ {0x19, 0x42, 42}, /* 0x50 */
+ {0x54, 0x46, 58}, /* 0x51 Chrontel */
+ {0x25, 0x42, 61}, /* 0x52 */
+ {0x44, 0x44, 66}, /* 0x53 Chrontel */
+ {0x3a, 0x62, 70}, /* 0x54 Chrontel */
+ {0x62, 0xc6, 34}, /* 0x55 848x480-60 */
+ {0x6a, 0xc6, 37}, /* 0x56 848x480-75 - TEMP */
+ {0xbf, 0xc8, 35}, /* 0x57 856x480-38i,60 */
+ {0x30, 0x23, 88}, /* 0x58 1360x768-62 (is 60Hz!) */
+ {0x52, 0x07, 149}, /* 0x59 1280x960-85 */
+ {0x56, 0x07, 156}, /* 0x5a 1400x1050-75 */
+ {0x70, 0x29, 81}, /* 0x5b 1280x768 LCD */
+ {0x45, 0x25, 83}, /* 0x5c 1280x800 */
+ {0x70, 0x0a, 147}, /* 0x5d 1680x1050 */
+ {0x70, 0x24, 162}, /* 0x5e 1600x1200 */
+ {0x5a, 0x64, 65}, /* 0x5f 1280x720 - temp */
+ {0x63, 0x46, 68}, /* 0x60 1280x768_2 */
+ {0x31, 0x42, 79}, /* 0x61 1280x768_3 - temp */
+ {0, 0, 0}, /* 0x62 - custom (will be filled out at run-time) */
+ {0x5a, 0x64, 65}, /* 0x63 1280x720 (LCD LVDS) */
+ {0x70, 0x28, 90}, /* 0x64 1152x864@60 */
+ {0x41, 0xc4, 32}, /* 0x65 848x480@60 */
+ {0x5c, 0xc6, 32}, /* 0x66 856x480@60 */
+ {0x76, 0xe7, 27}, /* 0x67 720x480@60 */
+ {0x5f, 0xc6, 33}, /* 0x68 720/768x576@60 */
+ {0x52, 0x27, 75}, /* 0x69 1920x1080i 60Hz interlaced */
+ {0x7c, 0x6b, 38}, /* 0x6a 960x540@60 */
+ {0xe3, 0x56, 41}, /* 0x6b 960x600@60 */
+ {0x45, 0x25, 83}, /* 0x6c 1280x800 */
+ {0x70, 0x28, 90}, /* 0x6d 1152x864@60 */
+ {0x15, 0xe1, 20}, /* 0x6e 640x400@60 (fake, not actually used) */
+ {0x5f, 0xc6, 33}, /* 0x6f 720x576@60 */
+ {0x37, 0x5a, 10}, /* 0x70 320x200@60 (fake, not actually used) */
+ {0x2b, 0xc2, 35} /* 0x71 768@576@60 */
+};
+
+#endif
diff --git a/drivers/usb/misc/trancevibrator.c b/drivers/usb/misc/trancevibrator.c
new file mode 100644
index 000000000..26baba3ab
--- /dev/null
+++ b/drivers/usb/misc/trancevibrator.c
@@ -0,0 +1,128 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * PlayStation 2 Trance Vibrator driver
+ *
+ * Copyright (C) 2006 Sam Hocevar <sam@zoy.org>
+ */
+
+/* Standard include files */
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+
+#define DRIVER_AUTHOR "Sam Hocevar, sam@zoy.org"
+#define DRIVER_DESC "PlayStation 2 Trance Vibrator driver"
+
+#define TRANCEVIBRATOR_VENDOR_ID 0x0b49 /* ASCII Corporation */
+#define TRANCEVIBRATOR_PRODUCT_ID 0x064f /* Trance Vibrator */
+
+static const struct usb_device_id id_table[] = {
+ { USB_DEVICE(TRANCEVIBRATOR_VENDOR_ID, TRANCEVIBRATOR_PRODUCT_ID) },
+ { },
+};
+MODULE_DEVICE_TABLE (usb, id_table);
+
+/* Driver-local specific stuff */
+struct trancevibrator {
+ struct usb_device *udev;
+ unsigned int speed;
+};
+
+static ssize_t speed_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct usb_interface *intf = to_usb_interface(dev);
+ struct trancevibrator *tv = usb_get_intfdata(intf);
+
+ return sprintf(buf, "%d\n", tv->speed);
+}
+
+static ssize_t speed_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct usb_interface *intf = to_usb_interface(dev);
+ struct trancevibrator *tv = usb_get_intfdata(intf);
+ int temp, retval, old;
+
+ retval = kstrtoint(buf, 10, &temp);
+ if (retval)
+ return retval;
+ if (temp > 255)
+ temp = 255;
+ else if (temp < 0)
+ temp = 0;
+ old = tv->speed;
+ tv->speed = temp;
+
+ dev_dbg(&tv->udev->dev, "speed = %d\n", tv->speed);
+
+ /* Set speed */
+ retval = usb_control_msg(tv->udev, usb_sndctrlpipe(tv->udev, 0),
+ 0x01, /* vendor request: set speed */
+ USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER,
+ tv->speed, /* speed value */
+ 0, NULL, 0, USB_CTRL_SET_TIMEOUT);
+ if (retval) {
+ tv->speed = old;
+ dev_dbg(&tv->udev->dev, "retval = %d\n", retval);
+ return retval;
+ }
+ return count;
+}
+static DEVICE_ATTR_RW(speed);
+
+static struct attribute *tv_attrs[] = {
+ &dev_attr_speed.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(tv);
+
+static int tv_probe(struct usb_interface *interface,
+ const struct usb_device_id *id)
+{
+ struct usb_device *udev = interface_to_usbdev(interface);
+ struct trancevibrator *dev;
+ int retval;
+
+ dev = kzalloc(sizeof(struct trancevibrator), GFP_KERNEL);
+ if (!dev) {
+ retval = -ENOMEM;
+ goto error;
+ }
+
+ dev->udev = usb_get_dev(udev);
+ usb_set_intfdata(interface, dev);
+
+ return 0;
+
+error:
+ kfree(dev);
+ return retval;
+}
+
+static void tv_disconnect(struct usb_interface *interface)
+{
+ struct trancevibrator *dev;
+
+ dev = usb_get_intfdata (interface);
+ usb_set_intfdata(interface, NULL);
+ usb_put_dev(dev->udev);
+ kfree(dev);
+}
+
+/* USB subsystem object */
+static struct usb_driver tv_driver = {
+ .name = "trancevibrator",
+ .probe = tv_probe,
+ .disconnect = tv_disconnect,
+ .id_table = id_table,
+ .dev_groups = tv_groups,
+};
+
+module_usb_driver(tv_driver);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/misc/usb251xb.c b/drivers/usb/misc/usb251xb.c
new file mode 100644
index 000000000..54337d72b
--- /dev/null
+++ b/drivers/usb/misc/usb251xb.c
@@ -0,0 +1,769 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Driver for Microchip USB251xB USB 2.0 Hi-Speed Hub Controller
+ * Configuration via SMBus.
+ *
+ * Copyright (c) 2017 SKIDATA AG
+ *
+ * This work is based on the USB3503 driver by Dongjin Kim and
+ * a not-accepted patch by Fabien Lahoudere, see:
+ * https://patchwork.kernel.org/patch/9257715/
+ */
+
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/gpio/driver.h>
+#include <linux/i2c.h>
+#include <linux/module.h>
+#include <linux/nls.h>
+#include <linux/of_device.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+
+/* Internal Register Set Addresses & Default Values acc. to DS00001692C */
+#define USB251XB_ADDR_VENDOR_ID_LSB 0x00
+#define USB251XB_ADDR_VENDOR_ID_MSB 0x01
+#define USB251XB_DEF_VENDOR_ID 0x0424
+
+#define USB251XB_ADDR_PRODUCT_ID_LSB 0x02
+#define USB251XB_ADDR_PRODUCT_ID_MSB 0x03
+
+#define USB251XB_ADDR_DEVICE_ID_LSB 0x04
+#define USB251XB_ADDR_DEVICE_ID_MSB 0x05
+#define USB251XB_DEF_DEVICE_ID 0x0BB3
+
+#define USB251XB_ADDR_CONFIG_DATA_1 0x06
+#define USB251XB_DEF_CONFIG_DATA_1 0x9B
+#define USB251XB_ADDR_CONFIG_DATA_2 0x07
+#define USB251XB_DEF_CONFIG_DATA_2 0x20
+#define USB251XB_ADDR_CONFIG_DATA_3 0x08
+#define USB251XB_DEF_CONFIG_DATA_3 0x02
+
+#define USB251XB_ADDR_NON_REMOVABLE_DEVICES 0x09
+#define USB251XB_DEF_NON_REMOVABLE_DEVICES 0x00
+
+#define USB251XB_ADDR_PORT_DISABLE_SELF 0x0A
+#define USB251XB_DEF_PORT_DISABLE_SELF 0x00
+#define USB251XB_ADDR_PORT_DISABLE_BUS 0x0B
+#define USB251XB_DEF_PORT_DISABLE_BUS 0x00
+
+#define USB251XB_ADDR_MAX_POWER_SELF 0x0C
+#define USB251XB_DEF_MAX_POWER_SELF 0x01
+#define USB251XB_ADDR_MAX_POWER_BUS 0x0D
+#define USB251XB_DEF_MAX_POWER_BUS 0x32
+
+#define USB251XB_ADDR_MAX_CURRENT_SELF 0x0E
+#define USB251XB_DEF_MAX_CURRENT_SELF 0x01
+#define USB251XB_ADDR_MAX_CURRENT_BUS 0x0F
+#define USB251XB_DEF_MAX_CURRENT_BUS 0x32
+
+#define USB251XB_ADDR_POWER_ON_TIME 0x10
+#define USB251XB_DEF_POWER_ON_TIME 0x32
+
+#define USB251XB_ADDR_LANGUAGE_ID_HIGH 0x11
+#define USB251XB_ADDR_LANGUAGE_ID_LOW 0x12
+#define USB251XB_DEF_LANGUAGE_ID 0x0000
+
+#define USB251XB_STRING_BUFSIZE 62
+#define USB251XB_ADDR_MANUFACTURER_STRING_LEN 0x13
+#define USB251XB_ADDR_MANUFACTURER_STRING 0x16
+#define USB251XB_DEF_MANUFACTURER_STRING "Microchip"
+
+#define USB251XB_ADDR_PRODUCT_STRING_LEN 0x14
+#define USB251XB_ADDR_PRODUCT_STRING 0x54
+
+#define USB251XB_ADDR_SERIAL_STRING_LEN 0x15
+#define USB251XB_ADDR_SERIAL_STRING 0x92
+#define USB251XB_DEF_SERIAL_STRING ""
+
+#define USB251XB_ADDR_BATTERY_CHARGING_ENABLE 0xD0
+#define USB251XB_DEF_BATTERY_CHARGING_ENABLE 0x00
+
+#define USB251XB_ADDR_BOOST_UP 0xF6
+#define USB251XB_DEF_BOOST_UP 0x00
+#define USB251XB_ADDR_BOOST_57 0xF7
+#define USB251XB_DEF_BOOST_57 0x00
+#define USB251XB_ADDR_BOOST_14 0xF8
+#define USB251XB_DEF_BOOST_14 0x00
+
+#define USB251XB_ADDR_PORT_SWAP 0xFA
+#define USB251XB_DEF_PORT_SWAP 0x00
+
+#define USB251XB_ADDR_PORT_MAP_12 0xFB
+#define USB251XB_DEF_PORT_MAP_12 0x00
+#define USB251XB_ADDR_PORT_MAP_34 0xFC
+#define USB251XB_DEF_PORT_MAP_34 0x00 /* USB251{3B/i,4B/i,7/i} only */
+#define USB251XB_ADDR_PORT_MAP_56 0xFD
+#define USB251XB_DEF_PORT_MAP_56 0x00 /* USB2517/i only */
+#define USB251XB_ADDR_PORT_MAP_7 0xFE
+#define USB251XB_DEF_PORT_MAP_7 0x00 /* USB2517/i only */
+
+#define USB251XB_ADDR_STATUS_COMMAND 0xFF
+#define USB251XB_STATUS_COMMAND_SMBUS_DOWN 0x04
+#define USB251XB_STATUS_COMMAND_RESET 0x02
+#define USB251XB_STATUS_COMMAND_ATTACH 0x01
+
+#define USB251XB_I2C_REG_SZ 0x100
+#define USB251XB_I2C_WRITE_SZ 0x10
+
+#define DRIVER_NAME "usb251xb"
+#define DRIVER_DESC "Microchip USB 2.0 Hi-Speed Hub Controller"
+
+struct usb251xb {
+ struct device *dev;
+ struct i2c_client *i2c;
+ struct regulator *vdd;
+ u8 skip_config;
+ struct gpio_desc *gpio_reset;
+ u16 vendor_id;
+ u16 product_id;
+ u16 device_id;
+ u8 conf_data1;
+ u8 conf_data2;
+ u8 conf_data3;
+ u8 non_rem_dev;
+ u8 port_disable_sp;
+ u8 port_disable_bp;
+ u8 max_power_sp;
+ u8 max_power_bp;
+ u8 max_current_sp;
+ u8 max_current_bp;
+ u8 power_on_time;
+ u16 lang_id;
+ u8 manufacturer_len;
+ u8 product_len;
+ u8 serial_len;
+ char manufacturer[USB251XB_STRING_BUFSIZE];
+ char product[USB251XB_STRING_BUFSIZE];
+ char serial[USB251XB_STRING_BUFSIZE];
+ u8 bat_charge_en;
+ u8 boost_up;
+ u8 boost_57;
+ u8 boost_14;
+ u8 port_swap;
+ u8 port_map12;
+ u8 port_map34;
+ u8 port_map56;
+ u8 port_map7;
+ u8 status;
+};
+
+struct usb251xb_data {
+ u16 product_id;
+ u8 port_cnt;
+ bool led_support;
+ bool bat_support;
+ char product_str[USB251XB_STRING_BUFSIZE / 2]; /* ASCII string */
+};
+
+static const struct usb251xb_data usb2422_data = {
+ .product_id = 0x2422,
+ .port_cnt = 2,
+ .led_support = false,
+ .bat_support = true,
+ .product_str = "USB2422",
+};
+
+static const struct usb251xb_data usb2512b_data = {
+ .product_id = 0x2512,
+ .port_cnt = 2,
+ .led_support = false,
+ .bat_support = true,
+ .product_str = "USB2512B",
+};
+
+static const struct usb251xb_data usb2512bi_data = {
+ .product_id = 0x2512,
+ .port_cnt = 2,
+ .led_support = false,
+ .bat_support = true,
+ .product_str = "USB2512Bi",
+};
+
+static const struct usb251xb_data usb2513b_data = {
+ .product_id = 0x2513,
+ .port_cnt = 3,
+ .led_support = false,
+ .bat_support = true,
+ .product_str = "USB2513B",
+};
+
+static const struct usb251xb_data usb2513bi_data = {
+ .product_id = 0x2513,
+ .port_cnt = 3,
+ .led_support = false,
+ .bat_support = true,
+ .product_str = "USB2513Bi",
+};
+
+static const struct usb251xb_data usb2514b_data = {
+ .product_id = 0x2514,
+ .port_cnt = 4,
+ .led_support = false,
+ .bat_support = true,
+ .product_str = "USB2514B",
+};
+
+static const struct usb251xb_data usb2514bi_data = {
+ .product_id = 0x2514,
+ .port_cnt = 4,
+ .led_support = false,
+ .bat_support = true,
+ .product_str = "USB2514Bi",
+};
+
+static const struct usb251xb_data usb2517_data = {
+ .product_id = 0x2517,
+ .port_cnt = 7,
+ .led_support = true,
+ .bat_support = false,
+ .product_str = "USB2517",
+};
+
+static const struct usb251xb_data usb2517i_data = {
+ .product_id = 0x2517,
+ .port_cnt = 7,
+ .led_support = true,
+ .bat_support = false,
+ .product_str = "USB2517i",
+};
+
+#ifdef CONFIG_GPIOLIB
+static int usb251xb_check_dev_children(struct device *dev, void *child)
+{
+ if (dev->type == &i2c_adapter_type) {
+ return device_for_each_child(dev, child,
+ usb251xb_check_dev_children);
+ }
+
+ return (dev == child);
+}
+
+static int usb251x_check_gpio_chip(struct usb251xb *hub)
+{
+ struct gpio_chip *gc = gpiod_to_chip(hub->gpio_reset);
+ struct i2c_adapter *adap = hub->i2c->adapter;
+ int ret;
+
+ if (!hub->gpio_reset)
+ return 0;
+
+ if (!gc)
+ return -EINVAL;
+
+ ret = usb251xb_check_dev_children(&adap->dev, gc->parent);
+ if (ret) {
+ dev_err(hub->dev, "Reset GPIO chip is at the same i2c-bus\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+#else
+static int usb251x_check_gpio_chip(struct usb251xb *hub)
+{
+ return 0;
+}
+#endif
+
+static void usb251xb_reset(struct usb251xb *hub)
+{
+ if (!hub->gpio_reset)
+ return;
+
+ i2c_lock_bus(hub->i2c->adapter, I2C_LOCK_SEGMENT);
+
+ gpiod_set_value_cansleep(hub->gpio_reset, 1);
+ usleep_range(1, 10); /* >=1us RESET_N asserted */
+ gpiod_set_value_cansleep(hub->gpio_reset, 0);
+
+ /* wait for hub recovery/stabilization */
+ usleep_range(500, 750); /* >=500us after RESET_N deasserted */
+
+ i2c_unlock_bus(hub->i2c->adapter, I2C_LOCK_SEGMENT);
+}
+
+static int usb251xb_connect(struct usb251xb *hub)
+{
+ struct device *dev = hub->dev;
+ int err, i;
+ char i2c_wb[USB251XB_I2C_REG_SZ];
+
+ memset(i2c_wb, 0, USB251XB_I2C_REG_SZ);
+
+ if (hub->skip_config) {
+ dev_info(dev, "Skip hub configuration, only attach.\n");
+ i2c_wb[0] = 0x01;
+ i2c_wb[1] = USB251XB_STATUS_COMMAND_ATTACH;
+
+ usb251xb_reset(hub);
+
+ err = i2c_smbus_write_i2c_block_data(hub->i2c,
+ USB251XB_ADDR_STATUS_COMMAND, 2, i2c_wb);
+ if (err) {
+ dev_err(dev, "attaching hub failed: %d\n", err);
+ return err;
+ }
+ return 0;
+ }
+
+ i2c_wb[USB251XB_ADDR_VENDOR_ID_MSB] = (hub->vendor_id >> 8) & 0xFF;
+ i2c_wb[USB251XB_ADDR_VENDOR_ID_LSB] = hub->vendor_id & 0xFF;
+ i2c_wb[USB251XB_ADDR_PRODUCT_ID_MSB] = (hub->product_id >> 8) & 0xFF;
+ i2c_wb[USB251XB_ADDR_PRODUCT_ID_LSB] = hub->product_id & 0xFF;
+ i2c_wb[USB251XB_ADDR_DEVICE_ID_MSB] = (hub->device_id >> 8) & 0xFF;
+ i2c_wb[USB251XB_ADDR_DEVICE_ID_LSB] = hub->device_id & 0xFF;
+ i2c_wb[USB251XB_ADDR_CONFIG_DATA_1] = hub->conf_data1;
+ i2c_wb[USB251XB_ADDR_CONFIG_DATA_2] = hub->conf_data2;
+ i2c_wb[USB251XB_ADDR_CONFIG_DATA_3] = hub->conf_data3;
+ i2c_wb[USB251XB_ADDR_NON_REMOVABLE_DEVICES] = hub->non_rem_dev;
+ i2c_wb[USB251XB_ADDR_PORT_DISABLE_SELF] = hub->port_disable_sp;
+ i2c_wb[USB251XB_ADDR_PORT_DISABLE_BUS] = hub->port_disable_bp;
+ i2c_wb[USB251XB_ADDR_MAX_POWER_SELF] = hub->max_power_sp;
+ i2c_wb[USB251XB_ADDR_MAX_POWER_BUS] = hub->max_power_bp;
+ i2c_wb[USB251XB_ADDR_MAX_CURRENT_SELF] = hub->max_current_sp;
+ i2c_wb[USB251XB_ADDR_MAX_CURRENT_BUS] = hub->max_current_bp;
+ i2c_wb[USB251XB_ADDR_POWER_ON_TIME] = hub->power_on_time;
+ i2c_wb[USB251XB_ADDR_LANGUAGE_ID_HIGH] = (hub->lang_id >> 8) & 0xFF;
+ i2c_wb[USB251XB_ADDR_LANGUAGE_ID_LOW] = hub->lang_id & 0xFF;
+ i2c_wb[USB251XB_ADDR_MANUFACTURER_STRING_LEN] = hub->manufacturer_len;
+ i2c_wb[USB251XB_ADDR_PRODUCT_STRING_LEN] = hub->product_len;
+ i2c_wb[USB251XB_ADDR_SERIAL_STRING_LEN] = hub->serial_len;
+ memcpy(&i2c_wb[USB251XB_ADDR_MANUFACTURER_STRING], hub->manufacturer,
+ USB251XB_STRING_BUFSIZE);
+ memcpy(&i2c_wb[USB251XB_ADDR_SERIAL_STRING], hub->serial,
+ USB251XB_STRING_BUFSIZE);
+ memcpy(&i2c_wb[USB251XB_ADDR_PRODUCT_STRING], hub->product,
+ USB251XB_STRING_BUFSIZE);
+ i2c_wb[USB251XB_ADDR_BATTERY_CHARGING_ENABLE] = hub->bat_charge_en;
+ i2c_wb[USB251XB_ADDR_BOOST_UP] = hub->boost_up;
+ i2c_wb[USB251XB_ADDR_BOOST_57] = hub->boost_57;
+ i2c_wb[USB251XB_ADDR_BOOST_14] = hub->boost_14;
+ i2c_wb[USB251XB_ADDR_PORT_SWAP] = hub->port_swap;
+ i2c_wb[USB251XB_ADDR_PORT_MAP_12] = hub->port_map12;
+ i2c_wb[USB251XB_ADDR_PORT_MAP_34] = hub->port_map34;
+ i2c_wb[USB251XB_ADDR_PORT_MAP_56] = hub->port_map56;
+ i2c_wb[USB251XB_ADDR_PORT_MAP_7] = hub->port_map7;
+ i2c_wb[USB251XB_ADDR_STATUS_COMMAND] = USB251XB_STATUS_COMMAND_ATTACH;
+
+ usb251xb_reset(hub);
+
+ /* write registers */
+ for (i = 0; i < (USB251XB_I2C_REG_SZ / USB251XB_I2C_WRITE_SZ); i++) {
+ int offset = i * USB251XB_I2C_WRITE_SZ;
+ char wbuf[USB251XB_I2C_WRITE_SZ + 1];
+
+ /* The first data byte transferred tells the hub how many data
+ * bytes will follow (byte count).
+ */
+ wbuf[0] = USB251XB_I2C_WRITE_SZ;
+ memcpy(&wbuf[1], &i2c_wb[offset], USB251XB_I2C_WRITE_SZ);
+
+ dev_dbg(dev, "writing %d byte block %d to 0x%02X\n",
+ USB251XB_I2C_WRITE_SZ, i, offset);
+
+ err = i2c_smbus_write_i2c_block_data(hub->i2c, offset,
+ USB251XB_I2C_WRITE_SZ + 1,
+ wbuf);
+ if (err)
+ goto out_err;
+ }
+
+ dev_info(dev, "Hub configuration was successful.\n");
+ return 0;
+
+out_err:
+ dev_err(dev, "configuring block %d failed: %d\n", i, err);
+ return err;
+}
+
+#ifdef CONFIG_OF
+static void usb251xb_get_ports_field(struct usb251xb *hub,
+ const char *prop_name, u8 port_cnt,
+ bool ds_only, u8 *fld)
+{
+ struct device *dev = hub->dev;
+ struct property *prop;
+ const __be32 *p;
+ u32 port;
+
+ of_property_for_each_u32(dev->of_node, prop_name, prop, p, port) {
+ if ((port >= ds_only ? 1 : 0) && (port <= port_cnt))
+ *fld |= BIT(port);
+ else
+ dev_warn(dev, "port %u doesn't exist\n", port);
+ }
+}
+
+static int usb251xb_get_ofdata(struct usb251xb *hub,
+ const struct usb251xb_data *data)
+{
+ struct device *dev = hub->dev;
+ struct device_node *np = dev->of_node;
+ int len;
+ u32 property_u32 = 0;
+ const char *cproperty_char;
+ char str[USB251XB_STRING_BUFSIZE / 2];
+
+ if (!np) {
+ dev_err(dev, "failed to get ofdata\n");
+ return -ENODEV;
+ }
+
+ if (of_get_property(np, "skip-config", NULL))
+ hub->skip_config = 1;
+ else
+ hub->skip_config = 0;
+
+ hub->gpio_reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
+ if (IS_ERR(hub->gpio_reset))
+ return dev_err_probe(dev, PTR_ERR(hub->gpio_reset),
+ "unable to request GPIO reset pin\n");
+
+ if (of_property_read_u16_array(np, "vendor-id", &hub->vendor_id, 1))
+ hub->vendor_id = USB251XB_DEF_VENDOR_ID;
+
+ if (of_property_read_u16_array(np, "product-id",
+ &hub->product_id, 1))
+ hub->product_id = data->product_id;
+
+ if (of_property_read_u16_array(np, "device-id", &hub->device_id, 1))
+ hub->device_id = USB251XB_DEF_DEVICE_ID;
+
+ hub->conf_data1 = USB251XB_DEF_CONFIG_DATA_1;
+ if (of_get_property(np, "self-powered", NULL)) {
+ hub->conf_data1 |= BIT(7);
+
+ /* Configure Over-Current sens when self-powered */
+ hub->conf_data1 &= ~BIT(2);
+ if (of_get_property(np, "ganged-sensing", NULL))
+ hub->conf_data1 &= ~BIT(1);
+ else if (of_get_property(np, "individual-sensing", NULL))
+ hub->conf_data1 |= BIT(1);
+ } else if (of_get_property(np, "bus-powered", NULL)) {
+ hub->conf_data1 &= ~BIT(7);
+
+ /* Disable Over-Current sense when bus-powered */
+ hub->conf_data1 |= BIT(2);
+ }
+
+ if (of_get_property(np, "disable-hi-speed", NULL))
+ hub->conf_data1 |= BIT(5);
+
+ if (of_get_property(np, "multi-tt", NULL))
+ hub->conf_data1 |= BIT(4);
+ else if (of_get_property(np, "single-tt", NULL))
+ hub->conf_data1 &= ~BIT(4);
+
+ if (of_get_property(np, "disable-eop", NULL))
+ hub->conf_data1 |= BIT(3);
+
+ if (of_get_property(np, "individual-port-switching", NULL))
+ hub->conf_data1 |= BIT(0);
+ else if (of_get_property(np, "ganged-port-switching", NULL))
+ hub->conf_data1 &= ~BIT(0);
+
+ hub->conf_data2 = USB251XB_DEF_CONFIG_DATA_2;
+ if (of_get_property(np, "dynamic-power-switching", NULL))
+ hub->conf_data2 |= BIT(7);
+
+ if (!of_property_read_u32(np, "oc-delay-us", &property_u32)) {
+ if (property_u32 == 100) {
+ /* 100 us*/
+ hub->conf_data2 &= ~BIT(5);
+ hub->conf_data2 &= ~BIT(4);
+ } else if (property_u32 == 4000) {
+ /* 4 ms */
+ hub->conf_data2 &= ~BIT(5);
+ hub->conf_data2 |= BIT(4);
+ } else if (property_u32 == 16000) {
+ /* 16 ms */
+ hub->conf_data2 |= BIT(5);
+ hub->conf_data2 |= BIT(4);
+ } else {
+ /* 8 ms (DEFAULT) */
+ hub->conf_data2 |= BIT(5);
+ hub->conf_data2 &= ~BIT(4);
+ }
+ }
+
+ if (of_get_property(np, "compound-device", NULL))
+ hub->conf_data2 |= BIT(3);
+
+ hub->conf_data3 = USB251XB_DEF_CONFIG_DATA_3;
+ if (of_get_property(np, "port-mapping-mode", NULL))
+ hub->conf_data3 |= BIT(3);
+
+ if (data->led_support && of_get_property(np, "led-usb-mode", NULL))
+ hub->conf_data3 &= ~BIT(1);
+
+ if (of_get_property(np, "string-support", NULL))
+ hub->conf_data3 |= BIT(0);
+
+ hub->non_rem_dev = USB251XB_DEF_NON_REMOVABLE_DEVICES;
+ usb251xb_get_ports_field(hub, "non-removable-ports", data->port_cnt,
+ true, &hub->non_rem_dev);
+
+ hub->port_disable_sp = USB251XB_DEF_PORT_DISABLE_SELF;
+ usb251xb_get_ports_field(hub, "sp-disabled-ports", data->port_cnt,
+ true, &hub->port_disable_sp);
+
+ hub->port_disable_bp = USB251XB_DEF_PORT_DISABLE_BUS;
+ usb251xb_get_ports_field(hub, "bp-disabled-ports", data->port_cnt,
+ true, &hub->port_disable_bp);
+
+ hub->max_power_sp = USB251XB_DEF_MAX_POWER_SELF;
+ if (!of_property_read_u32(np, "sp-max-total-current-microamp",
+ &property_u32))
+ hub->max_power_sp = min_t(u8, property_u32 / 2000, 50);
+
+ hub->max_power_bp = USB251XB_DEF_MAX_POWER_BUS;
+ if (!of_property_read_u32(np, "bp-max-total-current-microamp",
+ &property_u32))
+ hub->max_power_bp = min_t(u8, property_u32 / 2000, 255);
+
+ hub->max_current_sp = USB251XB_DEF_MAX_CURRENT_SELF;
+ if (!of_property_read_u32(np, "sp-max-removable-current-microamp",
+ &property_u32))
+ hub->max_current_sp = min_t(u8, property_u32 / 2000, 50);
+
+ hub->max_current_bp = USB251XB_DEF_MAX_CURRENT_BUS;
+ if (!of_property_read_u32(np, "bp-max-removable-current-microamp",
+ &property_u32))
+ hub->max_current_bp = min_t(u8, property_u32 / 2000, 255);
+
+ hub->power_on_time = USB251XB_DEF_POWER_ON_TIME;
+ if (!of_property_read_u32(np, "power-on-time-ms", &property_u32))
+ hub->power_on_time = min_t(u8, property_u32 / 2, 255);
+
+ if (of_property_read_u16_array(np, "language-id", &hub->lang_id, 1))
+ hub->lang_id = USB251XB_DEF_LANGUAGE_ID;
+
+ if (of_property_read_u8(np, "boost-up", &hub->boost_up))
+ hub->boost_up = USB251XB_DEF_BOOST_UP;
+
+ cproperty_char = of_get_property(np, "manufacturer", NULL);
+ strscpy(str, cproperty_char ? : USB251XB_DEF_MANUFACTURER_STRING,
+ sizeof(str));
+ hub->manufacturer_len = strlen(str) & 0xFF;
+ memset(hub->manufacturer, 0, USB251XB_STRING_BUFSIZE);
+ len = min_t(size_t, USB251XB_STRING_BUFSIZE / 2, strlen(str));
+ len = utf8s_to_utf16s(str, len, UTF16_LITTLE_ENDIAN,
+ (wchar_t *)hub->manufacturer,
+ USB251XB_STRING_BUFSIZE);
+
+ cproperty_char = of_get_property(np, "product", NULL);
+ strscpy(str, cproperty_char ? : data->product_str, sizeof(str));
+ hub->product_len = strlen(str) & 0xFF;
+ memset(hub->product, 0, USB251XB_STRING_BUFSIZE);
+ len = min_t(size_t, USB251XB_STRING_BUFSIZE / 2, strlen(str));
+ len = utf8s_to_utf16s(str, len, UTF16_LITTLE_ENDIAN,
+ (wchar_t *)hub->product,
+ USB251XB_STRING_BUFSIZE);
+
+ cproperty_char = of_get_property(np, "serial", NULL);
+ strscpy(str, cproperty_char ? : USB251XB_DEF_SERIAL_STRING,
+ sizeof(str));
+ hub->serial_len = strlen(str) & 0xFF;
+ memset(hub->serial, 0, USB251XB_STRING_BUFSIZE);
+ len = min_t(size_t, USB251XB_STRING_BUFSIZE / 2, strlen(str));
+ len = utf8s_to_utf16s(str, len, UTF16_LITTLE_ENDIAN,
+ (wchar_t *)hub->serial,
+ USB251XB_STRING_BUFSIZE);
+
+ /*
+ * The datasheet documents the register as 'Port Swap' but in real the
+ * register controls the USB DP/DM signal swapping for each port.
+ */
+ hub->port_swap = USB251XB_DEF_PORT_SWAP;
+ usb251xb_get_ports_field(hub, "swap-dx-lanes", data->port_cnt,
+ false, &hub->port_swap);
+
+ /* The following parameters are currently not exposed to devicetree, but
+ * may be as soon as needed.
+ */
+ hub->bat_charge_en = USB251XB_DEF_BATTERY_CHARGING_ENABLE;
+ hub->boost_57 = USB251XB_DEF_BOOST_57;
+ hub->boost_14 = USB251XB_DEF_BOOST_14;
+ hub->port_map12 = USB251XB_DEF_PORT_MAP_12;
+ hub->port_map34 = USB251XB_DEF_PORT_MAP_34;
+ hub->port_map56 = USB251XB_DEF_PORT_MAP_56;
+ hub->port_map7 = USB251XB_DEF_PORT_MAP_7;
+
+ return 0;
+}
+
+static const struct of_device_id usb251xb_of_match[] = {
+ {
+ .compatible = "microchip,usb2422",
+ .data = &usb2422_data,
+ }, {
+ .compatible = "microchip,usb2512b",
+ .data = &usb2512b_data,
+ }, {
+ .compatible = "microchip,usb2512bi",
+ .data = &usb2512bi_data,
+ }, {
+ .compatible = "microchip,usb2513b",
+ .data = &usb2513b_data,
+ }, {
+ .compatible = "microchip,usb2513bi",
+ .data = &usb2513bi_data,
+ }, {
+ .compatible = "microchip,usb2514b",
+ .data = &usb2514b_data,
+ }, {
+ .compatible = "microchip,usb2514bi",
+ .data = &usb2514bi_data,
+ }, {
+ .compatible = "microchip,usb2517",
+ .data = &usb2517_data,
+ }, {
+ .compatible = "microchip,usb2517i",
+ .data = &usb2517i_data,
+ }, {
+ /* sentinel */
+ }
+};
+MODULE_DEVICE_TABLE(of, usb251xb_of_match);
+#else /* CONFIG_OF */
+static int usb251xb_get_ofdata(struct usb251xb *hub,
+ const struct usb251xb_data *data)
+{
+ return 0;
+}
+#endif /* CONFIG_OF */
+
+static void usb251xb_regulator_disable_action(void *data)
+{
+ struct usb251xb *hub = data;
+
+ regulator_disable(hub->vdd);
+}
+
+static int usb251xb_probe(struct usb251xb *hub)
+{
+ struct device *dev = hub->dev;
+ struct device_node *np = dev->of_node;
+ const struct usb251xb_data *usb_data = of_device_get_match_data(dev);
+ int err;
+
+ if (np && usb_data) {
+ err = usb251xb_get_ofdata(hub, usb_data);
+ if (err) {
+ dev_err(dev, "failed to get ofdata: %d\n", err);
+ return err;
+ }
+ }
+
+ /*
+ * usb251x SMBus-slave SCL lane is muxed with CFG_SEL0 pin. So if anyone
+ * tries to work with the bus at the moment the hub reset is released,
+ * it may cause an invalid config being latched by usb251x. Particularly
+ * one of the config modes makes the hub loading a default registers
+ * value without SMBus-slave interface activation. If the hub
+ * accidentally gets this mode, this will cause the driver SMBus-
+ * functions failure. Normally we could just lock the SMBus-segment the
+ * hub i2c-interface resides for the device-specific reset timing. But
+ * the GPIO controller, which is used to handle the hub reset, might be
+ * placed at the same i2c-bus segment. In this case an error should be
+ * returned since we can't safely use the GPIO controller to clear the
+ * reset state (it may affect the hub configuration) and we can't lock
+ * the i2c-bus segment (it will cause a deadlock).
+ */
+ err = usb251x_check_gpio_chip(hub);
+ if (err)
+ return err;
+
+ hub->vdd = devm_regulator_get(dev, "vdd");
+ if (IS_ERR(hub->vdd))
+ return PTR_ERR(hub->vdd);
+
+ err = regulator_enable(hub->vdd);
+ if (err)
+ return err;
+
+ err = devm_add_action_or_reset(dev,
+ usb251xb_regulator_disable_action, hub);
+ if (err)
+ return err;
+
+ err = usb251xb_connect(hub);
+ if (err) {
+ dev_err(dev, "Failed to connect hub (%d)\n", err);
+ return err;
+ }
+
+ dev_info(dev, "Hub probed successfully\n");
+
+ return 0;
+}
+
+static int usb251xb_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct usb251xb *hub;
+
+ hub = devm_kzalloc(&i2c->dev, sizeof(struct usb251xb), GFP_KERNEL);
+ if (!hub)
+ return -ENOMEM;
+
+ i2c_set_clientdata(i2c, hub);
+ hub->dev = &i2c->dev;
+ hub->i2c = i2c;
+
+ return usb251xb_probe(hub);
+}
+
+static int __maybe_unused usb251xb_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct usb251xb *hub = i2c_get_clientdata(client);
+
+ return regulator_disable(hub->vdd);
+}
+
+static int __maybe_unused usb251xb_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct usb251xb *hub = i2c_get_clientdata(client);
+ int err;
+
+ err = regulator_enable(hub->vdd);
+ if (err)
+ return err;
+
+ return usb251xb_connect(hub);
+}
+
+static SIMPLE_DEV_PM_OPS(usb251xb_pm_ops, usb251xb_suspend, usb251xb_resume);
+
+static const struct i2c_device_id usb251xb_id[] = {
+ { "usb2422", 0 },
+ { "usb2512b", 0 },
+ { "usb2512bi", 0 },
+ { "usb2513b", 0 },
+ { "usb2513bi", 0 },
+ { "usb2514b", 0 },
+ { "usb2514bi", 0 },
+ { "usb2517", 0 },
+ { "usb2517i", 0 },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(i2c, usb251xb_id);
+
+static struct i2c_driver usb251xb_i2c_driver = {
+ .driver = {
+ .name = DRIVER_NAME,
+ .of_match_table = of_match_ptr(usb251xb_of_match),
+ .pm = &usb251xb_pm_ops,
+ },
+ .probe = usb251xb_i2c_probe,
+ .id_table = usb251xb_id,
+};
+
+module_i2c_driver(usb251xb_i2c_driver);
+
+MODULE_AUTHOR("Richard Leitner <richard.leitner@skidata.com>");
+MODULE_DESCRIPTION("USB251x/xBi USB 2.0 Hub Controller Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/misc/usb3503.c b/drivers/usb/misc/usb3503.c
new file mode 100644
index 000000000..c70ca475c
--- /dev/null
+++ b/drivers/usb/misc/usb3503.c
@@ -0,0 +1,449 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Driver for SMSC USB3503 USB 2.0 hub controller driver
+ *
+ * Copyright (c) 2012-2013 Dongjin Kim (tobetter@gmail.com)
+ */
+
+#include <linux/clk.h>
+#include <linux/i2c.h>
+#include <linux/gpio/consumer.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/platform_data/usb3503.h>
+#include <linux/regmap.h>
+
+#define USB3503_VIDL 0x00
+#define USB3503_VIDM 0x01
+#define USB3503_PIDL 0x02
+#define USB3503_PIDM 0x03
+#define USB3503_DIDL 0x04
+#define USB3503_DIDM 0x05
+
+#define USB3503_CFG1 0x06
+#define USB3503_SELF_BUS_PWR (1 << 7)
+
+#define USB3503_CFG2 0x07
+#define USB3503_CFG3 0x08
+#define USB3503_NRD 0x09
+
+#define USB3503_PDS 0x0a
+
+#define USB3503_SP_ILOCK 0xe7
+#define USB3503_SPILOCK_CONNECT (1 << 1)
+#define USB3503_SPILOCK_CONFIG (1 << 0)
+
+#define USB3503_CFGP 0xee
+#define USB3503_CLKSUSP (1 << 7)
+
+#define USB3503_RESET 0xff
+
+struct usb3503 {
+ enum usb3503_mode mode;
+ struct regmap *regmap;
+ struct device *dev;
+ struct clk *clk;
+ u8 port_off_mask;
+ struct gpio_desc *intn;
+ struct gpio_desc *reset;
+ struct gpio_desc *connect;
+ bool secondary_ref_clk;
+};
+
+static int usb3503_reset(struct usb3503 *hub, int state)
+{
+ if (!state && hub->connect)
+ gpiod_set_value_cansleep(hub->connect, 0);
+
+ if (hub->reset)
+ gpiod_set_value_cansleep(hub->reset, !state);
+
+ /* Wait T_HUBINIT == 4ms for hub logic to stabilize */
+ if (state)
+ usleep_range(4000, 10000);
+
+ return 0;
+}
+
+static int usb3503_connect(struct usb3503 *hub)
+{
+ struct device *dev = hub->dev;
+ int err;
+
+ usb3503_reset(hub, 1);
+
+ if (hub->regmap) {
+ /* SP_ILOCK: set connect_n, config_n for config */
+ err = regmap_write(hub->regmap, USB3503_SP_ILOCK,
+ (USB3503_SPILOCK_CONNECT
+ | USB3503_SPILOCK_CONFIG));
+ if (err < 0) {
+ dev_err(dev, "SP_ILOCK failed (%d)\n", err);
+ return err;
+ }
+
+ /* PDS : Set the ports which are disabled in self-powered mode. */
+ if (hub->port_off_mask) {
+ err = regmap_update_bits(hub->regmap, USB3503_PDS,
+ hub->port_off_mask,
+ hub->port_off_mask);
+ if (err < 0) {
+ dev_err(dev, "PDS failed (%d)\n", err);
+ return err;
+ }
+ }
+
+ /* CFG1 : Set SELF_BUS_PWR, this enables self-powered operation. */
+ err = regmap_update_bits(hub->regmap, USB3503_CFG1,
+ USB3503_SELF_BUS_PWR,
+ USB3503_SELF_BUS_PWR);
+ if (err < 0) {
+ dev_err(dev, "CFG1 failed (%d)\n", err);
+ return err;
+ }
+
+ /* SP_LOCK: clear connect_n, config_n for hub connect */
+ err = regmap_update_bits(hub->regmap, USB3503_SP_ILOCK,
+ (USB3503_SPILOCK_CONNECT
+ | USB3503_SPILOCK_CONFIG), 0);
+ if (err < 0) {
+ dev_err(dev, "SP_ILOCK failed (%d)\n", err);
+ return err;
+ }
+ }
+
+ if (hub->connect)
+ gpiod_set_value_cansleep(hub->connect, 1);
+
+ hub->mode = USB3503_MODE_HUB;
+ dev_info(dev, "switched to HUB mode\n");
+
+ return 0;
+}
+
+static int usb3503_switch_mode(struct usb3503 *hub, enum usb3503_mode mode)
+{
+ struct device *dev = hub->dev;
+ int err = 0;
+
+ switch (mode) {
+ case USB3503_MODE_HUB:
+ err = usb3503_connect(hub);
+ break;
+
+ case USB3503_MODE_STANDBY:
+ usb3503_reset(hub, 0);
+ dev_info(dev, "switched to STANDBY mode\n");
+ break;
+
+ default:
+ dev_err(dev, "unknown mode is requested\n");
+ err = -EINVAL;
+ break;
+ }
+
+ return err;
+}
+
+static const struct regmap_config usb3503_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+
+ .max_register = USB3503_RESET,
+};
+
+static int usb3503_probe(struct usb3503 *hub)
+{
+ struct device *dev = hub->dev;
+ struct usb3503_platform_data *pdata = dev_get_platdata(dev);
+ struct device_node *np = dev->of_node;
+ int err;
+ bool is_clk_enabled = false;
+ u32 mode = USB3503_MODE_HUB;
+ const u32 *property;
+ enum gpiod_flags flags;
+ int len;
+
+ if (pdata) {
+ hub->port_off_mask = pdata->port_off_mask;
+ hub->mode = pdata->initial_mode;
+ } else if (np) {
+ u32 rate = 0;
+ hub->port_off_mask = 0;
+
+ if (!of_property_read_u32(np, "refclk-frequency", &rate)) {
+ switch (rate) {
+ case 38400000:
+ case 26000000:
+ case 19200000:
+ case 12000000:
+ hub->secondary_ref_clk = 0;
+ break;
+ case 24000000:
+ case 27000000:
+ case 25000000:
+ case 50000000:
+ hub->secondary_ref_clk = 1;
+ break;
+ default:
+ dev_err(dev,
+ "unsupported reference clock rate (%d)\n",
+ (int) rate);
+ return -EINVAL;
+ }
+ }
+
+ hub->clk = devm_clk_get_optional(dev, "refclk");
+ if (IS_ERR(hub->clk)) {
+ dev_err(dev, "unable to request refclk (%ld)\n",
+ PTR_ERR(hub->clk));
+ return PTR_ERR(hub->clk);
+ }
+
+ if (rate != 0) {
+ err = clk_set_rate(hub->clk, rate);
+ if (err) {
+ dev_err(dev,
+ "unable to set reference clock rate to %d\n",
+ (int)rate);
+ return err;
+ }
+ }
+
+ err = clk_prepare_enable(hub->clk);
+ if (err) {
+ dev_err(dev, "unable to enable reference clock\n");
+ return err;
+ }
+
+ is_clk_enabled = true;
+ property = of_get_property(np, "disabled-ports", &len);
+ if (property && (len / sizeof(u32)) > 0) {
+ int i;
+ for (i = 0; i < len / sizeof(u32); i++) {
+ u32 port = be32_to_cpu(property[i]);
+ if ((1 <= port) && (port <= 3))
+ hub->port_off_mask |= (1 << port);
+ }
+ }
+
+ of_property_read_u32(np, "initial-mode", &mode);
+ hub->mode = mode;
+ }
+
+ if (hub->secondary_ref_clk)
+ flags = GPIOD_OUT_LOW;
+ else
+ flags = GPIOD_OUT_HIGH;
+ hub->intn = devm_gpiod_get_optional(dev, "intn", flags);
+ if (IS_ERR(hub->intn)) {
+ err = PTR_ERR(hub->intn);
+ goto err_clk;
+ }
+ if (hub->intn)
+ gpiod_set_consumer_name(hub->intn, "usb3503 intn");
+
+ hub->connect = devm_gpiod_get_optional(dev, "connect", GPIOD_OUT_LOW);
+ if (IS_ERR(hub->connect)) {
+ err = PTR_ERR(hub->connect);
+ goto err_clk;
+ }
+ if (hub->connect)
+ gpiod_set_consumer_name(hub->connect, "usb3503 connect");
+
+ hub->reset = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
+ if (IS_ERR(hub->reset)) {
+ err = PTR_ERR(hub->reset);
+ goto err_clk;
+ }
+ if (hub->reset) {
+ /* Datasheet defines a hardware reset to be at least 100us */
+ usleep_range(100, 10000);
+ gpiod_set_consumer_name(hub->reset, "usb3503 reset");
+ }
+
+ if (hub->port_off_mask && !hub->regmap)
+ dev_err(dev, "Ports disabled with no control interface\n");
+
+ usb3503_switch_mode(hub, hub->mode);
+
+ dev_info(dev, "%s: probed in %s mode\n", __func__,
+ (hub->mode == USB3503_MODE_HUB) ? "hub" : "standby");
+
+ return 0;
+
+err_clk:
+ if (is_clk_enabled)
+ clk_disable_unprepare(hub->clk);
+ return err;
+}
+
+static int usb3503_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct usb3503 *hub;
+ int err;
+
+ hub = devm_kzalloc(&i2c->dev, sizeof(struct usb3503), GFP_KERNEL);
+ if (!hub)
+ return -ENOMEM;
+
+ i2c_set_clientdata(i2c, hub);
+ hub->regmap = devm_regmap_init_i2c(i2c, &usb3503_regmap_config);
+ if (IS_ERR(hub->regmap)) {
+ err = PTR_ERR(hub->regmap);
+ dev_err(&i2c->dev, "Failed to initialise regmap: %d\n", err);
+ return err;
+ }
+ hub->dev = &i2c->dev;
+
+ return usb3503_probe(hub);
+}
+
+static void usb3503_i2c_remove(struct i2c_client *i2c)
+{
+ struct usb3503 *hub;
+
+ hub = i2c_get_clientdata(i2c);
+ clk_disable_unprepare(hub->clk);
+}
+
+static int usb3503_platform_probe(struct platform_device *pdev)
+{
+ struct usb3503 *hub;
+
+ hub = devm_kzalloc(&pdev->dev, sizeof(struct usb3503), GFP_KERNEL);
+ if (!hub)
+ return -ENOMEM;
+ hub->dev = &pdev->dev;
+ platform_set_drvdata(pdev, hub);
+
+ return usb3503_probe(hub);
+}
+
+static int usb3503_platform_remove(struct platform_device *pdev)
+{
+ struct usb3503 *hub;
+
+ hub = platform_get_drvdata(pdev);
+ clk_disable_unprepare(hub->clk);
+
+ return 0;
+}
+
+static int __maybe_unused usb3503_suspend(struct usb3503 *hub)
+{
+ usb3503_switch_mode(hub, USB3503_MODE_STANDBY);
+ clk_disable_unprepare(hub->clk);
+
+ return 0;
+}
+
+static int __maybe_unused usb3503_resume(struct usb3503 *hub)
+{
+ clk_prepare_enable(hub->clk);
+ usb3503_switch_mode(hub, hub->mode);
+
+ return 0;
+}
+
+static int __maybe_unused usb3503_i2c_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+
+ return usb3503_suspend(i2c_get_clientdata(client));
+}
+
+static int __maybe_unused usb3503_i2c_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+
+ return usb3503_resume(i2c_get_clientdata(client));
+}
+
+static int __maybe_unused usb3503_platform_suspend(struct device *dev)
+{
+ return usb3503_suspend(dev_get_drvdata(dev));
+}
+
+static int __maybe_unused usb3503_platform_resume(struct device *dev)
+{
+ return usb3503_resume(dev_get_drvdata(dev));
+}
+
+static SIMPLE_DEV_PM_OPS(usb3503_i2c_pm_ops, usb3503_i2c_suspend,
+ usb3503_i2c_resume);
+
+static SIMPLE_DEV_PM_OPS(usb3503_platform_pm_ops, usb3503_platform_suspend,
+ usb3503_platform_resume);
+
+static const struct i2c_device_id usb3503_id[] = {
+ { USB3503_I2C_NAME, 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, usb3503_id);
+
+#ifdef CONFIG_OF
+static const struct of_device_id usb3503_of_match[] = {
+ { .compatible = "smsc,usb3503", },
+ { .compatible = "smsc,usb3503a", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, usb3503_of_match);
+#endif
+
+static struct i2c_driver usb3503_i2c_driver = {
+ .driver = {
+ .name = USB3503_I2C_NAME,
+ .pm = pm_ptr(&usb3503_i2c_pm_ops),
+ .of_match_table = of_match_ptr(usb3503_of_match),
+ },
+ .probe = usb3503_i2c_probe,
+ .remove = usb3503_i2c_remove,
+ .id_table = usb3503_id,
+};
+
+static struct platform_driver usb3503_platform_driver = {
+ .driver = {
+ .name = USB3503_I2C_NAME,
+ .of_match_table = of_match_ptr(usb3503_of_match),
+ .pm = pm_ptr(&usb3503_platform_pm_ops),
+ },
+ .probe = usb3503_platform_probe,
+ .remove = usb3503_platform_remove,
+};
+
+static int __init usb3503_init(void)
+{
+ int err;
+
+ err = i2c_add_driver(&usb3503_i2c_driver);
+ if (err) {
+ pr_err("usb3503: Failed to register I2C driver: %d\n", err);
+ return err;
+ }
+
+ err = platform_driver_register(&usb3503_platform_driver);
+ if (err) {
+ pr_err("usb3503: Failed to register platform driver: %d\n",
+ err);
+ i2c_del_driver(&usb3503_i2c_driver);
+ return err;
+ }
+
+ return 0;
+}
+module_init(usb3503_init);
+
+static void __exit usb3503_exit(void)
+{
+ platform_driver_unregister(&usb3503_platform_driver);
+ i2c_del_driver(&usb3503_i2c_driver);
+}
+module_exit(usb3503_exit);
+
+MODULE_AUTHOR("Dongjin Kim <tobetter@gmail.com>");
+MODULE_DESCRIPTION("USB3503 USB HUB driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/misc/usb4604.c b/drivers/usb/misc/usb4604.c
new file mode 100644
index 000000000..2142af9bb
--- /dev/null
+++ b/drivers/usb/misc/usb4604.c
@@ -0,0 +1,164 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Driver for SMSC USB4604 USB HSIC 4-port 2.0 hub controller driver
+ * Based on usb3503 driver
+ *
+ * Copyright (c) 2012-2013 Dongjin Kim (tobetter@gmail.com)
+ * Copyright (c) 2016 Linaro Ltd.
+ */
+
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/gpio/consumer.h>
+
+enum usb4604_mode {
+ USB4604_MODE_UNKNOWN,
+ USB4604_MODE_HUB,
+ USB4604_MODE_STANDBY,
+};
+
+struct usb4604 {
+ enum usb4604_mode mode;
+ struct device *dev;
+ struct gpio_desc *gpio_reset;
+};
+
+static void usb4604_reset(struct usb4604 *hub, int state)
+{
+ gpiod_set_value_cansleep(hub->gpio_reset, state);
+
+ /* Wait for i2c logic to come up */
+ if (state)
+ msleep(250);
+}
+
+static int usb4604_connect(struct usb4604 *hub)
+{
+ struct device *dev = hub->dev;
+ struct i2c_client *client = to_i2c_client(dev);
+ int err;
+ u8 connect_cmd[] = { 0xaa, 0x55, 0x00 };
+
+ usb4604_reset(hub, 1);
+
+ err = i2c_master_send(client, connect_cmd, ARRAY_SIZE(connect_cmd));
+ if (err < 0) {
+ usb4604_reset(hub, 0);
+ return err;
+ }
+
+ hub->mode = USB4604_MODE_HUB;
+ dev_dbg(dev, "switched to HUB mode\n");
+
+ return 0;
+}
+
+static int usb4604_switch_mode(struct usb4604 *hub, enum usb4604_mode mode)
+{
+ struct device *dev = hub->dev;
+ int err = 0;
+
+ switch (mode) {
+ case USB4604_MODE_HUB:
+ err = usb4604_connect(hub);
+ break;
+
+ case USB4604_MODE_STANDBY:
+ usb4604_reset(hub, 0);
+ dev_dbg(dev, "switched to STANDBY mode\n");
+ break;
+
+ default:
+ dev_err(dev, "unknown mode is requested\n");
+ err = -EINVAL;
+ break;
+ }
+
+ return err;
+}
+
+static int usb4604_probe(struct usb4604 *hub)
+{
+ struct device *dev = hub->dev;
+ struct device_node *np = dev->of_node;
+ struct gpio_desc *gpio;
+ u32 mode = USB4604_MODE_HUB;
+
+ gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW);
+ if (IS_ERR(gpio))
+ return PTR_ERR(gpio);
+ hub->gpio_reset = gpio;
+
+ if (of_property_read_u32(np, "initial-mode", &hub->mode))
+ hub->mode = mode;
+
+ return usb4604_switch_mode(hub, hub->mode);
+}
+
+static int usb4604_i2c_probe(struct i2c_client *i2c,
+ const struct i2c_device_id *id)
+{
+ struct usb4604 *hub;
+
+ hub = devm_kzalloc(&i2c->dev, sizeof(*hub), GFP_KERNEL);
+ if (!hub)
+ return -ENOMEM;
+
+ i2c_set_clientdata(i2c, hub);
+ hub->dev = &i2c->dev;
+
+ return usb4604_probe(hub);
+}
+
+static int __maybe_unused usb4604_i2c_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct usb4604 *hub = i2c_get_clientdata(client);
+
+ usb4604_switch_mode(hub, USB4604_MODE_STANDBY);
+
+ return 0;
+}
+
+static int __maybe_unused usb4604_i2c_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct usb4604 *hub = i2c_get_clientdata(client);
+
+ usb4604_switch_mode(hub, hub->mode);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(usb4604_i2c_pm_ops, usb4604_i2c_suspend,
+ usb4604_i2c_resume);
+
+static const struct i2c_device_id usb4604_id[] = {
+ { "usb4604", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, usb4604_id);
+
+#ifdef CONFIG_OF
+static const struct of_device_id usb4604_of_match[] = {
+ { .compatible = "smsc,usb4604" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, usb4604_of_match);
+#endif
+
+static struct i2c_driver usb4604_i2c_driver = {
+ .driver = {
+ .name = "usb4604",
+ .pm = pm_ptr(&usb4604_i2c_pm_ops),
+ .of_match_table = of_match_ptr(usb4604_of_match),
+ },
+ .probe = usb4604_i2c_probe,
+ .id_table = usb4604_id,
+};
+module_i2c_driver(usb4604_i2c_driver);
+
+MODULE_DESCRIPTION("USB4604 USB HUB driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/usb/misc/usb_u132.h b/drivers/usb/misc/usb_u132.h
new file mode 100644
index 000000000..1584efbbd
--- /dev/null
+++ b/drivers/usb/misc/usb_u132.h
@@ -0,0 +1,97 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+* Common Header File for the Elan Digital Systems U132 adapter
+* this file should be included by both the "ftdi-u132" and
+* the "u132-hcd" modules.
+*
+* Copyright(C) 2006 Elan Digital Systems Limited
+*(http://www.elandigitalsystems.com)
+*
+* Author and Maintainer - Tony Olech - Elan Digital Systems
+*(tony.olech@elandigitalsystems.com)
+*
+* The 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.
+*
+* The driver consists of two modules, the "ftdi-u132" module is
+* a USB client driver that interfaces to the FTDI chip within
+* the U132 adapter manufactured by Elan Digital Systems, and the
+* "u132-hcd" module is a USB host controller driver that talks
+* to the OHCI controller within CardBus card that are inserted
+* in the U132 adapter.
+*
+* The "ftdi-u132" module should be loaded automatically by the
+* hot plug system when the U132 adapter is plugged in. The module
+* initialises the adapter which mostly consists of synchronising
+* the FTDI chip, before continuously polling the adapter to detect
+* PC card insertions. As soon as a PC card containing a recognised
+* OHCI controller is seen the "ftdi-u132" module explicitly requests
+* the kernel to load the "u132-hcd" module.
+*
+* The "ftdi-u132" module provides the interface to the inserted
+* PC card and the "u132-hcd" module uses the API to send and receive
+* data. The API features call-backs, so that part of the "u132-hcd"
+* module code will run in the context of one of the kernel threads
+* of the "ftdi-u132" module.
+*
+*/
+int ftdi_elan_switch_on_diagnostics(int number);
+void ftdi_elan_gone_away(struct platform_device *pdev);
+void start_usb_lock_device_tracing(void);
+struct u132_platform_data {
+ u16 vendor;
+ u16 device;
+ u8 potpg;
+ void (*port_power) (struct device *dev, int is_on);
+ void (*reset) (struct device *dev);
+};
+int usb_ftdi_elan_edset_single(struct platform_device *pdev, u8 ed_number,
+ void *endp, struct urb *urb, u8 address, u8 ep_number, u8 toggle_bits,
+ 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));
+int usb_ftdi_elan_edset_output(struct platform_device *pdev, u8 ed_number,
+ void *endp, struct urb *urb, u8 address, u8 ep_number, u8 toggle_bits,
+ 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));
+int usb_ftdi_elan_edset_empty(struct platform_device *pdev, u8 ed_number,
+ void *endp, struct urb *urb, u8 address, u8 ep_number, u8 toggle_bits,
+ 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));
+int usb_ftdi_elan_edset_input(struct platform_device *pdev, u8 ed_number,
+ void *endp, struct urb *urb, u8 address, u8 ep_number, u8 toggle_bits,
+ 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));
+int usb_ftdi_elan_edset_setup(struct platform_device *pdev, u8 ed_number,
+ void *endp, struct urb *urb, u8 address, u8 ep_number, u8 toggle_bits,
+ 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));
+int usb_ftdi_elan_edset_flush(struct platform_device *pdev, u8 ed_number,
+ void *endp);
+int usb_ftdi_elan_read_pcimem(struct platform_device *pdev, int mem_offset,
+ u8 width, u32 *data);
+int usb_ftdi_elan_write_pcimem(struct platform_device *pdev, int mem_offset,
+ u8 width, u32 data);
diff --git a/drivers/usb/misc/usblcd.c b/drivers/usb/misc/usblcd.c
new file mode 100644
index 000000000..bb546f624
--- /dev/null
+++ b/drivers/usb/misc/usblcd.c
@@ -0,0 +1,450 @@
+// SPDX-License-Identifier: GPL-2.0
+/*****************************************************************************
+ * USBLCD Kernel Driver *
+ * Version 1.05 *
+ * (C) 2005 Georges Toth <g.toth@e-biz.lu> *
+ * *
+ * This file is licensed under the GPL. See COPYING in the package. *
+ * Based on usb-skeleton.c 2.0 by Greg Kroah-Hartman (greg@kroah.com) *
+ * *
+ * *
+ * 28.02.05 Complete rewrite of the original usblcd.c driver, *
+ * based on usb_skeleton.c. *
+ * This new driver allows more than one USB-LCD to be connected *
+ * and controlled, at once *
+ *****************************************************************************/
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/mutex.h>
+#include <linux/rwsem.h>
+#include <linux/uaccess.h>
+#include <linux/usb.h>
+
+#define DRIVER_VERSION "USBLCD Driver Version 1.05"
+
+#define USBLCD_MINOR 144
+
+#define IOCTL_GET_HARD_VERSION 1
+#define IOCTL_GET_DRV_VERSION 2
+
+
+static const struct usb_device_id id_table[] = {
+ { .idVendor = 0x10D2, .match_flags = USB_DEVICE_ID_MATCH_VENDOR, },
+ { },
+};
+MODULE_DEVICE_TABLE(usb, id_table);
+
+struct usb_lcd {
+ struct usb_device *udev; /* init: probe_lcd */
+ struct usb_interface *interface; /* the interface for
+ this device */
+ unsigned char *bulk_in_buffer; /* the buffer to receive
+ data */
+ size_t bulk_in_size; /* the size of the
+ receive buffer */
+ __u8 bulk_in_endpointAddr; /* the address of the
+ bulk in endpoint */
+ __u8 bulk_out_endpointAddr; /* the address of the
+ bulk out endpoint */
+ struct kref kref;
+ struct semaphore limit_sem; /* to stop writes at
+ full throttle from
+ using up all RAM */
+ struct usb_anchor submitted; /* URBs to wait for
+ before suspend */
+ struct rw_semaphore io_rwsem;
+ unsigned long disconnected:1;
+};
+#define to_lcd_dev(d) container_of(d, struct usb_lcd, kref)
+
+#define USB_LCD_CONCURRENT_WRITES 5
+
+static struct usb_driver lcd_driver;
+
+
+static void lcd_delete(struct kref *kref)
+{
+ struct usb_lcd *dev = to_lcd_dev(kref);
+
+ usb_put_dev(dev->udev);
+ kfree(dev->bulk_in_buffer);
+ kfree(dev);
+}
+
+
+static int lcd_open(struct inode *inode, struct file *file)
+{
+ struct usb_lcd *dev;
+ struct usb_interface *interface;
+ int subminor, r;
+
+ subminor = iminor(inode);
+
+ interface = usb_find_interface(&lcd_driver, subminor);
+ if (!interface) {
+ pr_err("USBLCD: %s - error, can't find device for minor %d\n",
+ __func__, subminor);
+ return -ENODEV;
+ }
+
+ dev = usb_get_intfdata(interface);
+
+ /* increment our usage count for the device */
+ kref_get(&dev->kref);
+
+ /* grab a power reference */
+ r = usb_autopm_get_interface(interface);
+ if (r < 0) {
+ kref_put(&dev->kref, lcd_delete);
+ return r;
+ }
+
+ /* save our object in the file's private structure */
+ file->private_data = dev;
+
+ return 0;
+}
+
+static int lcd_release(struct inode *inode, struct file *file)
+{
+ struct usb_lcd *dev;
+
+ dev = file->private_data;
+ if (dev == NULL)
+ return -ENODEV;
+
+ /* decrement the count on our device */
+ usb_autopm_put_interface(dev->interface);
+ kref_put(&dev->kref, lcd_delete);
+ return 0;
+}
+
+static ssize_t lcd_read(struct file *file, char __user * buffer,
+ size_t count, loff_t *ppos)
+{
+ struct usb_lcd *dev;
+ int retval = 0;
+ int bytes_read;
+
+ dev = file->private_data;
+
+ down_read(&dev->io_rwsem);
+
+ if (dev->disconnected) {
+ retval = -ENODEV;
+ goto out_up_io;
+ }
+
+ /* do a blocking bulk read to get data from the device */
+ retval = usb_bulk_msg(dev->udev,
+ usb_rcvbulkpipe(dev->udev,
+ dev->bulk_in_endpointAddr),
+ dev->bulk_in_buffer,
+ min(dev->bulk_in_size, count),
+ &bytes_read, 10000);
+
+ /* if the read was successful, copy the data to userspace */
+ if (!retval) {
+ if (copy_to_user(buffer, dev->bulk_in_buffer, bytes_read))
+ retval = -EFAULT;
+ else
+ retval = bytes_read;
+ }
+
+out_up_io:
+ up_read(&dev->io_rwsem);
+
+ return retval;
+}
+
+static long lcd_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ struct usb_lcd *dev;
+ u16 bcdDevice;
+ char buf[30];
+
+ dev = file->private_data;
+ if (dev == NULL)
+ return -ENODEV;
+
+ switch (cmd) {
+ case IOCTL_GET_HARD_VERSION:
+ bcdDevice = le16_to_cpu((dev->udev)->descriptor.bcdDevice);
+ sprintf(buf, "%1d%1d.%1d%1d",
+ (bcdDevice & 0xF000)>>12,
+ (bcdDevice & 0xF00)>>8,
+ (bcdDevice & 0xF0)>>4,
+ (bcdDevice & 0xF));
+ if (copy_to_user((void __user *)arg, buf, strlen(buf)) != 0)
+ return -EFAULT;
+ break;
+ case IOCTL_GET_DRV_VERSION:
+ sprintf(buf, DRIVER_VERSION);
+ if (copy_to_user((void __user *)arg, buf, strlen(buf)) != 0)
+ return -EFAULT;
+ break;
+ default:
+ return -ENOTTY;
+ }
+
+ return 0;
+}
+
+static void lcd_write_bulk_callback(struct urb *urb)
+{
+ struct usb_lcd *dev;
+ int status = urb->status;
+
+ dev = urb->context;
+
+ /* sync/async unlink faults aren't errors */
+ if (status &&
+ !(status == -ENOENT ||
+ status == -ECONNRESET ||
+ status == -ESHUTDOWN)) {
+ dev_dbg(&dev->interface->dev,
+ "nonzero write bulk status received: %d\n", status);
+ }
+
+ /* free up our allocated buffer */
+ usb_free_coherent(urb->dev, urb->transfer_buffer_length,
+ urb->transfer_buffer, urb->transfer_dma);
+ up(&dev->limit_sem);
+}
+
+static ssize_t lcd_write(struct file *file, const char __user * user_buffer,
+ size_t count, loff_t *ppos)
+{
+ struct usb_lcd *dev;
+ int retval = 0, r;
+ struct urb *urb = NULL;
+ char *buf = NULL;
+
+ dev = file->private_data;
+
+ /* verify that we actually have some data to write */
+ if (count == 0)
+ goto exit;
+
+ r = down_interruptible(&dev->limit_sem);
+ if (r < 0)
+ return -EINTR;
+
+ down_read(&dev->io_rwsem);
+
+ if (dev->disconnected) {
+ retval = -ENODEV;
+ goto err_up_io;
+ }
+
+ /* create a urb, and a buffer for it, and copy the data to the urb */
+ urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!urb) {
+ retval = -ENOMEM;
+ goto err_up_io;
+ }
+
+ buf = usb_alloc_coherent(dev->udev, count, GFP_KERNEL,
+ &urb->transfer_dma);
+ if (!buf) {
+ retval = -ENOMEM;
+ goto error;
+ }
+
+ if (copy_from_user(buf, user_buffer, count)) {
+ retval = -EFAULT;
+ goto error;
+ }
+
+ /* initialize the urb properly */
+ usb_fill_bulk_urb(urb, dev->udev,
+ usb_sndbulkpipe(dev->udev,
+ dev->bulk_out_endpointAddr),
+ buf, count, lcd_write_bulk_callback, dev);
+ urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+
+ usb_anchor_urb(urb, &dev->submitted);
+
+ /* send the data out the bulk port */
+ retval = usb_submit_urb(urb, GFP_KERNEL);
+ if (retval) {
+ dev_err(&dev->udev->dev,
+ "%s - failed submitting write urb, error %d\n",
+ __func__, retval);
+ goto error_unanchor;
+ }
+
+ /* release our reference to this urb,
+ the USB core will eventually free it entirely */
+ usb_free_urb(urb);
+
+ up_read(&dev->io_rwsem);
+exit:
+ return count;
+error_unanchor:
+ usb_unanchor_urb(urb);
+error:
+ usb_free_coherent(dev->udev, count, buf, urb->transfer_dma);
+ usb_free_urb(urb);
+err_up_io:
+ up_read(&dev->io_rwsem);
+ up(&dev->limit_sem);
+ return retval;
+}
+
+static const struct file_operations lcd_fops = {
+ .owner = THIS_MODULE,
+ .read = lcd_read,
+ .write = lcd_write,
+ .open = lcd_open,
+ .unlocked_ioctl = lcd_ioctl,
+ .release = lcd_release,
+ .llseek = noop_llseek,
+};
+
+/*
+ * usb class driver info in order to get a minor number from the usb core,
+ * and to have the device registered with the driver core
+ */
+static struct usb_class_driver lcd_class = {
+ .name = "lcd%d",
+ .fops = &lcd_fops,
+ .minor_base = USBLCD_MINOR,
+};
+
+static int lcd_probe(struct usb_interface *interface,
+ const struct usb_device_id *id)
+{
+ struct usb_lcd *dev = NULL;
+ struct usb_endpoint_descriptor *bulk_in, *bulk_out;
+ int i;
+ int retval;
+
+ /* allocate memory for our device state and initialize it */
+ dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+ if (!dev)
+ return -ENOMEM;
+
+ kref_init(&dev->kref);
+ sema_init(&dev->limit_sem, USB_LCD_CONCURRENT_WRITES);
+ init_rwsem(&dev->io_rwsem);
+ init_usb_anchor(&dev->submitted);
+
+ dev->udev = usb_get_dev(interface_to_usbdev(interface));
+ dev->interface = interface;
+
+ if (le16_to_cpu(dev->udev->descriptor.idProduct) != 0x0001) {
+ dev_warn(&interface->dev, "USBLCD model not supported.\n");
+ retval = -ENODEV;
+ goto error;
+ }
+
+ /* set up the endpoint information */
+ /* use only the first bulk-in and bulk-out endpoints */
+ retval = usb_find_common_endpoints(interface->cur_altsetting,
+ &bulk_in, &bulk_out, NULL, NULL);
+ if (retval) {
+ dev_err(&interface->dev,
+ "Could not find both bulk-in and bulk-out endpoints\n");
+ goto error;
+ }
+
+ dev->bulk_in_size = usb_endpoint_maxp(bulk_in);
+ dev->bulk_in_endpointAddr = bulk_in->bEndpointAddress;
+ dev->bulk_in_buffer = kmalloc(dev->bulk_in_size, GFP_KERNEL);
+ if (!dev->bulk_in_buffer) {
+ retval = -ENOMEM;
+ goto error;
+ }
+
+ dev->bulk_out_endpointAddr = bulk_out->bEndpointAddress;
+
+ /* save our data pointer in this interface device */
+ usb_set_intfdata(interface, dev);
+
+ /* we can register the device now, as it is ready */
+ retval = usb_register_dev(interface, &lcd_class);
+ if (retval) {
+ /* something prevented us from registering this driver */
+ dev_err(&interface->dev,
+ "Not able to get a minor for this device.\n");
+ goto error;
+ }
+
+ i = le16_to_cpu(dev->udev->descriptor.bcdDevice);
+
+ dev_info(&interface->dev, "USBLCD Version %1d%1d.%1d%1d found "
+ "at address %d\n", (i & 0xF000)>>12, (i & 0xF00)>>8,
+ (i & 0xF0)>>4, (i & 0xF), dev->udev->devnum);
+
+ /* let the user know what node this device is now attached to */
+ dev_info(&interface->dev, "USB LCD device now attached to USBLCD-%d\n",
+ interface->minor);
+ return 0;
+
+error:
+ kref_put(&dev->kref, lcd_delete);
+ return retval;
+}
+
+static void lcd_draw_down(struct usb_lcd *dev)
+{
+ int time;
+
+ time = usb_wait_anchor_empty_timeout(&dev->submitted, 1000);
+ if (!time)
+ usb_kill_anchored_urbs(&dev->submitted);
+}
+
+static int lcd_suspend(struct usb_interface *intf, pm_message_t message)
+{
+ struct usb_lcd *dev = usb_get_intfdata(intf);
+
+ if (!dev)
+ return 0;
+ lcd_draw_down(dev);
+ return 0;
+}
+
+static int lcd_resume(struct usb_interface *intf)
+{
+ return 0;
+}
+
+static void lcd_disconnect(struct usb_interface *interface)
+{
+ struct usb_lcd *dev = usb_get_intfdata(interface);
+ int minor = interface->minor;
+
+ /* give back our minor */
+ usb_deregister_dev(interface, &lcd_class);
+
+ down_write(&dev->io_rwsem);
+ dev->disconnected = 1;
+ up_write(&dev->io_rwsem);
+
+ usb_kill_anchored_urbs(&dev->submitted);
+
+ /* decrement our usage count */
+ kref_put(&dev->kref, lcd_delete);
+
+ dev_info(&interface->dev, "USB LCD #%d now disconnected\n", minor);
+}
+
+static struct usb_driver lcd_driver = {
+ .name = "usblcd",
+ .probe = lcd_probe,
+ .disconnect = lcd_disconnect,
+ .suspend = lcd_suspend,
+ .resume = lcd_resume,
+ .id_table = id_table,
+ .supports_autosuspend = 1,
+};
+
+module_usb_driver(lcd_driver);
+
+MODULE_AUTHOR("Georges Toth <g.toth@e-biz.lu>");
+MODULE_DESCRIPTION(DRIVER_VERSION);
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/misc/usbsevseg.c b/drivers/usb/misc/usbsevseg.c
new file mode 100644
index 000000000..c3114d9bd
--- /dev/null
+++ b/drivers/usb/misc/usbsevseg.c
@@ -0,0 +1,396 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * USB 7 Segment Driver
+ *
+ * Copyright (C) 2008 Harrison Metzger <harrisonmetz@gmail.com>
+ * Based on usbled.c by Greg Kroah-Hartman (greg@kroah.com)
+ */
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/usb.h>
+
+
+#define DRIVER_AUTHOR "Harrison Metzger <harrisonmetz@gmail.com>"
+#define DRIVER_DESC "USB 7 Segment Driver"
+
+#define VENDOR_ID 0x0fc5
+#define PRODUCT_ID 0x1227
+#define MAXLEN 8
+
+/* table of devices that work with this driver */
+static const struct usb_device_id id_table[] = {
+ { USB_DEVICE(VENDOR_ID, PRODUCT_ID) },
+ { },
+};
+MODULE_DEVICE_TABLE(usb, id_table);
+
+/* the different text display modes the device is capable of */
+static const char *display_textmodes[] = {"raw", "hex", "ascii"};
+
+struct usb_sevsegdev {
+ struct usb_device *udev;
+ struct usb_interface *intf;
+
+ u8 powered;
+ u8 mode_msb;
+ u8 mode_lsb;
+ u8 decimals[MAXLEN];
+ u8 textmode;
+ u8 text[MAXLEN];
+ u16 textlength;
+
+ u8 shadow_power; /* for PM */
+ u8 has_interface_pm;
+};
+
+/* sysfs_streq can't replace this completely
+ * If the device was in hex mode, and the user wanted a 0,
+ * if str commands are used, we would assume the end of string
+ * so mem commands are used.
+ */
+static inline size_t my_memlen(const char *buf, size_t count)
+{
+ if (count > 0 && buf[count-1] == '\n')
+ return count - 1;
+ else
+ return count;
+}
+
+static void update_display_powered(struct usb_sevsegdev *mydev)
+{
+ int rc;
+
+ if (mydev->powered && !mydev->has_interface_pm) {
+ rc = usb_autopm_get_interface(mydev->intf);
+ if (rc < 0)
+ return;
+ mydev->has_interface_pm = 1;
+ }
+
+ if (mydev->shadow_power != 1)
+ return;
+
+ rc = usb_control_msg_send(mydev->udev, 0, 0x12, 0x48,
+ (80 * 0x100) + 10, /* (power mode) */
+ (0x00 * 0x100) + (mydev->powered ? 1 : 0),
+ NULL, 0, 2000, GFP_KERNEL);
+ if (rc < 0)
+ dev_dbg(&mydev->udev->dev, "power retval = %d\n", rc);
+
+ if (!mydev->powered && mydev->has_interface_pm) {
+ usb_autopm_put_interface(mydev->intf);
+ mydev->has_interface_pm = 0;
+ }
+}
+
+static void update_display_mode(struct usb_sevsegdev *mydev)
+{
+ int rc;
+
+ if(mydev->shadow_power != 1)
+ return;
+
+ rc = usb_control_msg_send(mydev->udev, 0, 0x12, 0x48,
+ (82 * 0x100) + 10, /* (set mode) */
+ (mydev->mode_msb * 0x100) + mydev->mode_lsb,
+ NULL, 0, 2000, GFP_NOIO);
+
+ if (rc < 0)
+ dev_dbg(&mydev->udev->dev, "mode retval = %d\n", rc);
+}
+
+static void update_display_visual(struct usb_sevsegdev *mydev, gfp_t mf)
+{
+ int rc;
+ int i;
+ unsigned char buffer[MAXLEN] = {0};
+ u8 decimals = 0;
+
+ if(mydev->shadow_power != 1)
+ return;
+
+ /* The device is right to left, where as you write left to right */
+ for (i = 0; i < mydev->textlength; i++)
+ buffer[i] = mydev->text[mydev->textlength-1-i];
+
+ rc = usb_control_msg_send(mydev->udev, 0, 0x12, 0x48,
+ (85 * 0x100) + 10, /* (write text) */
+ (0 * 0x100) + mydev->textmode, /* mode */
+ &buffer, mydev->textlength, 2000, mf);
+
+ if (rc < 0)
+ dev_dbg(&mydev->udev->dev, "write retval = %d\n", rc);
+
+ /* The device is right to left, where as you write left to right */
+ for (i = 0; i < sizeof(mydev->decimals); i++)
+ decimals |= mydev->decimals[i] << i;
+
+ rc = usb_control_msg_send(mydev->udev, 0, 0x12, 0x48,
+ (86 * 0x100) + 10, /* (set decimal) */
+ (0 * 0x100) + decimals, /* decimals */
+ NULL, 0, 2000, mf);
+
+ if (rc < 0)
+ dev_dbg(&mydev->udev->dev, "decimal retval = %d\n", rc);
+}
+
+#define MYDEV_ATTR_SIMPLE_UNSIGNED(name, update_fcn) \
+static ssize_t name##_show(struct device *dev, \
+ struct device_attribute *attr, char *buf) \
+{ \
+ struct usb_interface *intf = to_usb_interface(dev); \
+ struct usb_sevsegdev *mydev = usb_get_intfdata(intf); \
+ \
+ return sprintf(buf, "%u\n", mydev->name); \
+} \
+ \
+static ssize_t name##_store(struct device *dev, \
+ struct device_attribute *attr, const char *buf, size_t count) \
+{ \
+ struct usb_interface *intf = to_usb_interface(dev); \
+ struct usb_sevsegdev *mydev = usb_get_intfdata(intf); \
+ \
+ mydev->name = simple_strtoul(buf, NULL, 10); \
+ update_fcn(mydev); \
+ \
+ return count; \
+} \
+static DEVICE_ATTR_RW(name);
+
+static ssize_t text_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct usb_interface *intf = to_usb_interface(dev);
+ struct usb_sevsegdev *mydev = usb_get_intfdata(intf);
+
+ return sysfs_emit(buf, "%s\n", mydev->text);
+}
+
+static ssize_t text_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct usb_interface *intf = to_usb_interface(dev);
+ struct usb_sevsegdev *mydev = usb_get_intfdata(intf);
+ size_t end = my_memlen(buf, count);
+
+ if (end > sizeof(mydev->text))
+ return -EINVAL;
+
+ memset(mydev->text, 0, sizeof(mydev->text));
+ mydev->textlength = end;
+
+ if (end > 0)
+ memcpy(mydev->text, buf, end);
+
+ update_display_visual(mydev, GFP_KERNEL);
+ return count;
+}
+
+static DEVICE_ATTR_RW(text);
+
+static ssize_t decimals_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct usb_interface *intf = to_usb_interface(dev);
+ struct usb_sevsegdev *mydev = usb_get_intfdata(intf);
+ int i;
+ int pos;
+
+ for (i = 0; i < sizeof(mydev->decimals); i++) {
+ pos = sizeof(mydev->decimals) - 1 - i;
+ if (mydev->decimals[i] == 0)
+ buf[pos] = '0';
+ else if (mydev->decimals[i] == 1)
+ buf[pos] = '1';
+ else
+ buf[pos] = 'x';
+ }
+
+ buf[sizeof(mydev->decimals)] = '\n';
+ return sizeof(mydev->decimals) + 1;
+}
+
+static ssize_t decimals_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct usb_interface *intf = to_usb_interface(dev);
+ struct usb_sevsegdev *mydev = usb_get_intfdata(intf);
+ size_t end = my_memlen(buf, count);
+ int i;
+
+ if (end > sizeof(mydev->decimals))
+ return -EINVAL;
+
+ for (i = 0; i < end; i++)
+ if (buf[i] != '0' && buf[i] != '1')
+ return -EINVAL;
+
+ memset(mydev->decimals, 0, sizeof(mydev->decimals));
+ for (i = 0; i < end; i++)
+ if (buf[i] == '1')
+ mydev->decimals[end-1-i] = 1;
+
+ update_display_visual(mydev, GFP_KERNEL);
+
+ return count;
+}
+
+static DEVICE_ATTR_RW(decimals);
+
+static ssize_t textmode_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct usb_interface *intf = to_usb_interface(dev);
+ struct usb_sevsegdev *mydev = usb_get_intfdata(intf);
+ int i;
+
+ buf[0] = 0;
+
+ for (i = 0; i < ARRAY_SIZE(display_textmodes); i++) {
+ if (mydev->textmode == i) {
+ strcat(buf, " [");
+ strcat(buf, display_textmodes[i]);
+ strcat(buf, "] ");
+ } else {
+ strcat(buf, " ");
+ strcat(buf, display_textmodes[i]);
+ strcat(buf, " ");
+ }
+ }
+ strcat(buf, "\n");
+
+
+ return strlen(buf);
+}
+
+static ssize_t textmode_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct usb_interface *intf = to_usb_interface(dev);
+ struct usb_sevsegdev *mydev = usb_get_intfdata(intf);
+ int i;
+
+ i = sysfs_match_string(display_textmodes, buf);
+ if (i < 0)
+ return i;
+
+ mydev->textmode = i;
+ update_display_visual(mydev, GFP_KERNEL);
+ return count;
+}
+
+static DEVICE_ATTR_RW(textmode);
+
+
+MYDEV_ATTR_SIMPLE_UNSIGNED(powered, update_display_powered);
+MYDEV_ATTR_SIMPLE_UNSIGNED(mode_msb, update_display_mode);
+MYDEV_ATTR_SIMPLE_UNSIGNED(mode_lsb, update_display_mode);
+
+static struct attribute *sevseg_attrs[] = {
+ &dev_attr_powered.attr,
+ &dev_attr_text.attr,
+ &dev_attr_textmode.attr,
+ &dev_attr_decimals.attr,
+ &dev_attr_mode_msb.attr,
+ &dev_attr_mode_lsb.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(sevseg);
+
+static int sevseg_probe(struct usb_interface *interface,
+ const struct usb_device_id *id)
+{
+ struct usb_device *udev = interface_to_usbdev(interface);
+ struct usb_sevsegdev *mydev = NULL;
+ int rc = -ENOMEM;
+
+ mydev = kzalloc(sizeof(struct usb_sevsegdev), GFP_KERNEL);
+ if (!mydev)
+ goto error_mem;
+
+ mydev->udev = usb_get_dev(udev);
+ mydev->intf = interface;
+ usb_set_intfdata(interface, mydev);
+
+ /* PM */
+ mydev->shadow_power = 1; /* currently active */
+ mydev->has_interface_pm = 0; /* have not issued autopm_get */
+
+ /*set defaults */
+ mydev->textmode = 0x02; /* ascii mode */
+ mydev->mode_msb = 0x06; /* 6 characters */
+ mydev->mode_lsb = 0x3f; /* scanmode for 6 chars */
+
+ dev_info(&interface->dev, "USB 7 Segment device now attached\n");
+ return 0;
+
+error_mem:
+ return rc;
+}
+
+static void sevseg_disconnect(struct usb_interface *interface)
+{
+ struct usb_sevsegdev *mydev;
+
+ mydev = usb_get_intfdata(interface);
+ usb_set_intfdata(interface, NULL);
+ usb_put_dev(mydev->udev);
+ kfree(mydev);
+ dev_info(&interface->dev, "USB 7 Segment now disconnected\n");
+}
+
+static int sevseg_suspend(struct usb_interface *intf, pm_message_t message)
+{
+ struct usb_sevsegdev *mydev;
+
+ mydev = usb_get_intfdata(intf);
+ mydev->shadow_power = 0;
+
+ return 0;
+}
+
+static int sevseg_resume(struct usb_interface *intf)
+{
+ struct usb_sevsegdev *mydev;
+
+ mydev = usb_get_intfdata(intf);
+ mydev->shadow_power = 1;
+ update_display_mode(mydev);
+ update_display_visual(mydev, GFP_NOIO);
+
+ return 0;
+}
+
+static int sevseg_reset_resume(struct usb_interface *intf)
+{
+ struct usb_sevsegdev *mydev;
+
+ mydev = usb_get_intfdata(intf);
+ mydev->shadow_power = 1;
+ update_display_mode(mydev);
+ update_display_visual(mydev, GFP_NOIO);
+
+ return 0;
+}
+
+static struct usb_driver sevseg_driver = {
+ .name = "usbsevseg",
+ .probe = sevseg_probe,
+ .disconnect = sevseg_disconnect,
+ .suspend = sevseg_suspend,
+ .resume = sevseg_resume,
+ .reset_resume = sevseg_reset_resume,
+ .id_table = id_table,
+ .dev_groups = sevseg_groups,
+ .supports_autosuspend = 1,
+};
+
+module_usb_driver(sevseg_driver);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/misc/usbtest.c b/drivers/usb/misc/usbtest.c
new file mode 100644
index 000000000..ac0d75ac2
--- /dev/null
+++ b/drivers/usb/misc/usbtest.c
@@ -0,0 +1,3078 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/scatterlist.h>
+#include <linux/mutex.h>
+#include <linux/timer.h>
+#include <linux/usb.h>
+
+#define SIMPLE_IO_TIMEOUT 10000 /* in milliseconds */
+
+/*-------------------------------------------------------------------------*/
+
+static int override_alt = -1;
+module_param_named(alt, override_alt, int, 0644);
+MODULE_PARM_DESC(alt, ">= 0 to override altsetting selection");
+static void complicated_callback(struct urb *urb);
+
+/*-------------------------------------------------------------------------*/
+
+/* FIXME make these public somewhere; usbdevfs.h? */
+
+/* Parameter for usbtest driver. */
+struct usbtest_param_32 {
+ /* inputs */
+ __u32 test_num; /* 0..(TEST_CASES-1) */
+ __u32 iterations;
+ __u32 length;
+ __u32 vary;
+ __u32 sglen;
+
+ /* outputs */
+ __s32 duration_sec;
+ __s32 duration_usec;
+};
+
+/*
+ * Compat parameter to the usbtest driver.
+ * This supports older user space binaries compiled with 64 bit compiler.
+ */
+struct usbtest_param_64 {
+ /* inputs */
+ __u32 test_num; /* 0..(TEST_CASES-1) */
+ __u32 iterations;
+ __u32 length;
+ __u32 vary;
+ __u32 sglen;
+
+ /* outputs */
+ __s64 duration_sec;
+ __s64 duration_usec;
+};
+
+/* IOCTL interface to the driver. */
+#define USBTEST_REQUEST_32 _IOWR('U', 100, struct usbtest_param_32)
+/* COMPAT IOCTL interface to the driver. */
+#define USBTEST_REQUEST_64 _IOWR('U', 100, struct usbtest_param_64)
+
+/*-------------------------------------------------------------------------*/
+
+#define GENERIC /* let probe() bind using module params */
+
+/* Some devices that can be used for testing will have "real" drivers.
+ * Entries for those need to be enabled here by hand, after disabling
+ * that "real" driver.
+ */
+//#define IBOT2 /* grab iBOT2 webcams */
+//#define KEYSPAN_19Qi /* grab un-renumerated serial adapter */
+
+/*-------------------------------------------------------------------------*/
+
+struct usbtest_info {
+ const char *name;
+ u8 ep_in; /* bulk/intr source */
+ u8 ep_out; /* bulk/intr sink */
+ unsigned autoconf:1;
+ unsigned ctrl_out:1;
+ unsigned iso:1; /* try iso in/out */
+ unsigned intr:1; /* try interrupt in/out */
+ int alt;
+};
+
+/* this is accessed only through usbfs ioctl calls.
+ * one ioctl to issue a test ... one lock per device.
+ * tests create other threads if they need them.
+ * urbs and buffers are allocated dynamically,
+ * and data generated deterministically.
+ */
+struct usbtest_dev {
+ struct usb_interface *intf;
+ struct usbtest_info *info;
+ int in_pipe;
+ int out_pipe;
+ int in_iso_pipe;
+ int out_iso_pipe;
+ int in_int_pipe;
+ int out_int_pipe;
+ struct usb_endpoint_descriptor *iso_in, *iso_out;
+ struct usb_endpoint_descriptor *int_in, *int_out;
+ struct mutex lock;
+
+#define TBUF_SIZE 256
+ u8 *buf;
+};
+
+static struct usb_device *testdev_to_usbdev(struct usbtest_dev *test)
+{
+ return interface_to_usbdev(test->intf);
+}
+
+/* set up all urbs so they can be used with either bulk or interrupt */
+#define INTERRUPT_RATE 1 /* msec/transfer */
+
+#define ERROR(tdev, fmt, args...) \
+ dev_err(&(tdev)->intf->dev , fmt , ## args)
+#define WARNING(tdev, fmt, args...) \
+ dev_warn(&(tdev)->intf->dev , fmt , ## args)
+
+#define GUARD_BYTE 0xA5
+#define MAX_SGLEN 128
+
+/*-------------------------------------------------------------------------*/
+
+static inline void endpoint_update(int edi,
+ struct usb_host_endpoint **in,
+ struct usb_host_endpoint **out,
+ struct usb_host_endpoint *e)
+{
+ if (edi) {
+ if (!*in)
+ *in = e;
+ } else {
+ if (!*out)
+ *out = e;
+ }
+}
+
+static int
+get_endpoints(struct usbtest_dev *dev, struct usb_interface *intf)
+{
+ int tmp;
+ struct usb_host_interface *alt;
+ struct usb_host_endpoint *in, *out;
+ struct usb_host_endpoint *iso_in, *iso_out;
+ struct usb_host_endpoint *int_in, *int_out;
+ struct usb_device *udev;
+
+ for (tmp = 0; tmp < intf->num_altsetting; tmp++) {
+ unsigned ep;
+
+ in = out = NULL;
+ iso_in = iso_out = NULL;
+ int_in = int_out = NULL;
+ alt = intf->altsetting + tmp;
+
+ if (override_alt >= 0 &&
+ override_alt != alt->desc.bAlternateSetting)
+ continue;
+
+ /* take the first altsetting with in-bulk + out-bulk;
+ * ignore other endpoints and altsettings.
+ */
+ for (ep = 0; ep < alt->desc.bNumEndpoints; ep++) {
+ struct usb_host_endpoint *e;
+ int edi;
+
+ e = alt->endpoint + ep;
+ edi = usb_endpoint_dir_in(&e->desc);
+
+ switch (usb_endpoint_type(&e->desc)) {
+ case USB_ENDPOINT_XFER_BULK:
+ endpoint_update(edi, &in, &out, e);
+ continue;
+ case USB_ENDPOINT_XFER_INT:
+ if (dev->info->intr)
+ endpoint_update(edi, &int_in, &int_out, e);
+ continue;
+ case USB_ENDPOINT_XFER_ISOC:
+ if (dev->info->iso)
+ endpoint_update(edi, &iso_in, &iso_out, e);
+ fallthrough;
+ default:
+ continue;
+ }
+ }
+ if ((in && out) || iso_in || iso_out || int_in || int_out)
+ goto found;
+ }
+ return -EINVAL;
+
+found:
+ udev = testdev_to_usbdev(dev);
+ dev->info->alt = alt->desc.bAlternateSetting;
+ if (alt->desc.bAlternateSetting != 0) {
+ tmp = usb_set_interface(udev,
+ alt->desc.bInterfaceNumber,
+ alt->desc.bAlternateSetting);
+ if (tmp < 0)
+ return tmp;
+ }
+
+ if (in)
+ dev->in_pipe = usb_rcvbulkpipe(udev,
+ in->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK);
+ if (out)
+ dev->out_pipe = usb_sndbulkpipe(udev,
+ out->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK);
+
+ if (iso_in) {
+ dev->iso_in = &iso_in->desc;
+ dev->in_iso_pipe = usb_rcvisocpipe(udev,
+ iso_in->desc.bEndpointAddress
+ & USB_ENDPOINT_NUMBER_MASK);
+ }
+
+ if (iso_out) {
+ dev->iso_out = &iso_out->desc;
+ dev->out_iso_pipe = usb_sndisocpipe(udev,
+ iso_out->desc.bEndpointAddress
+ & USB_ENDPOINT_NUMBER_MASK);
+ }
+
+ if (int_in) {
+ dev->int_in = &int_in->desc;
+ dev->in_int_pipe = usb_rcvintpipe(udev,
+ int_in->desc.bEndpointAddress
+ & USB_ENDPOINT_NUMBER_MASK);
+ }
+
+ if (int_out) {
+ dev->int_out = &int_out->desc;
+ dev->out_int_pipe = usb_sndintpipe(udev,
+ int_out->desc.bEndpointAddress
+ & USB_ENDPOINT_NUMBER_MASK);
+ }
+ return 0;
+}
+
+/*-------------------------------------------------------------------------*/
+
+/* Support for testing basic non-queued I/O streams.
+ *
+ * These just package urbs as requests that can be easily canceled.
+ * Each urb's data buffer is dynamically allocated; callers can fill
+ * them with non-zero test data (or test for it) when appropriate.
+ */
+
+static void simple_callback(struct urb *urb)
+{
+ complete(urb->context);
+}
+
+static struct urb *usbtest_alloc_urb(
+ struct usb_device *udev,
+ int pipe,
+ unsigned long bytes,
+ unsigned transfer_flags,
+ unsigned offset,
+ u8 bInterval,
+ usb_complete_t complete_fn)
+{
+ struct urb *urb;
+
+ urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!urb)
+ return urb;
+
+ if (bInterval)
+ usb_fill_int_urb(urb, udev, pipe, NULL, bytes, complete_fn,
+ NULL, bInterval);
+ else
+ usb_fill_bulk_urb(urb, udev, pipe, NULL, bytes, complete_fn,
+ NULL);
+
+ urb->interval = (udev->speed == USB_SPEED_HIGH)
+ ? (INTERRUPT_RATE << 3)
+ : INTERRUPT_RATE;
+ urb->transfer_flags = transfer_flags;
+ if (usb_pipein(pipe))
+ urb->transfer_flags |= URB_SHORT_NOT_OK;
+
+ if ((bytes + offset) == 0)
+ return urb;
+
+ if (urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP)
+ urb->transfer_buffer = usb_alloc_coherent(udev, bytes + offset,
+ GFP_KERNEL, &urb->transfer_dma);
+ else
+ urb->transfer_buffer = kmalloc(bytes + offset, GFP_KERNEL);
+
+ if (!urb->transfer_buffer) {
+ usb_free_urb(urb);
+ return NULL;
+ }
+
+ /* To test unaligned transfers add an offset and fill the
+ unused memory with a guard value */
+ if (offset) {
+ memset(urb->transfer_buffer, GUARD_BYTE, offset);
+ urb->transfer_buffer += offset;
+ if (urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP)
+ urb->transfer_dma += offset;
+ }
+
+ /* For inbound transfers use guard byte so that test fails if
+ data not correctly copied */
+ memset(urb->transfer_buffer,
+ usb_pipein(urb->pipe) ? GUARD_BYTE : 0,
+ bytes);
+ return urb;
+}
+
+static struct urb *simple_alloc_urb(
+ struct usb_device *udev,
+ int pipe,
+ unsigned long bytes,
+ u8 bInterval)
+{
+ return usbtest_alloc_urb(udev, pipe, bytes, URB_NO_TRANSFER_DMA_MAP, 0,
+ bInterval, simple_callback);
+}
+
+static struct urb *complicated_alloc_urb(
+ struct usb_device *udev,
+ int pipe,
+ unsigned long bytes,
+ u8 bInterval)
+{
+ return usbtest_alloc_urb(udev, pipe, bytes, URB_NO_TRANSFER_DMA_MAP, 0,
+ bInterval, complicated_callback);
+}
+
+static unsigned pattern;
+static unsigned mod_pattern;
+module_param_named(pattern, mod_pattern, uint, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(mod_pattern, "i/o pattern (0 == zeroes)");
+
+static unsigned get_maxpacket(struct usb_device *udev, int pipe)
+{
+ struct usb_host_endpoint *ep;
+
+ ep = usb_pipe_endpoint(udev, pipe);
+ return le16_to_cpup(&ep->desc.wMaxPacketSize);
+}
+
+static int ss_isoc_get_packet_num(struct usb_device *udev, int pipe)
+{
+ struct usb_host_endpoint *ep = usb_pipe_endpoint(udev, pipe);
+
+ return USB_SS_MULT(ep->ss_ep_comp.bmAttributes)
+ * (1 + ep->ss_ep_comp.bMaxBurst);
+}
+
+static void simple_fill_buf(struct urb *urb)
+{
+ unsigned i;
+ u8 *buf = urb->transfer_buffer;
+ unsigned len = urb->transfer_buffer_length;
+ unsigned maxpacket;
+
+ switch (pattern) {
+ default:
+ fallthrough;
+ case 0:
+ memset(buf, 0, len);
+ break;
+ case 1: /* mod63 */
+ maxpacket = get_maxpacket(urb->dev, urb->pipe);
+ for (i = 0; i < len; i++)
+ *buf++ = (u8) ((i % maxpacket) % 63);
+ break;
+ }
+}
+
+static inline unsigned long buffer_offset(void *buf)
+{
+ return (unsigned long)buf & (ARCH_KMALLOC_MINALIGN - 1);
+}
+
+static int check_guard_bytes(struct usbtest_dev *tdev, struct urb *urb)
+{
+ u8 *buf = urb->transfer_buffer;
+ u8 *guard = buf - buffer_offset(buf);
+ unsigned i;
+
+ for (i = 0; guard < buf; i++, guard++) {
+ if (*guard != GUARD_BYTE) {
+ ERROR(tdev, "guard byte[%d] %d (not %d)\n",
+ i, *guard, GUARD_BYTE);
+ return -EINVAL;
+ }
+ }
+ return 0;
+}
+
+static int simple_check_buf(struct usbtest_dev *tdev, struct urb *urb)
+{
+ unsigned i;
+ u8 expected;
+ u8 *buf = urb->transfer_buffer;
+ unsigned len = urb->actual_length;
+ unsigned maxpacket = get_maxpacket(urb->dev, urb->pipe);
+
+ int ret = check_guard_bytes(tdev, urb);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < len; i++, buf++) {
+ switch (pattern) {
+ /* all-zeroes has no synchronization issues */
+ case 0:
+ expected = 0;
+ break;
+ /* mod63 stays in sync with short-terminated transfers,
+ * or otherwise when host and gadget agree on how large
+ * each usb transfer request should be. resync is done
+ * with set_interface or set_config.
+ */
+ case 1: /* mod63 */
+ expected = (i % maxpacket) % 63;
+ break;
+ /* always fail unsupported patterns */
+ default:
+ expected = !*buf;
+ break;
+ }
+ if (*buf == expected)
+ continue;
+ ERROR(tdev, "buf[%d] = %d (not %d)\n", i, *buf, expected);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static void simple_free_urb(struct urb *urb)
+{
+ unsigned long offset = buffer_offset(urb->transfer_buffer);
+
+ if (urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP)
+ usb_free_coherent(
+ urb->dev,
+ urb->transfer_buffer_length + offset,
+ urb->transfer_buffer - offset,
+ urb->transfer_dma - offset);
+ else
+ kfree(urb->transfer_buffer - offset);
+ usb_free_urb(urb);
+}
+
+static int simple_io(
+ struct usbtest_dev *tdev,
+ struct urb *urb,
+ int iterations,
+ int vary,
+ int expected,
+ const char *label
+)
+{
+ struct usb_device *udev = urb->dev;
+ int max = urb->transfer_buffer_length;
+ struct completion completion;
+ int retval = 0;
+ unsigned long expire;
+
+ urb->context = &completion;
+ while (retval == 0 && iterations-- > 0) {
+ init_completion(&completion);
+ if (usb_pipeout(urb->pipe)) {
+ simple_fill_buf(urb);
+ urb->transfer_flags |= URB_ZERO_PACKET;
+ }
+ retval = usb_submit_urb(urb, GFP_KERNEL);
+ if (retval != 0)
+ break;
+
+ expire = msecs_to_jiffies(SIMPLE_IO_TIMEOUT);
+ if (!wait_for_completion_timeout(&completion, expire)) {
+ usb_kill_urb(urb);
+ retval = (urb->status == -ENOENT ?
+ -ETIMEDOUT : urb->status);
+ } else {
+ retval = urb->status;
+ }
+
+ urb->dev = udev;
+ if (retval == 0 && usb_pipein(urb->pipe))
+ retval = simple_check_buf(tdev, urb);
+
+ if (vary) {
+ int len = urb->transfer_buffer_length;
+
+ len += vary;
+ len %= max;
+ if (len == 0)
+ len = (vary < max) ? vary : max;
+ urb->transfer_buffer_length = len;
+ }
+
+ /* FIXME if endpoint halted, clear halt (and log) */
+ }
+ urb->transfer_buffer_length = max;
+
+ if (expected != retval)
+ dev_err(&udev->dev,
+ "%s failed, iterations left %d, status %d (not %d)\n",
+ label, iterations, retval, expected);
+ return retval;
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+/* We use scatterlist primitives to test queued I/O.
+ * Yes, this also tests the scatterlist primitives.
+ */
+
+static void free_sglist(struct scatterlist *sg, int nents)
+{
+ unsigned i;
+
+ if (!sg)
+ return;
+ for (i = 0; i < nents; i++) {
+ if (!sg_page(&sg[i]))
+ continue;
+ kfree(sg_virt(&sg[i]));
+ }
+ kfree(sg);
+}
+
+static struct scatterlist *
+alloc_sglist(int nents, int max, int vary, struct usbtest_dev *dev, int pipe)
+{
+ struct scatterlist *sg;
+ unsigned int n_size = 0;
+ unsigned i;
+ unsigned size = max;
+ unsigned maxpacket =
+ get_maxpacket(interface_to_usbdev(dev->intf), pipe);
+
+ if (max == 0)
+ return NULL;
+
+ sg = kmalloc_array(nents, sizeof(*sg), GFP_KERNEL);
+ if (!sg)
+ return NULL;
+ sg_init_table(sg, nents);
+
+ for (i = 0; i < nents; i++) {
+ char *buf;
+ unsigned j;
+
+ buf = kzalloc(size, GFP_KERNEL);
+ if (!buf) {
+ free_sglist(sg, i);
+ return NULL;
+ }
+
+ /* kmalloc pages are always physically contiguous! */
+ sg_set_buf(&sg[i], buf, size);
+
+ switch (pattern) {
+ case 0:
+ /* already zeroed */
+ break;
+ case 1:
+ for (j = 0; j < size; j++)
+ *buf++ = (u8) (((j + n_size) % maxpacket) % 63);
+ n_size += size;
+ break;
+ }
+
+ if (vary) {
+ size += vary;
+ size %= max;
+ if (size == 0)
+ size = (vary < max) ? vary : max;
+ }
+ }
+
+ return sg;
+}
+
+struct sg_timeout {
+ struct timer_list timer;
+ struct usb_sg_request *req;
+};
+
+static void sg_timeout(struct timer_list *t)
+{
+ struct sg_timeout *timeout = from_timer(timeout, t, timer);
+
+ usb_sg_cancel(timeout->req);
+}
+
+static int perform_sglist(
+ struct usbtest_dev *tdev,
+ unsigned iterations,
+ int pipe,
+ struct usb_sg_request *req,
+ struct scatterlist *sg,
+ int nents
+)
+{
+ struct usb_device *udev = testdev_to_usbdev(tdev);
+ int retval = 0;
+ struct sg_timeout timeout = {
+ .req = req,
+ };
+
+ timer_setup_on_stack(&timeout.timer, sg_timeout, 0);
+
+ while (retval == 0 && iterations-- > 0) {
+ retval = usb_sg_init(req, udev, pipe,
+ (udev->speed == USB_SPEED_HIGH)
+ ? (INTERRUPT_RATE << 3)
+ : INTERRUPT_RATE,
+ sg, nents, 0, GFP_KERNEL);
+
+ if (retval)
+ break;
+ mod_timer(&timeout.timer, jiffies +
+ msecs_to_jiffies(SIMPLE_IO_TIMEOUT));
+ usb_sg_wait(req);
+ if (!del_timer_sync(&timeout.timer))
+ retval = -ETIMEDOUT;
+ else
+ retval = req->status;
+ destroy_timer_on_stack(&timeout.timer);
+
+ /* FIXME check resulting data pattern */
+
+ /* FIXME if endpoint halted, clear halt (and log) */
+ }
+
+ /* FIXME for unlink or fault handling tests, don't report
+ * failure if retval is as we expected ...
+ */
+ if (retval)
+ ERROR(tdev, "perform_sglist failed, "
+ "iterations left %d, status %d\n",
+ iterations, retval);
+ return retval;
+}
+
+
+/*-------------------------------------------------------------------------*/
+
+/* unqueued control message testing
+ *
+ * there's a nice set of device functional requirements in chapter 9 of the
+ * usb 2.0 spec, which we can apply to ANY device, even ones that don't use
+ * special test firmware.
+ *
+ * we know the device is configured (or suspended) by the time it's visible
+ * through usbfs. we can't change that, so we won't test enumeration (which
+ * worked 'well enough' to get here, this time), power management (ditto),
+ * or remote wakeup (which needs human interaction).
+ */
+
+static unsigned realworld = 1;
+module_param(realworld, uint, 0);
+MODULE_PARM_DESC(realworld, "clear to demand stricter spec compliance");
+
+static int get_altsetting(struct usbtest_dev *dev)
+{
+ struct usb_interface *iface = dev->intf;
+ struct usb_device *udev = interface_to_usbdev(iface);
+ int retval;
+
+ retval = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0),
+ USB_REQ_GET_INTERFACE, USB_DIR_IN|USB_RECIP_INTERFACE,
+ 0, iface->altsetting[0].desc.bInterfaceNumber,
+ dev->buf, 1, USB_CTRL_GET_TIMEOUT);
+ switch (retval) {
+ case 1:
+ return dev->buf[0];
+ case 0:
+ retval = -ERANGE;
+ fallthrough;
+ default:
+ return retval;
+ }
+}
+
+static int set_altsetting(struct usbtest_dev *dev, int alternate)
+{
+ struct usb_interface *iface = dev->intf;
+ struct usb_device *udev;
+
+ if (alternate < 0 || alternate >= 256)
+ return -EINVAL;
+
+ udev = interface_to_usbdev(iface);
+ return usb_set_interface(udev,
+ iface->altsetting[0].desc.bInterfaceNumber,
+ alternate);
+}
+
+static int is_good_config(struct usbtest_dev *tdev, int len)
+{
+ struct usb_config_descriptor *config;
+
+ if (len < sizeof(*config))
+ return 0;
+ config = (struct usb_config_descriptor *) tdev->buf;
+
+ switch (config->bDescriptorType) {
+ case USB_DT_CONFIG:
+ case USB_DT_OTHER_SPEED_CONFIG:
+ if (config->bLength != 9) {
+ ERROR(tdev, "bogus config descriptor length\n");
+ return 0;
+ }
+ /* this bit 'must be 1' but often isn't */
+ if (!realworld && !(config->bmAttributes & 0x80)) {
+ ERROR(tdev, "high bit of config attributes not set\n");
+ return 0;
+ }
+ if (config->bmAttributes & 0x1f) { /* reserved == 0 */
+ ERROR(tdev, "reserved config bits set\n");
+ return 0;
+ }
+ break;
+ default:
+ return 0;
+ }
+
+ if (le16_to_cpu(config->wTotalLength) == len) /* read it all */
+ return 1;
+ if (le16_to_cpu(config->wTotalLength) >= TBUF_SIZE) /* max partial read */
+ return 1;
+ ERROR(tdev, "bogus config descriptor read size\n");
+ return 0;
+}
+
+static int is_good_ext(struct usbtest_dev *tdev, u8 *buf)
+{
+ struct usb_ext_cap_descriptor *ext;
+ u32 attr;
+
+ ext = (struct usb_ext_cap_descriptor *) buf;
+
+ if (ext->bLength != USB_DT_USB_EXT_CAP_SIZE) {
+ ERROR(tdev, "bogus usb 2.0 extension descriptor length\n");
+ return 0;
+ }
+
+ attr = le32_to_cpu(ext->bmAttributes);
+ /* bits[1:15] is used and others are reserved */
+ if (attr & ~0xfffe) { /* reserved == 0 */
+ ERROR(tdev, "reserved bits set\n");
+ return 0;
+ }
+
+ return 1;
+}
+
+static int is_good_ss_cap(struct usbtest_dev *tdev, u8 *buf)
+{
+ struct usb_ss_cap_descriptor *ss;
+
+ ss = (struct usb_ss_cap_descriptor *) buf;
+
+ if (ss->bLength != USB_DT_USB_SS_CAP_SIZE) {
+ ERROR(tdev, "bogus superspeed device capability descriptor length\n");
+ return 0;
+ }
+
+ /*
+ * only bit[1] of bmAttributes is used for LTM and others are
+ * reserved
+ */
+ if (ss->bmAttributes & ~0x02) { /* reserved == 0 */
+ ERROR(tdev, "reserved bits set in bmAttributes\n");
+ return 0;
+ }
+
+ /* bits[0:3] of wSpeedSupported is used and others are reserved */
+ if (le16_to_cpu(ss->wSpeedSupported) & ~0x0f) { /* reserved == 0 */
+ ERROR(tdev, "reserved bits set in wSpeedSupported\n");
+ return 0;
+ }
+
+ return 1;
+}
+
+static int is_good_con_id(struct usbtest_dev *tdev, u8 *buf)
+{
+ struct usb_ss_container_id_descriptor *con_id;
+
+ con_id = (struct usb_ss_container_id_descriptor *) buf;
+
+ if (con_id->bLength != USB_DT_USB_SS_CONTN_ID_SIZE) {
+ ERROR(tdev, "bogus container id descriptor length\n");
+ return 0;
+ }
+
+ if (con_id->bReserved) { /* reserved == 0 */
+ ERROR(tdev, "reserved bits set\n");
+ return 0;
+ }
+
+ return 1;
+}
+
+/* sanity test for standard requests working with usb_control_mesg() and some
+ * of the utility functions which use it.
+ *
+ * this doesn't test how endpoint halts behave or data toggles get set, since
+ * we won't do I/O to bulk/interrupt endpoints here (which is how to change
+ * halt or toggle). toggle testing is impractical without support from hcds.
+ *
+ * this avoids failing devices linux would normally work with, by not testing
+ * config/altsetting operations for devices that only support their defaults.
+ * such devices rarely support those needless operations.
+ *
+ * NOTE that since this is a sanity test, it's not examining boundary cases
+ * to see if usbcore, hcd, and device all behave right. such testing would
+ * involve varied read sizes and other operation sequences.
+ */
+static int ch9_postconfig(struct usbtest_dev *dev)
+{
+ struct usb_interface *iface = dev->intf;
+ struct usb_device *udev = interface_to_usbdev(iface);
+ int i, alt, retval;
+
+ /* [9.2.3] if there's more than one altsetting, we need to be able to
+ * set and get each one. mostly trusts the descriptors from usbcore.
+ */
+ for (i = 0; i < iface->num_altsetting; i++) {
+
+ /* 9.2.3 constrains the range here */
+ alt = iface->altsetting[i].desc.bAlternateSetting;
+ if (alt < 0 || alt >= iface->num_altsetting) {
+ dev_err(&iface->dev,
+ "invalid alt [%d].bAltSetting = %d\n",
+ i, alt);
+ }
+
+ /* [real world] get/set unimplemented if there's only one */
+ if (realworld && iface->num_altsetting == 1)
+ continue;
+
+ /* [9.4.10] set_interface */
+ retval = set_altsetting(dev, alt);
+ if (retval) {
+ dev_err(&iface->dev, "can't set_interface = %d, %d\n",
+ alt, retval);
+ return retval;
+ }
+
+ /* [9.4.4] get_interface always works */
+ retval = get_altsetting(dev);
+ if (retval != alt) {
+ dev_err(&iface->dev, "get alt should be %d, was %d\n",
+ alt, retval);
+ return (retval < 0) ? retval : -EDOM;
+ }
+
+ }
+
+ /* [real world] get_config unimplemented if there's only one */
+ if (!realworld || udev->descriptor.bNumConfigurations != 1) {
+ int expected = udev->actconfig->desc.bConfigurationValue;
+
+ /* [9.4.2] get_configuration always works
+ * ... although some cheap devices (like one TI Hub I've got)
+ * won't return config descriptors except before set_config.
+ */
+ retval = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0),
+ USB_REQ_GET_CONFIGURATION,
+ USB_DIR_IN | USB_RECIP_DEVICE,
+ 0, 0, dev->buf, 1, USB_CTRL_GET_TIMEOUT);
+ if (retval != 1 || dev->buf[0] != expected) {
+ dev_err(&iface->dev, "get config --> %d %d (1 %d)\n",
+ retval, dev->buf[0], expected);
+ return (retval < 0) ? retval : -EDOM;
+ }
+ }
+
+ /* there's always [9.4.3] a device descriptor [9.6.1] */
+ retval = usb_get_descriptor(udev, USB_DT_DEVICE, 0,
+ dev->buf, sizeof(udev->descriptor));
+ if (retval != sizeof(udev->descriptor)) {
+ dev_err(&iface->dev, "dev descriptor --> %d\n", retval);
+ return (retval < 0) ? retval : -EDOM;
+ }
+
+ /*
+ * there's always [9.4.3] a bos device descriptor [9.6.2] in USB
+ * 3.0 spec
+ */
+ if (le16_to_cpu(udev->descriptor.bcdUSB) >= 0x0210) {
+ struct usb_bos_descriptor *bos = NULL;
+ struct usb_dev_cap_header *header = NULL;
+ unsigned total, num, length;
+ u8 *buf;
+
+ retval = usb_get_descriptor(udev, USB_DT_BOS, 0, dev->buf,
+ sizeof(*udev->bos->desc));
+ if (retval != sizeof(*udev->bos->desc)) {
+ dev_err(&iface->dev, "bos descriptor --> %d\n", retval);
+ return (retval < 0) ? retval : -EDOM;
+ }
+
+ bos = (struct usb_bos_descriptor *)dev->buf;
+ total = le16_to_cpu(bos->wTotalLength);
+ num = bos->bNumDeviceCaps;
+
+ if (total > TBUF_SIZE)
+ total = TBUF_SIZE;
+
+ /*
+ * get generic device-level capability descriptors [9.6.2]
+ * in USB 3.0 spec
+ */
+ retval = usb_get_descriptor(udev, USB_DT_BOS, 0, dev->buf,
+ total);
+ if (retval != total) {
+ dev_err(&iface->dev, "bos descriptor set --> %d\n",
+ retval);
+ return (retval < 0) ? retval : -EDOM;
+ }
+
+ length = sizeof(*udev->bos->desc);
+ buf = dev->buf;
+ for (i = 0; i < num; i++) {
+ buf += length;
+ if (buf + sizeof(struct usb_dev_cap_header) >
+ dev->buf + total)
+ break;
+
+ header = (struct usb_dev_cap_header *)buf;
+ length = header->bLength;
+
+ if (header->bDescriptorType !=
+ USB_DT_DEVICE_CAPABILITY) {
+ dev_warn(&udev->dev, "not device capability descriptor, skip\n");
+ continue;
+ }
+
+ switch (header->bDevCapabilityType) {
+ case USB_CAP_TYPE_EXT:
+ if (buf + USB_DT_USB_EXT_CAP_SIZE >
+ dev->buf + total ||
+ !is_good_ext(dev, buf)) {
+ dev_err(&iface->dev, "bogus usb 2.0 extension descriptor\n");
+ return -EDOM;
+ }
+ break;
+ case USB_SS_CAP_TYPE:
+ if (buf + USB_DT_USB_SS_CAP_SIZE >
+ dev->buf + total ||
+ !is_good_ss_cap(dev, buf)) {
+ dev_err(&iface->dev, "bogus superspeed device capability descriptor\n");
+ return -EDOM;
+ }
+ break;
+ case CONTAINER_ID_TYPE:
+ if (buf + USB_DT_USB_SS_CONTN_ID_SIZE >
+ dev->buf + total ||
+ !is_good_con_id(dev, buf)) {
+ dev_err(&iface->dev, "bogus container id descriptor\n");
+ return -EDOM;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ /* there's always [9.4.3] at least one config descriptor [9.6.3] */
+ for (i = 0; i < udev->descriptor.bNumConfigurations; i++) {
+ retval = usb_get_descriptor(udev, USB_DT_CONFIG, i,
+ dev->buf, TBUF_SIZE);
+ if (!is_good_config(dev, retval)) {
+ dev_err(&iface->dev,
+ "config [%d] descriptor --> %d\n",
+ i, retval);
+ return (retval < 0) ? retval : -EDOM;
+ }
+
+ /* FIXME cross-checking udev->config[i] to make sure usbcore
+ * parsed it right (etc) would be good testing paranoia
+ */
+ }
+
+ /* and sometimes [9.2.6.6] speed dependent descriptors */
+ if (le16_to_cpu(udev->descriptor.bcdUSB) == 0x0200) {
+ struct usb_qualifier_descriptor *d = NULL;
+
+ /* device qualifier [9.6.2] */
+ retval = usb_get_descriptor(udev,
+ USB_DT_DEVICE_QUALIFIER, 0, dev->buf,
+ sizeof(struct usb_qualifier_descriptor));
+ if (retval == -EPIPE) {
+ if (udev->speed == USB_SPEED_HIGH) {
+ dev_err(&iface->dev,
+ "hs dev qualifier --> %d\n",
+ retval);
+ return retval;
+ }
+ /* usb2.0 but not high-speed capable; fine */
+ } else if (retval != sizeof(struct usb_qualifier_descriptor)) {
+ dev_err(&iface->dev, "dev qualifier --> %d\n", retval);
+ return (retval < 0) ? retval : -EDOM;
+ } else
+ d = (struct usb_qualifier_descriptor *) dev->buf;
+
+ /* might not have [9.6.2] any other-speed configs [9.6.4] */
+ if (d) {
+ unsigned max = d->bNumConfigurations;
+ for (i = 0; i < max; i++) {
+ retval = usb_get_descriptor(udev,
+ USB_DT_OTHER_SPEED_CONFIG, i,
+ dev->buf, TBUF_SIZE);
+ if (!is_good_config(dev, retval)) {
+ dev_err(&iface->dev,
+ "other speed config --> %d\n",
+ retval);
+ return (retval < 0) ? retval : -EDOM;
+ }
+ }
+ }
+ }
+ /* FIXME fetch strings from at least the device descriptor */
+
+ /* [9.4.5] get_status always works */
+ retval = usb_get_std_status(udev, USB_RECIP_DEVICE, 0, dev->buf);
+ if (retval) {
+ dev_err(&iface->dev, "get dev status --> %d\n", retval);
+ return retval;
+ }
+
+ /* FIXME configuration.bmAttributes says if we could try to set/clear
+ * the device's remote wakeup feature ... if we can, test that here
+ */
+
+ retval = usb_get_std_status(udev, USB_RECIP_INTERFACE,
+ iface->altsetting[0].desc.bInterfaceNumber, dev->buf);
+ if (retval) {
+ dev_err(&iface->dev, "get interface status --> %d\n", retval);
+ return retval;
+ }
+ /* FIXME get status for each endpoint in the interface */
+
+ return 0;
+}
+
+/*-------------------------------------------------------------------------*/
+
+/* use ch9 requests to test whether:
+ * (a) queues work for control, keeping N subtests queued and
+ * active (auto-resubmit) for M loops through the queue.
+ * (b) protocol stalls (control-only) will autorecover.
+ * it's not like bulk/intr; no halt clearing.
+ * (c) short control reads are reported and handled.
+ * (d) queues are always processed in-order
+ */
+
+struct ctrl_ctx {
+ spinlock_t lock;
+ struct usbtest_dev *dev;
+ struct completion complete;
+ unsigned count;
+ unsigned pending;
+ int status;
+ struct urb **urb;
+ struct usbtest_param_32 *param;
+ int last;
+};
+
+#define NUM_SUBCASES 16 /* how many test subcases here? */
+
+struct subcase {
+ struct usb_ctrlrequest setup;
+ int number;
+ int expected;
+};
+
+static void ctrl_complete(struct urb *urb)
+{
+ struct ctrl_ctx *ctx = urb->context;
+ struct usb_ctrlrequest *reqp;
+ struct subcase *subcase;
+ int status = urb->status;
+ unsigned long flags;
+
+ reqp = (struct usb_ctrlrequest *)urb->setup_packet;
+ subcase = container_of(reqp, struct subcase, setup);
+
+ spin_lock_irqsave(&ctx->lock, flags);
+ ctx->count--;
+ ctx->pending--;
+
+ /* queue must transfer and complete in fifo order, unless
+ * usb_unlink_urb() is used to unlink something not at the
+ * physical queue head (not tested).
+ */
+ if (subcase->number > 0) {
+ if ((subcase->number - ctx->last) != 1) {
+ ERROR(ctx->dev,
+ "subcase %d completed out of order, last %d\n",
+ subcase->number, ctx->last);
+ status = -EDOM;
+ ctx->last = subcase->number;
+ goto error;
+ }
+ }
+ ctx->last = subcase->number;
+
+ /* succeed or fault in only one way? */
+ if (status == subcase->expected)
+ status = 0;
+
+ /* async unlink for cleanup? */
+ else if (status != -ECONNRESET) {
+
+ /* some faults are allowed, not required */
+ if (subcase->expected > 0 && (
+ ((status == -subcase->expected /* happened */
+ || status == 0)))) /* didn't */
+ status = 0;
+ /* sometimes more than one fault is allowed */
+ else if (subcase->number == 12 && status == -EPIPE)
+ status = 0;
+ else
+ ERROR(ctx->dev, "subtest %d error, status %d\n",
+ subcase->number, status);
+ }
+
+ /* unexpected status codes mean errors; ideally, in hardware */
+ if (status) {
+error:
+ if (ctx->status == 0) {
+ int i;
+
+ ctx->status = status;
+ ERROR(ctx->dev, "control queue %02x.%02x, err %d, "
+ "%d left, subcase %d, len %d/%d\n",
+ reqp->bRequestType, reqp->bRequest,
+ status, ctx->count, subcase->number,
+ urb->actual_length,
+ urb->transfer_buffer_length);
+
+ /* FIXME this "unlink everything" exit route should
+ * be a separate test case.
+ */
+
+ /* unlink whatever's still pending */
+ for (i = 1; i < ctx->param->sglen; i++) {
+ struct urb *u = ctx->urb[
+ (i + subcase->number)
+ % ctx->param->sglen];
+
+ if (u == urb || !u->dev)
+ continue;
+ spin_unlock(&ctx->lock);
+ status = usb_unlink_urb(u);
+ spin_lock(&ctx->lock);
+ switch (status) {
+ case -EINPROGRESS:
+ case -EBUSY:
+ case -EIDRM:
+ continue;
+ default:
+ ERROR(ctx->dev, "urb unlink --> %d\n",
+ status);
+ }
+ }
+ status = ctx->status;
+ }
+ }
+
+ /* resubmit if we need to, else mark this as done */
+ if ((status == 0) && (ctx->pending < ctx->count)) {
+ status = usb_submit_urb(urb, GFP_ATOMIC);
+ if (status != 0) {
+ ERROR(ctx->dev,
+ "can't resubmit ctrl %02x.%02x, err %d\n",
+ reqp->bRequestType, reqp->bRequest, status);
+ urb->dev = NULL;
+ } else
+ ctx->pending++;
+ } else
+ urb->dev = NULL;
+
+ /* signal completion when nothing's queued */
+ if (ctx->pending == 0)
+ complete(&ctx->complete);
+ spin_unlock_irqrestore(&ctx->lock, flags);
+}
+
+static int
+test_ctrl_queue(struct usbtest_dev *dev, struct usbtest_param_32 *param)
+{
+ struct usb_device *udev = testdev_to_usbdev(dev);
+ struct urb **urb;
+ struct ctrl_ctx context;
+ int i;
+
+ if (param->sglen == 0 || param->iterations > UINT_MAX / param->sglen)
+ return -EOPNOTSUPP;
+
+ spin_lock_init(&context.lock);
+ context.dev = dev;
+ init_completion(&context.complete);
+ context.count = param->sglen * param->iterations;
+ context.pending = 0;
+ context.status = -ENOMEM;
+ context.param = param;
+ context.last = -1;
+
+ /* allocate and init the urbs we'll queue.
+ * as with bulk/intr sglists, sglen is the queue depth; it also
+ * controls which subtests run (more tests than sglen) or rerun.
+ */
+ urb = kcalloc(param->sglen, sizeof(struct urb *), GFP_KERNEL);
+ if (!urb)
+ return -ENOMEM;
+ for (i = 0; i < param->sglen; i++) {
+ int pipe = usb_rcvctrlpipe(udev, 0);
+ unsigned len;
+ struct urb *u;
+ struct usb_ctrlrequest req;
+ struct subcase *reqp;
+
+ /* sign of this variable means:
+ * -: tested code must return this (negative) error code
+ * +: tested code may return this (negative too) error code
+ */
+ int expected = 0;
+
+ /* requests here are mostly expected to succeed on any
+ * device, but some are chosen to trigger protocol stalls
+ * or short reads.
+ */
+ memset(&req, 0, sizeof(req));
+ req.bRequest = USB_REQ_GET_DESCRIPTOR;
+ req.bRequestType = USB_DIR_IN|USB_RECIP_DEVICE;
+
+ switch (i % NUM_SUBCASES) {
+ case 0: /* get device descriptor */
+ req.wValue = cpu_to_le16(USB_DT_DEVICE << 8);
+ len = sizeof(struct usb_device_descriptor);
+ break;
+ case 1: /* get first config descriptor (only) */
+ req.wValue = cpu_to_le16((USB_DT_CONFIG << 8) | 0);
+ len = sizeof(struct usb_config_descriptor);
+ break;
+ case 2: /* get altsetting (OFTEN STALLS) */
+ req.bRequest = USB_REQ_GET_INTERFACE;
+ req.bRequestType = USB_DIR_IN|USB_RECIP_INTERFACE;
+ /* index = 0 means first interface */
+ len = 1;
+ expected = EPIPE;
+ break;
+ case 3: /* get interface status */
+ req.bRequest = USB_REQ_GET_STATUS;
+ req.bRequestType = USB_DIR_IN|USB_RECIP_INTERFACE;
+ /* interface 0 */
+ len = 2;
+ break;
+ case 4: /* get device status */
+ req.bRequest = USB_REQ_GET_STATUS;
+ req.bRequestType = USB_DIR_IN|USB_RECIP_DEVICE;
+ len = 2;
+ break;
+ case 5: /* get device qualifier (MAY STALL) */
+ req.wValue = cpu_to_le16 (USB_DT_DEVICE_QUALIFIER << 8);
+ len = sizeof(struct usb_qualifier_descriptor);
+ if (udev->speed != USB_SPEED_HIGH)
+ expected = EPIPE;
+ break;
+ case 6: /* get first config descriptor, plus interface */
+ req.wValue = cpu_to_le16((USB_DT_CONFIG << 8) | 0);
+ len = sizeof(struct usb_config_descriptor);
+ len += sizeof(struct usb_interface_descriptor);
+ break;
+ case 7: /* get interface descriptor (ALWAYS STALLS) */
+ req.wValue = cpu_to_le16 (USB_DT_INTERFACE << 8);
+ /* interface == 0 */
+ len = sizeof(struct usb_interface_descriptor);
+ expected = -EPIPE;
+ break;
+ /* NOTE: two consecutive stalls in the queue here.
+ * that tests fault recovery a bit more aggressively. */
+ case 8: /* clear endpoint halt (MAY STALL) */
+ req.bRequest = USB_REQ_CLEAR_FEATURE;
+ req.bRequestType = USB_RECIP_ENDPOINT;
+ /* wValue 0 == ep halt */
+ /* wIndex 0 == ep0 (shouldn't halt!) */
+ len = 0;
+ pipe = usb_sndctrlpipe(udev, 0);
+ expected = EPIPE;
+ break;
+ case 9: /* get endpoint status */
+ req.bRequest = USB_REQ_GET_STATUS;
+ req.bRequestType = USB_DIR_IN|USB_RECIP_ENDPOINT;
+ /* endpoint 0 */
+ len = 2;
+ break;
+ case 10: /* trigger short read (EREMOTEIO) */
+ req.wValue = cpu_to_le16((USB_DT_CONFIG << 8) | 0);
+ len = 1024;
+ expected = -EREMOTEIO;
+ break;
+ /* NOTE: two consecutive _different_ faults in the queue. */
+ case 11: /* get endpoint descriptor (ALWAYS STALLS) */
+ req.wValue = cpu_to_le16(USB_DT_ENDPOINT << 8);
+ /* endpoint == 0 */
+ len = sizeof(struct usb_interface_descriptor);
+ expected = EPIPE;
+ break;
+ /* NOTE: sometimes even a third fault in the queue! */
+ case 12: /* get string 0 descriptor (MAY STALL) */
+ req.wValue = cpu_to_le16(USB_DT_STRING << 8);
+ /* string == 0, for language IDs */
+ len = sizeof(struct usb_interface_descriptor);
+ /* may succeed when > 4 languages */
+ expected = EREMOTEIO; /* or EPIPE, if no strings */
+ break;
+ case 13: /* short read, resembling case 10 */
+ req.wValue = cpu_to_le16((USB_DT_CONFIG << 8) | 0);
+ /* last data packet "should" be DATA1, not DATA0 */
+ if (udev->speed == USB_SPEED_SUPER)
+ len = 1024 - 512;
+ else
+ len = 1024 - udev->descriptor.bMaxPacketSize0;
+ expected = -EREMOTEIO;
+ break;
+ case 14: /* short read; try to fill the last packet */
+ req.wValue = cpu_to_le16((USB_DT_DEVICE << 8) | 0);
+ /* device descriptor size == 18 bytes */
+ len = udev->descriptor.bMaxPacketSize0;
+ if (udev->speed == USB_SPEED_SUPER)
+ len = 512;
+ switch (len) {
+ case 8:
+ len = 24;
+ break;
+ case 16:
+ len = 32;
+ break;
+ }
+ expected = -EREMOTEIO;
+ break;
+ case 15:
+ req.wValue = cpu_to_le16(USB_DT_BOS << 8);
+ if (udev->bos)
+ len = le16_to_cpu(udev->bos->desc->wTotalLength);
+ else
+ len = sizeof(struct usb_bos_descriptor);
+ if (le16_to_cpu(udev->descriptor.bcdUSB) < 0x0201)
+ expected = -EPIPE;
+ break;
+ default:
+ ERROR(dev, "bogus number of ctrl queue testcases!\n");
+ context.status = -EINVAL;
+ goto cleanup;
+ }
+ req.wLength = cpu_to_le16(len);
+ urb[i] = u = simple_alloc_urb(udev, pipe, len, 0);
+ if (!u)
+ goto cleanup;
+
+ reqp = kmalloc(sizeof(*reqp), GFP_KERNEL);
+ if (!reqp)
+ goto cleanup;
+ reqp->setup = req;
+ reqp->number = i % NUM_SUBCASES;
+ reqp->expected = expected;
+ u->setup_packet = (char *) &reqp->setup;
+
+ u->context = &context;
+ u->complete = ctrl_complete;
+ }
+
+ /* queue the urbs */
+ context.urb = urb;
+ spin_lock_irq(&context.lock);
+ for (i = 0; i < param->sglen; i++) {
+ context.status = usb_submit_urb(urb[i], GFP_ATOMIC);
+ if (context.status != 0) {
+ ERROR(dev, "can't submit urb[%d], status %d\n",
+ i, context.status);
+ context.count = context.pending;
+ break;
+ }
+ context.pending++;
+ }
+ spin_unlock_irq(&context.lock);
+
+ /* FIXME set timer and time out; provide a disconnect hook */
+
+ /* wait for the last one to complete */
+ if (context.pending > 0)
+ wait_for_completion(&context.complete);
+
+cleanup:
+ for (i = 0; i < param->sglen; i++) {
+ if (!urb[i])
+ continue;
+ urb[i]->dev = udev;
+ kfree(urb[i]->setup_packet);
+ simple_free_urb(urb[i]);
+ }
+ kfree(urb);
+ return context.status;
+}
+#undef NUM_SUBCASES
+
+
+/*-------------------------------------------------------------------------*/
+
+static void unlink1_callback(struct urb *urb)
+{
+ int status = urb->status;
+
+ /* we "know" -EPIPE (stall) never happens */
+ if (!status)
+ status = usb_submit_urb(urb, GFP_ATOMIC);
+ if (status) {
+ urb->status = status;
+ complete(urb->context);
+ }
+}
+
+static int unlink1(struct usbtest_dev *dev, int pipe, int size, int async)
+{
+ struct urb *urb;
+ struct completion completion;
+ int retval = 0;
+
+ init_completion(&completion);
+ urb = simple_alloc_urb(testdev_to_usbdev(dev), pipe, size, 0);
+ if (!urb)
+ return -ENOMEM;
+ urb->context = &completion;
+ urb->complete = unlink1_callback;
+
+ if (usb_pipeout(urb->pipe)) {
+ simple_fill_buf(urb);
+ urb->transfer_flags |= URB_ZERO_PACKET;
+ }
+
+ /* keep the endpoint busy. there are lots of hc/hcd-internal
+ * states, and testing should get to all of them over time.
+ *
+ * FIXME want additional tests for when endpoint is STALLing
+ * due to errors, or is just NAKing requests.
+ */
+ retval = usb_submit_urb(urb, GFP_KERNEL);
+ if (retval != 0) {
+ dev_err(&dev->intf->dev, "submit fail %d\n", retval);
+ return retval;
+ }
+
+ /* unlinking that should always work. variable delay tests more
+ * hcd states and code paths, even with little other system load.
+ */
+ msleep(jiffies % (2 * INTERRUPT_RATE));
+ if (async) {
+ while (!completion_done(&completion)) {
+ retval = usb_unlink_urb(urb);
+
+ if (retval == 0 && usb_pipein(urb->pipe))
+ retval = simple_check_buf(dev, urb);
+
+ switch (retval) {
+ case -EBUSY:
+ case -EIDRM:
+ /* we can't unlink urbs while they're completing
+ * or if they've completed, and we haven't
+ * resubmitted. "normal" drivers would prevent
+ * resubmission, but since we're testing unlink
+ * paths, we can't.
+ */
+ ERROR(dev, "unlink retry\n");
+ continue;
+ case 0:
+ case -EINPROGRESS:
+ break;
+
+ default:
+ dev_err(&dev->intf->dev,
+ "unlink fail %d\n", retval);
+ return retval;
+ }
+
+ break;
+ }
+ } else
+ usb_kill_urb(urb);
+
+ wait_for_completion(&completion);
+ retval = urb->status;
+ simple_free_urb(urb);
+
+ if (async)
+ return (retval == -ECONNRESET) ? 0 : retval - 1000;
+ else
+ return (retval == -ENOENT || retval == -EPERM) ?
+ 0 : retval - 2000;
+}
+
+static int unlink_simple(struct usbtest_dev *dev, int pipe, int len)
+{
+ int retval = 0;
+
+ /* test sync and async paths */
+ retval = unlink1(dev, pipe, len, 1);
+ if (!retval)
+ retval = unlink1(dev, pipe, len, 0);
+ return retval;
+}
+
+/*-------------------------------------------------------------------------*/
+
+struct queued_ctx {
+ struct completion complete;
+ atomic_t pending;
+ unsigned num;
+ int status;
+ struct urb **urbs;
+};
+
+static void unlink_queued_callback(struct urb *urb)
+{
+ int status = urb->status;
+ struct queued_ctx *ctx = urb->context;
+
+ if (ctx->status)
+ goto done;
+ if (urb == ctx->urbs[ctx->num - 4] || urb == ctx->urbs[ctx->num - 2]) {
+ if (status == -ECONNRESET)
+ goto done;
+ /* What error should we report if the URB completed normally? */
+ }
+ if (status != 0)
+ ctx->status = status;
+
+ done:
+ if (atomic_dec_and_test(&ctx->pending))
+ complete(&ctx->complete);
+}
+
+static int unlink_queued(struct usbtest_dev *dev, int pipe, unsigned num,
+ unsigned size)
+{
+ struct queued_ctx ctx;
+ struct usb_device *udev = testdev_to_usbdev(dev);
+ void *buf;
+ dma_addr_t buf_dma;
+ int i;
+ int retval = -ENOMEM;
+
+ init_completion(&ctx.complete);
+ atomic_set(&ctx.pending, 1); /* One more than the actual value */
+ ctx.num = num;
+ ctx.status = 0;
+
+ buf = usb_alloc_coherent(udev, size, GFP_KERNEL, &buf_dma);
+ if (!buf)
+ return retval;
+ memset(buf, 0, size);
+
+ /* Allocate and init the urbs we'll queue */
+ ctx.urbs = kcalloc(num, sizeof(struct urb *), GFP_KERNEL);
+ if (!ctx.urbs)
+ goto free_buf;
+ for (i = 0; i < num; i++) {
+ ctx.urbs[i] = usb_alloc_urb(0, GFP_KERNEL);
+ if (!ctx.urbs[i])
+ goto free_urbs;
+ usb_fill_bulk_urb(ctx.urbs[i], udev, pipe, buf, size,
+ unlink_queued_callback, &ctx);
+ ctx.urbs[i]->transfer_dma = buf_dma;
+ ctx.urbs[i]->transfer_flags = URB_NO_TRANSFER_DMA_MAP;
+
+ if (usb_pipeout(ctx.urbs[i]->pipe)) {
+ simple_fill_buf(ctx.urbs[i]);
+ ctx.urbs[i]->transfer_flags |= URB_ZERO_PACKET;
+ }
+ }
+
+ /* Submit all the URBs and then unlink URBs num - 4 and num - 2. */
+ for (i = 0; i < num; i++) {
+ atomic_inc(&ctx.pending);
+ retval = usb_submit_urb(ctx.urbs[i], GFP_KERNEL);
+ if (retval != 0) {
+ dev_err(&dev->intf->dev, "submit urbs[%d] fail %d\n",
+ i, retval);
+ atomic_dec(&ctx.pending);
+ ctx.status = retval;
+ break;
+ }
+ }
+ if (i == num) {
+ usb_unlink_urb(ctx.urbs[num - 4]);
+ usb_unlink_urb(ctx.urbs[num - 2]);
+ } else {
+ while (--i >= 0)
+ usb_unlink_urb(ctx.urbs[i]);
+ }
+
+ if (atomic_dec_and_test(&ctx.pending)) /* The extra count */
+ complete(&ctx.complete);
+ wait_for_completion(&ctx.complete);
+ retval = ctx.status;
+
+ free_urbs:
+ for (i = 0; i < num; i++)
+ usb_free_urb(ctx.urbs[i]);
+ kfree(ctx.urbs);
+ free_buf:
+ usb_free_coherent(udev, size, buf, buf_dma);
+ return retval;
+}
+
+/*-------------------------------------------------------------------------*/
+
+static int verify_not_halted(struct usbtest_dev *tdev, int ep, struct urb *urb)
+{
+ int retval;
+ u16 status;
+
+ /* shouldn't look or act halted */
+ retval = usb_get_std_status(urb->dev, USB_RECIP_ENDPOINT, ep, &status);
+ if (retval < 0) {
+ ERROR(tdev, "ep %02x couldn't get no-halt status, %d\n",
+ ep, retval);
+ return retval;
+ }
+ if (status != 0) {
+ ERROR(tdev, "ep %02x bogus status: %04x != 0\n", ep, status);
+ return -EINVAL;
+ }
+ retval = simple_io(tdev, urb, 1, 0, 0, __func__);
+ if (retval != 0)
+ return -EINVAL;
+ return 0;
+}
+
+static int verify_halted(struct usbtest_dev *tdev, int ep, struct urb *urb)
+{
+ int retval;
+ u16 status;
+
+ /* should look and act halted */
+ retval = usb_get_std_status(urb->dev, USB_RECIP_ENDPOINT, ep, &status);
+ if (retval < 0) {
+ ERROR(tdev, "ep %02x couldn't get halt status, %d\n",
+ ep, retval);
+ return retval;
+ }
+ if (status != 1) {
+ ERROR(tdev, "ep %02x bogus status: %04x != 1\n", ep, status);
+ return -EINVAL;
+ }
+ retval = simple_io(tdev, urb, 1, 0, -EPIPE, __func__);
+ if (retval != -EPIPE)
+ return -EINVAL;
+ retval = simple_io(tdev, urb, 1, 0, -EPIPE, "verify_still_halted");
+ if (retval != -EPIPE)
+ return -EINVAL;
+ return 0;
+}
+
+static int test_halt(struct usbtest_dev *tdev, int ep, struct urb *urb)
+{
+ int retval;
+
+ /* shouldn't look or act halted now */
+ retval = verify_not_halted(tdev, ep, urb);
+ if (retval < 0)
+ return retval;
+
+ /* set halt (protocol test only), verify it worked */
+ retval = usb_control_msg(urb->dev, usb_sndctrlpipe(urb->dev, 0),
+ USB_REQ_SET_FEATURE, USB_RECIP_ENDPOINT,
+ USB_ENDPOINT_HALT, ep,
+ NULL, 0, USB_CTRL_SET_TIMEOUT);
+ if (retval < 0) {
+ ERROR(tdev, "ep %02x couldn't set halt, %d\n", ep, retval);
+ return retval;
+ }
+ retval = verify_halted(tdev, ep, urb);
+ if (retval < 0) {
+ int ret;
+
+ /* clear halt anyways, else further tests will fail */
+ ret = usb_clear_halt(urb->dev, urb->pipe);
+ if (ret)
+ ERROR(tdev, "ep %02x couldn't clear halt, %d\n",
+ ep, ret);
+
+ return retval;
+ }
+
+ /* clear halt (tests API + protocol), verify it worked */
+ retval = usb_clear_halt(urb->dev, urb->pipe);
+ if (retval < 0) {
+ ERROR(tdev, "ep %02x couldn't clear halt, %d\n", ep, retval);
+ return retval;
+ }
+ retval = verify_not_halted(tdev, ep, urb);
+ if (retval < 0)
+ return retval;
+
+ /* NOTE: could also verify SET_INTERFACE clear halts ... */
+
+ return 0;
+}
+
+static int test_toggle_sync(struct usbtest_dev *tdev, int ep, struct urb *urb)
+{
+ int retval;
+
+ /* clear initial data toggle to DATA0 */
+ retval = usb_clear_halt(urb->dev, urb->pipe);
+ if (retval < 0) {
+ ERROR(tdev, "ep %02x couldn't clear halt, %d\n", ep, retval);
+ return retval;
+ }
+
+ /* transfer 3 data packets, should be DATA0, DATA1, DATA0 */
+ retval = simple_io(tdev, urb, 1, 0, 0, __func__);
+ if (retval != 0)
+ return -EINVAL;
+
+ /* clear halt resets device side data toggle, host should react to it */
+ retval = usb_clear_halt(urb->dev, urb->pipe);
+ if (retval < 0) {
+ ERROR(tdev, "ep %02x couldn't clear halt, %d\n", ep, retval);
+ return retval;
+ }
+
+ /* host should use DATA0 again after clear halt */
+ retval = simple_io(tdev, urb, 1, 0, 0, __func__);
+
+ return retval;
+}
+
+static int halt_simple(struct usbtest_dev *dev)
+{
+ int ep;
+ int retval = 0;
+ struct urb *urb;
+ struct usb_device *udev = testdev_to_usbdev(dev);
+
+ if (udev->speed == USB_SPEED_SUPER)
+ urb = simple_alloc_urb(udev, 0, 1024, 0);
+ else
+ urb = simple_alloc_urb(udev, 0, 512, 0);
+ if (urb == NULL)
+ return -ENOMEM;
+
+ if (dev->in_pipe) {
+ ep = usb_pipeendpoint(dev->in_pipe) | USB_DIR_IN;
+ urb->pipe = dev->in_pipe;
+ retval = test_halt(dev, ep, urb);
+ if (retval < 0)
+ goto done;
+ }
+
+ if (dev->out_pipe) {
+ ep = usb_pipeendpoint(dev->out_pipe);
+ urb->pipe = dev->out_pipe;
+ retval = test_halt(dev, ep, urb);
+ }
+done:
+ simple_free_urb(urb);
+ return retval;
+}
+
+static int toggle_sync_simple(struct usbtest_dev *dev)
+{
+ int ep;
+ int retval = 0;
+ struct urb *urb;
+ struct usb_device *udev = testdev_to_usbdev(dev);
+ unsigned maxp = get_maxpacket(udev, dev->out_pipe);
+
+ /*
+ * Create a URB that causes a transfer of uneven amount of data packets
+ * This way the clear toggle has an impact on the data toggle sequence.
+ * Use 2 maxpacket length packets and one zero packet.
+ */
+ urb = simple_alloc_urb(udev, 0, 2 * maxp, 0);
+ if (urb == NULL)
+ return -ENOMEM;
+
+ urb->transfer_flags |= URB_ZERO_PACKET;
+
+ ep = usb_pipeendpoint(dev->out_pipe);
+ urb->pipe = dev->out_pipe;
+ retval = test_toggle_sync(dev, ep, urb);
+
+ simple_free_urb(urb);
+ return retval;
+}
+
+/*-------------------------------------------------------------------------*/
+
+/* Control OUT tests use the vendor control requests from Intel's
+ * USB 2.0 compliance test device: write a buffer, read it back.
+ *
+ * Intel's spec only _requires_ that it work for one packet, which
+ * is pretty weak. Some HCDs place limits here; most devices will
+ * need to be able to handle more than one OUT data packet. We'll
+ * try whatever we're told to try.
+ */
+static int ctrl_out(struct usbtest_dev *dev,
+ unsigned count, unsigned length, unsigned vary, unsigned offset)
+{
+ unsigned i, j, len;
+ int retval;
+ u8 *buf;
+ char *what = "?";
+ struct usb_device *udev;
+
+ if (length < 1 || length > 0xffff || vary >= length)
+ return -EINVAL;
+
+ buf = kmalloc(length + offset, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ buf += offset;
+ udev = testdev_to_usbdev(dev);
+ len = length;
+ retval = 0;
+
+ /* NOTE: hardware might well act differently if we pushed it
+ * with lots back-to-back queued requests.
+ */
+ for (i = 0; i < count; i++) {
+ /* write patterned data */
+ for (j = 0; j < len; j++)
+ buf[j] = (u8)(i + j);
+ retval = usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
+ 0x5b, USB_DIR_OUT|USB_TYPE_VENDOR,
+ 0, 0, buf, len, USB_CTRL_SET_TIMEOUT);
+ if (retval != len) {
+ what = "write";
+ if (retval >= 0) {
+ ERROR(dev, "ctrl_out, wlen %d (expected %d)\n",
+ retval, len);
+ retval = -EBADMSG;
+ }
+ break;
+ }
+
+ /* read it back -- assuming nothing intervened!! */
+ retval = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0),
+ 0x5c, USB_DIR_IN|USB_TYPE_VENDOR,
+ 0, 0, buf, len, USB_CTRL_GET_TIMEOUT);
+ if (retval != len) {
+ what = "read";
+ if (retval >= 0) {
+ ERROR(dev, "ctrl_out, rlen %d (expected %d)\n",
+ retval, len);
+ retval = -EBADMSG;
+ }
+ break;
+ }
+
+ /* fail if we can't verify */
+ for (j = 0; j < len; j++) {
+ if (buf[j] != (u8)(i + j)) {
+ ERROR(dev, "ctrl_out, byte %d is %d not %d\n",
+ j, buf[j], (u8)(i + j));
+ retval = -EBADMSG;
+ break;
+ }
+ }
+ if (retval < 0) {
+ what = "verify";
+ break;
+ }
+
+ len += vary;
+
+ /* [real world] the "zero bytes IN" case isn't really used.
+ * hardware can easily trip up in this weird case, since its
+ * status stage is IN, not OUT like other ep0in transfers.
+ */
+ if (len > length)
+ len = realworld ? 1 : 0;
+ }
+
+ if (retval < 0)
+ ERROR(dev, "ctrl_out %s failed, code %d, count %d\n",
+ what, retval, i);
+
+ kfree(buf - offset);
+ return retval;
+}
+
+/*-------------------------------------------------------------------------*/
+
+/* ISO/BULK tests ... mimics common usage
+ * - buffer length is split into N packets (mostly maxpacket sized)
+ * - multi-buffers according to sglen
+ */
+
+struct transfer_context {
+ unsigned count;
+ unsigned pending;
+ spinlock_t lock;
+ struct completion done;
+ int submit_error;
+ unsigned long errors;
+ unsigned long packet_count;
+ struct usbtest_dev *dev;
+ bool is_iso;
+};
+
+static void complicated_callback(struct urb *urb)
+{
+ struct transfer_context *ctx = urb->context;
+ unsigned long flags;
+
+ spin_lock_irqsave(&ctx->lock, flags);
+ ctx->count--;
+
+ ctx->packet_count += urb->number_of_packets;
+ if (urb->error_count > 0)
+ ctx->errors += urb->error_count;
+ else if (urb->status != 0)
+ ctx->errors += (ctx->is_iso ? urb->number_of_packets : 1);
+ else if (urb->actual_length != urb->transfer_buffer_length)
+ ctx->errors++;
+ else if (check_guard_bytes(ctx->dev, urb) != 0)
+ ctx->errors++;
+
+ if (urb->status == 0 && ctx->count > (ctx->pending - 1)
+ && !ctx->submit_error) {
+ int status = usb_submit_urb(urb, GFP_ATOMIC);
+ switch (status) {
+ case 0:
+ goto done;
+ default:
+ dev_err(&ctx->dev->intf->dev,
+ "resubmit err %d\n",
+ status);
+ fallthrough;
+ case -ENODEV: /* disconnected */
+ case -ESHUTDOWN: /* endpoint disabled */
+ ctx->submit_error = 1;
+ break;
+ }
+ }
+
+ ctx->pending--;
+ if (ctx->pending == 0) {
+ if (ctx->errors)
+ dev_err(&ctx->dev->intf->dev,
+ "during the test, %lu errors out of %lu\n",
+ ctx->errors, ctx->packet_count);
+ complete(&ctx->done);
+ }
+done:
+ spin_unlock_irqrestore(&ctx->lock, flags);
+}
+
+static struct urb *iso_alloc_urb(
+ struct usb_device *udev,
+ int pipe,
+ struct usb_endpoint_descriptor *desc,
+ long bytes,
+ unsigned offset
+)
+{
+ struct urb *urb;
+ unsigned i, maxp, packets;
+
+ if (bytes < 0 || !desc)
+ return NULL;
+
+ maxp = usb_endpoint_maxp(desc);
+ if (udev->speed >= USB_SPEED_SUPER)
+ maxp *= ss_isoc_get_packet_num(udev, pipe);
+ else
+ maxp *= usb_endpoint_maxp_mult(desc);
+
+ packets = DIV_ROUND_UP(bytes, maxp);
+
+ urb = usb_alloc_urb(packets, GFP_KERNEL);
+ if (!urb)
+ return urb;
+ urb->dev = udev;
+ urb->pipe = pipe;
+
+ urb->number_of_packets = packets;
+ urb->transfer_buffer_length = bytes;
+ urb->transfer_buffer = usb_alloc_coherent(udev, bytes + offset,
+ GFP_KERNEL,
+ &urb->transfer_dma);
+ if (!urb->transfer_buffer) {
+ usb_free_urb(urb);
+ return NULL;
+ }
+ if (offset) {
+ memset(urb->transfer_buffer, GUARD_BYTE, offset);
+ urb->transfer_buffer += offset;
+ urb->transfer_dma += offset;
+ }
+ /* For inbound transfers use guard byte so that test fails if
+ data not correctly copied */
+ memset(urb->transfer_buffer,
+ usb_pipein(urb->pipe) ? GUARD_BYTE : 0,
+ bytes);
+
+ for (i = 0; i < packets; i++) {
+ /* here, only the last packet will be short */
+ urb->iso_frame_desc[i].length = min((unsigned) bytes, maxp);
+ bytes -= urb->iso_frame_desc[i].length;
+
+ urb->iso_frame_desc[i].offset = maxp * i;
+ }
+
+ urb->complete = complicated_callback;
+ /* urb->context = SET BY CALLER */
+ urb->interval = 1 << (desc->bInterval - 1);
+ urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP;
+ return urb;
+}
+
+static int
+test_queue(struct usbtest_dev *dev, struct usbtest_param_32 *param,
+ int pipe, struct usb_endpoint_descriptor *desc, unsigned offset)
+{
+ struct transfer_context context;
+ struct usb_device *udev;
+ unsigned i;
+ unsigned long packets = 0;
+ int status = 0;
+ struct urb **urbs;
+
+ if (!param->sglen || param->iterations > UINT_MAX / param->sglen)
+ return -EINVAL;
+
+ if (param->sglen > MAX_SGLEN)
+ return -EINVAL;
+
+ urbs = kcalloc(param->sglen, sizeof(*urbs), GFP_KERNEL);
+ if (!urbs)
+ return -ENOMEM;
+
+ memset(&context, 0, sizeof(context));
+ context.count = param->iterations * param->sglen;
+ context.dev = dev;
+ context.is_iso = !!desc;
+ init_completion(&context.done);
+ spin_lock_init(&context.lock);
+
+ udev = testdev_to_usbdev(dev);
+
+ for (i = 0; i < param->sglen; i++) {
+ if (context.is_iso)
+ urbs[i] = iso_alloc_urb(udev, pipe, desc,
+ param->length, offset);
+ else
+ urbs[i] = complicated_alloc_urb(udev, pipe,
+ param->length, 0);
+
+ if (!urbs[i]) {
+ status = -ENOMEM;
+ goto fail;
+ }
+ packets += urbs[i]->number_of_packets;
+ urbs[i]->context = &context;
+ }
+ packets *= param->iterations;
+
+ if (context.is_iso) {
+ int transaction_num;
+
+ if (udev->speed >= USB_SPEED_SUPER)
+ transaction_num = ss_isoc_get_packet_num(udev, pipe);
+ else
+ transaction_num = usb_endpoint_maxp_mult(desc);
+
+ dev_info(&dev->intf->dev,
+ "iso period %d %sframes, wMaxPacket %d, transactions: %d\n",
+ 1 << (desc->bInterval - 1),
+ (udev->speed >= USB_SPEED_HIGH) ? "micro" : "",
+ usb_endpoint_maxp(desc),
+ transaction_num);
+
+ dev_info(&dev->intf->dev,
+ "total %lu msec (%lu packets)\n",
+ (packets * (1 << (desc->bInterval - 1)))
+ / ((udev->speed >= USB_SPEED_HIGH) ? 8 : 1),
+ packets);
+ }
+
+ spin_lock_irq(&context.lock);
+ for (i = 0; i < param->sglen; i++) {
+ ++context.pending;
+ status = usb_submit_urb(urbs[i], GFP_ATOMIC);
+ if (status < 0) {
+ ERROR(dev, "submit iso[%d], error %d\n", i, status);
+ if (i == 0) {
+ spin_unlock_irq(&context.lock);
+ goto fail;
+ }
+
+ simple_free_urb(urbs[i]);
+ urbs[i] = NULL;
+ context.pending--;
+ context.submit_error = 1;
+ break;
+ }
+ }
+ spin_unlock_irq(&context.lock);
+
+ wait_for_completion(&context.done);
+
+ for (i = 0; i < param->sglen; i++) {
+ if (urbs[i])
+ simple_free_urb(urbs[i]);
+ }
+ /*
+ * Isochronous transfers are expected to fail sometimes. As an
+ * arbitrary limit, we will report an error if any submissions
+ * fail or if the transfer failure rate is > 10%.
+ */
+ if (status != 0)
+ ;
+ else if (context.submit_error)
+ status = -EACCES;
+ else if (context.errors >
+ (context.is_iso ? context.packet_count / 10 : 0))
+ status = -EIO;
+
+ kfree(urbs);
+ return status;
+
+fail:
+ for (i = 0; i < param->sglen; i++) {
+ if (urbs[i])
+ simple_free_urb(urbs[i]);
+ }
+
+ kfree(urbs);
+ return status;
+}
+
+static int test_unaligned_bulk(
+ struct usbtest_dev *tdev,
+ int pipe,
+ unsigned length,
+ int iterations,
+ unsigned transfer_flags,
+ const char *label)
+{
+ int retval;
+ struct urb *urb = usbtest_alloc_urb(testdev_to_usbdev(tdev),
+ pipe, length, transfer_flags, 1, 0, simple_callback);
+
+ if (!urb)
+ return -ENOMEM;
+
+ retval = simple_io(tdev, urb, iterations, 0, 0, label);
+ simple_free_urb(urb);
+ return retval;
+}
+
+/* Run tests. */
+static int
+usbtest_do_ioctl(struct usb_interface *intf, struct usbtest_param_32 *param)
+{
+ struct usbtest_dev *dev = usb_get_intfdata(intf);
+ struct usb_device *udev = testdev_to_usbdev(dev);
+ struct urb *urb;
+ struct scatterlist *sg;
+ struct usb_sg_request req;
+ unsigned i;
+ int retval = -EOPNOTSUPP;
+
+ if (param->iterations <= 0)
+ return -EINVAL;
+ if (param->sglen > MAX_SGLEN)
+ return -EINVAL;
+ /*
+ * Just a bunch of test cases that every HCD is expected to handle.
+ *
+ * Some may need specific firmware, though it'd be good to have
+ * one firmware image to handle all the test cases.
+ *
+ * FIXME add more tests! cancel requests, verify the data, control
+ * queueing, concurrent read+write threads, and so on.
+ */
+ switch (param->test_num) {
+
+ case 0:
+ dev_info(&intf->dev, "TEST 0: NOP\n");
+ retval = 0;
+ break;
+
+ /* Simple non-queued bulk I/O tests */
+ case 1:
+ if (dev->out_pipe == 0)
+ break;
+ dev_info(&intf->dev,
+ "TEST 1: write %d bytes %u times\n",
+ param->length, param->iterations);
+ urb = simple_alloc_urb(udev, dev->out_pipe, param->length, 0);
+ if (!urb) {
+ retval = -ENOMEM;
+ break;
+ }
+ /* FIRMWARE: bulk sink (maybe accepts short writes) */
+ retval = simple_io(dev, urb, param->iterations, 0, 0, "test1");
+ simple_free_urb(urb);
+ break;
+ case 2:
+ if (dev->in_pipe == 0)
+ break;
+ dev_info(&intf->dev,
+ "TEST 2: read %d bytes %u times\n",
+ param->length, param->iterations);
+ urb = simple_alloc_urb(udev, dev->in_pipe, param->length, 0);
+ if (!urb) {
+ retval = -ENOMEM;
+ break;
+ }
+ /* FIRMWARE: bulk source (maybe generates short writes) */
+ retval = simple_io(dev, urb, param->iterations, 0, 0, "test2");
+ simple_free_urb(urb);
+ break;
+ case 3:
+ if (dev->out_pipe == 0 || param->vary == 0)
+ break;
+ dev_info(&intf->dev,
+ "TEST 3: write/%d 0..%d bytes %u times\n",
+ param->vary, param->length, param->iterations);
+ urb = simple_alloc_urb(udev, dev->out_pipe, param->length, 0);
+ if (!urb) {
+ retval = -ENOMEM;
+ break;
+ }
+ /* FIRMWARE: bulk sink (maybe accepts short writes) */
+ retval = simple_io(dev, urb, param->iterations, param->vary,
+ 0, "test3");
+ simple_free_urb(urb);
+ break;
+ case 4:
+ if (dev->in_pipe == 0 || param->vary == 0)
+ break;
+ dev_info(&intf->dev,
+ "TEST 4: read/%d 0..%d bytes %u times\n",
+ param->vary, param->length, param->iterations);
+ urb = simple_alloc_urb(udev, dev->in_pipe, param->length, 0);
+ if (!urb) {
+ retval = -ENOMEM;
+ break;
+ }
+ /* FIRMWARE: bulk source (maybe generates short writes) */
+ retval = simple_io(dev, urb, param->iterations, param->vary,
+ 0, "test4");
+ simple_free_urb(urb);
+ break;
+
+ /* Queued bulk I/O tests */
+ case 5:
+ if (dev->out_pipe == 0 || param->sglen == 0)
+ break;
+ dev_info(&intf->dev,
+ "TEST 5: write %d sglists %d entries of %d bytes\n",
+ param->iterations,
+ param->sglen, param->length);
+ sg = alloc_sglist(param->sglen, param->length,
+ 0, dev, dev->out_pipe);
+ if (!sg) {
+ retval = -ENOMEM;
+ break;
+ }
+ /* FIRMWARE: bulk sink (maybe accepts short writes) */
+ retval = perform_sglist(dev, param->iterations, dev->out_pipe,
+ &req, sg, param->sglen);
+ free_sglist(sg, param->sglen);
+ break;
+
+ case 6:
+ if (dev->in_pipe == 0 || param->sglen == 0)
+ break;
+ dev_info(&intf->dev,
+ "TEST 6: read %d sglists %d entries of %d bytes\n",
+ param->iterations,
+ param->sglen, param->length);
+ sg = alloc_sglist(param->sglen, param->length,
+ 0, dev, dev->in_pipe);
+ if (!sg) {
+ retval = -ENOMEM;
+ break;
+ }
+ /* FIRMWARE: bulk source (maybe generates short writes) */
+ retval = perform_sglist(dev, param->iterations, dev->in_pipe,
+ &req, sg, param->sglen);
+ free_sglist(sg, param->sglen);
+ break;
+ case 7:
+ if (dev->out_pipe == 0 || param->sglen == 0 || param->vary == 0)
+ break;
+ dev_info(&intf->dev,
+ "TEST 7: write/%d %d sglists %d entries 0..%d bytes\n",
+ param->vary, param->iterations,
+ param->sglen, param->length);
+ sg = alloc_sglist(param->sglen, param->length,
+ param->vary, dev, dev->out_pipe);
+ if (!sg) {
+ retval = -ENOMEM;
+ break;
+ }
+ /* FIRMWARE: bulk sink (maybe accepts short writes) */
+ retval = perform_sglist(dev, param->iterations, dev->out_pipe,
+ &req, sg, param->sglen);
+ free_sglist(sg, param->sglen);
+ break;
+ case 8:
+ if (dev->in_pipe == 0 || param->sglen == 0 || param->vary == 0)
+ break;
+ dev_info(&intf->dev,
+ "TEST 8: read/%d %d sglists %d entries 0..%d bytes\n",
+ param->vary, param->iterations,
+ param->sglen, param->length);
+ sg = alloc_sglist(param->sglen, param->length,
+ param->vary, dev, dev->in_pipe);
+ if (!sg) {
+ retval = -ENOMEM;
+ break;
+ }
+ /* FIRMWARE: bulk source (maybe generates short writes) */
+ retval = perform_sglist(dev, param->iterations, dev->in_pipe,
+ &req, sg, param->sglen);
+ free_sglist(sg, param->sglen);
+ break;
+
+ /* non-queued sanity tests for control (chapter 9 subset) */
+ case 9:
+ retval = 0;
+ dev_info(&intf->dev,
+ "TEST 9: ch9 (subset) control tests, %d times\n",
+ param->iterations);
+ for (i = param->iterations; retval == 0 && i--; /* NOP */)
+ retval = ch9_postconfig(dev);
+ if (retval)
+ dev_err(&intf->dev, "ch9 subset failed, "
+ "iterations left %d\n", i);
+ break;
+
+ /* queued control messaging */
+ case 10:
+ retval = 0;
+ dev_info(&intf->dev,
+ "TEST 10: queue %d control calls, %d times\n",
+ param->sglen,
+ param->iterations);
+ retval = test_ctrl_queue(dev, param);
+ break;
+
+ /* simple non-queued unlinks (ring with one urb) */
+ case 11:
+ if (dev->in_pipe == 0 || !param->length)
+ break;
+ retval = 0;
+ dev_info(&intf->dev, "TEST 11: unlink %d reads of %d\n",
+ param->iterations, param->length);
+ for (i = param->iterations; retval == 0 && i--; /* NOP */)
+ retval = unlink_simple(dev, dev->in_pipe,
+ param->length);
+ if (retval)
+ dev_err(&intf->dev, "unlink reads failed %d, "
+ "iterations left %d\n", retval, i);
+ break;
+ case 12:
+ if (dev->out_pipe == 0 || !param->length)
+ break;
+ retval = 0;
+ dev_info(&intf->dev, "TEST 12: unlink %d writes of %d\n",
+ param->iterations, param->length);
+ for (i = param->iterations; retval == 0 && i--; /* NOP */)
+ retval = unlink_simple(dev, dev->out_pipe,
+ param->length);
+ if (retval)
+ dev_err(&intf->dev, "unlink writes failed %d, "
+ "iterations left %d\n", retval, i);
+ break;
+
+ /* ep halt tests */
+ case 13:
+ if (dev->out_pipe == 0 && dev->in_pipe == 0)
+ break;
+ retval = 0;
+ dev_info(&intf->dev, "TEST 13: set/clear %d halts\n",
+ param->iterations);
+ for (i = param->iterations; retval == 0 && i--; /* NOP */)
+ retval = halt_simple(dev);
+
+ if (retval)
+ ERROR(dev, "halts failed, iterations left %d\n", i);
+ break;
+
+ /* control write tests */
+ case 14:
+ if (!dev->info->ctrl_out)
+ break;
+ dev_info(&intf->dev, "TEST 14: %d ep0out, %d..%d vary %d\n",
+ param->iterations,
+ realworld ? 1 : 0, param->length,
+ param->vary);
+ retval = ctrl_out(dev, param->iterations,
+ param->length, param->vary, 0);
+ break;
+
+ /* iso write tests */
+ case 15:
+ if (dev->out_iso_pipe == 0 || param->sglen == 0)
+ break;
+ dev_info(&intf->dev,
+ "TEST 15: write %d iso, %d entries of %d bytes\n",
+ param->iterations,
+ param->sglen, param->length);
+ /* FIRMWARE: iso sink */
+ retval = test_queue(dev, param,
+ dev->out_iso_pipe, dev->iso_out, 0);
+ break;
+
+ /* iso read tests */
+ case 16:
+ if (dev->in_iso_pipe == 0 || param->sglen == 0)
+ break;
+ dev_info(&intf->dev,
+ "TEST 16: read %d iso, %d entries of %d bytes\n",
+ param->iterations,
+ param->sglen, param->length);
+ /* FIRMWARE: iso source */
+ retval = test_queue(dev, param,
+ dev->in_iso_pipe, dev->iso_in, 0);
+ break;
+
+ /* FIXME scatterlist cancel (needs helper thread) */
+
+ /* Tests for bulk I/O using DMA mapping by core and odd address */
+ case 17:
+ if (dev->out_pipe == 0)
+ break;
+ dev_info(&intf->dev,
+ "TEST 17: write odd addr %d bytes %u times core map\n",
+ param->length, param->iterations);
+
+ retval = test_unaligned_bulk(
+ dev, dev->out_pipe,
+ param->length, param->iterations,
+ 0, "test17");
+ break;
+
+ case 18:
+ if (dev->in_pipe == 0)
+ break;
+ dev_info(&intf->dev,
+ "TEST 18: read odd addr %d bytes %u times core map\n",
+ param->length, param->iterations);
+
+ retval = test_unaligned_bulk(
+ dev, dev->in_pipe,
+ param->length, param->iterations,
+ 0, "test18");
+ break;
+
+ /* Tests for bulk I/O using premapped coherent buffer and odd address */
+ case 19:
+ if (dev->out_pipe == 0)
+ break;
+ dev_info(&intf->dev,
+ "TEST 19: write odd addr %d bytes %u times premapped\n",
+ param->length, param->iterations);
+
+ retval = test_unaligned_bulk(
+ dev, dev->out_pipe,
+ param->length, param->iterations,
+ URB_NO_TRANSFER_DMA_MAP, "test19");
+ break;
+
+ case 20:
+ if (dev->in_pipe == 0)
+ break;
+ dev_info(&intf->dev,
+ "TEST 20: read odd addr %d bytes %u times premapped\n",
+ param->length, param->iterations);
+
+ retval = test_unaligned_bulk(
+ dev, dev->in_pipe,
+ param->length, param->iterations,
+ URB_NO_TRANSFER_DMA_MAP, "test20");
+ break;
+
+ /* control write tests with unaligned buffer */
+ case 21:
+ if (!dev->info->ctrl_out)
+ break;
+ dev_info(&intf->dev,
+ "TEST 21: %d ep0out odd addr, %d..%d vary %d\n",
+ param->iterations,
+ realworld ? 1 : 0, param->length,
+ param->vary);
+ retval = ctrl_out(dev, param->iterations,
+ param->length, param->vary, 1);
+ break;
+
+ /* unaligned iso tests */
+ case 22:
+ if (dev->out_iso_pipe == 0 || param->sglen == 0)
+ break;
+ dev_info(&intf->dev,
+ "TEST 22: write %d iso odd, %d entries of %d bytes\n",
+ param->iterations,
+ param->sglen, param->length);
+ retval = test_queue(dev, param,
+ dev->out_iso_pipe, dev->iso_out, 1);
+ break;
+
+ case 23:
+ if (dev->in_iso_pipe == 0 || param->sglen == 0)
+ break;
+ dev_info(&intf->dev,
+ "TEST 23: read %d iso odd, %d entries of %d bytes\n",
+ param->iterations,
+ param->sglen, param->length);
+ retval = test_queue(dev, param,
+ dev->in_iso_pipe, dev->iso_in, 1);
+ break;
+
+ /* unlink URBs from a bulk-OUT queue */
+ case 24:
+ if (dev->out_pipe == 0 || !param->length || param->sglen < 4)
+ break;
+ retval = 0;
+ dev_info(&intf->dev, "TEST 24: unlink from %d queues of "
+ "%d %d-byte writes\n",
+ param->iterations, param->sglen, param->length);
+ for (i = param->iterations; retval == 0 && i > 0; --i) {
+ retval = unlink_queued(dev, dev->out_pipe,
+ param->sglen, param->length);
+ if (retval) {
+ dev_err(&intf->dev,
+ "unlink queued writes failed %d, "
+ "iterations left %d\n", retval, i);
+ break;
+ }
+ }
+ break;
+
+ /* Simple non-queued interrupt I/O tests */
+ case 25:
+ if (dev->out_int_pipe == 0)
+ break;
+ dev_info(&intf->dev,
+ "TEST 25: write %d bytes %u times\n",
+ param->length, param->iterations);
+ urb = simple_alloc_urb(udev, dev->out_int_pipe, param->length,
+ dev->int_out->bInterval);
+ if (!urb) {
+ retval = -ENOMEM;
+ break;
+ }
+ /* FIRMWARE: interrupt sink (maybe accepts short writes) */
+ retval = simple_io(dev, urb, param->iterations, 0, 0, "test25");
+ simple_free_urb(urb);
+ break;
+ case 26:
+ if (dev->in_int_pipe == 0)
+ break;
+ dev_info(&intf->dev,
+ "TEST 26: read %d bytes %u times\n",
+ param->length, param->iterations);
+ urb = simple_alloc_urb(udev, dev->in_int_pipe, param->length,
+ dev->int_in->bInterval);
+ if (!urb) {
+ retval = -ENOMEM;
+ break;
+ }
+ /* FIRMWARE: interrupt source (maybe generates short writes) */
+ retval = simple_io(dev, urb, param->iterations, 0, 0, "test26");
+ simple_free_urb(urb);
+ break;
+ case 27:
+ /* We do performance test, so ignore data compare */
+ if (dev->out_pipe == 0 || param->sglen == 0 || pattern != 0)
+ break;
+ dev_info(&intf->dev,
+ "TEST 27: bulk write %dMbytes\n", (param->iterations *
+ param->sglen * param->length) / (1024 * 1024));
+ retval = test_queue(dev, param,
+ dev->out_pipe, NULL, 0);
+ break;
+ case 28:
+ if (dev->in_pipe == 0 || param->sglen == 0 || pattern != 0)
+ break;
+ dev_info(&intf->dev,
+ "TEST 28: bulk read %dMbytes\n", (param->iterations *
+ param->sglen * param->length) / (1024 * 1024));
+ retval = test_queue(dev, param,
+ dev->in_pipe, NULL, 0);
+ break;
+ /* Test data Toggle/seq_nr clear between bulk out transfers */
+ case 29:
+ if (dev->out_pipe == 0)
+ break;
+ retval = 0;
+ dev_info(&intf->dev, "TEST 29: Clear toggle between bulk writes %d times\n",
+ param->iterations);
+ for (i = param->iterations; retval == 0 && i > 0; --i)
+ retval = toggle_sync_simple(dev);
+
+ if (retval)
+ ERROR(dev, "toggle sync failed, iterations left %d\n",
+ i);
+ break;
+ }
+ return retval;
+}
+
+/*-------------------------------------------------------------------------*/
+
+/* We only have this one interface to user space, through usbfs.
+ * User mode code can scan usbfs to find N different devices (maybe on
+ * different busses) to use when testing, and allocate one thread per
+ * test. So discovery is simplified, and we have no device naming issues.
+ *
+ * Don't use these only as stress/load tests. Use them along with
+ * other USB bus activity: plugging, unplugging, mousing, mp3 playback,
+ * video capture, and so on. Run different tests at different times, in
+ * different sequences. Nothing here should interact with other devices,
+ * except indirectly by consuming USB bandwidth and CPU resources for test
+ * threads and request completion. But the only way to know that for sure
+ * is to test when HC queues are in use by many devices.
+ *
+ * WARNING: Because usbfs grabs udev->dev.sem before calling this ioctl(),
+ * it locks out usbcore in certain code paths. Notably, if you disconnect
+ * the device-under-test, hub_wq will wait block forever waiting for the
+ * ioctl to complete ... so that usb_disconnect() can abort the pending
+ * urbs and then call usbtest_disconnect(). To abort a test, you're best
+ * off just killing the userspace task and waiting for it to exit.
+ */
+
+static int
+usbtest_ioctl(struct usb_interface *intf, unsigned int code, void *buf)
+{
+
+ struct usbtest_dev *dev = usb_get_intfdata(intf);
+ struct usbtest_param_64 *param_64 = buf;
+ struct usbtest_param_32 temp;
+ struct usbtest_param_32 *param_32 = buf;
+ struct timespec64 start;
+ struct timespec64 end;
+ struct timespec64 duration;
+ int retval = -EOPNOTSUPP;
+
+ /* FIXME USBDEVFS_CONNECTINFO doesn't say how fast the device is. */
+
+ pattern = mod_pattern;
+
+ if (mutex_lock_interruptible(&dev->lock))
+ return -ERESTARTSYS;
+
+ /* FIXME: What if a system sleep starts while a test is running? */
+
+ /* some devices, like ez-usb default devices, need a non-default
+ * altsetting to have any active endpoints. some tests change
+ * altsettings; force a default so most tests don't need to check.
+ */
+ if (dev->info->alt >= 0) {
+ if (intf->altsetting->desc.bInterfaceNumber) {
+ retval = -ENODEV;
+ goto free_mutex;
+ }
+ retval = set_altsetting(dev, dev->info->alt);
+ if (retval) {
+ dev_err(&intf->dev,
+ "set altsetting to %d failed, %d\n",
+ dev->info->alt, retval);
+ goto free_mutex;
+ }
+ }
+
+ switch (code) {
+ case USBTEST_REQUEST_64:
+ temp.test_num = param_64->test_num;
+ temp.iterations = param_64->iterations;
+ temp.length = param_64->length;
+ temp.sglen = param_64->sglen;
+ temp.vary = param_64->vary;
+ param_32 = &temp;
+ break;
+
+ case USBTEST_REQUEST_32:
+ break;
+
+ default:
+ retval = -EOPNOTSUPP;
+ goto free_mutex;
+ }
+
+ ktime_get_ts64(&start);
+
+ retval = usbtest_do_ioctl(intf, param_32);
+ if (retval < 0)
+ goto free_mutex;
+
+ ktime_get_ts64(&end);
+
+ duration = timespec64_sub(end, start);
+
+ temp.duration_sec = duration.tv_sec;
+ temp.duration_usec = duration.tv_nsec/NSEC_PER_USEC;
+
+ switch (code) {
+ case USBTEST_REQUEST_32:
+ param_32->duration_sec = temp.duration_sec;
+ param_32->duration_usec = temp.duration_usec;
+ break;
+
+ case USBTEST_REQUEST_64:
+ param_64->duration_sec = temp.duration_sec;
+ param_64->duration_usec = temp.duration_usec;
+ break;
+ }
+
+free_mutex:
+ mutex_unlock(&dev->lock);
+ return retval;
+}
+
+/*-------------------------------------------------------------------------*/
+
+static unsigned force_interrupt;
+module_param(force_interrupt, uint, 0);
+MODULE_PARM_DESC(force_interrupt, "0 = test default; else interrupt");
+
+#ifdef GENERIC
+static unsigned short vendor;
+module_param(vendor, ushort, 0);
+MODULE_PARM_DESC(vendor, "vendor code (from usb-if)");
+
+static unsigned short product;
+module_param(product, ushort, 0);
+MODULE_PARM_DESC(product, "product code (from vendor)");
+#endif
+
+static int
+usbtest_probe(struct usb_interface *intf, const struct usb_device_id *id)
+{
+ struct usb_device *udev;
+ struct usbtest_dev *dev;
+ struct usbtest_info *info;
+ char *rtest, *wtest;
+ char *irtest, *iwtest;
+ char *intrtest, *intwtest;
+
+ udev = interface_to_usbdev(intf);
+
+#ifdef GENERIC
+ /* specify devices by module parameters? */
+ if (id->match_flags == 0) {
+ /* vendor match required, product match optional */
+ if (!vendor || le16_to_cpu(udev->descriptor.idVendor) != (u16)vendor)
+ return -ENODEV;
+ if (product && le16_to_cpu(udev->descriptor.idProduct) != (u16)product)
+ return -ENODEV;
+ dev_info(&intf->dev, "matched module params, "
+ "vend=0x%04x prod=0x%04x\n",
+ le16_to_cpu(udev->descriptor.idVendor),
+ le16_to_cpu(udev->descriptor.idProduct));
+ }
+#endif
+
+ dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+ if (!dev)
+ return -ENOMEM;
+ info = (struct usbtest_info *) id->driver_info;
+ dev->info = info;
+ mutex_init(&dev->lock);
+
+ dev->intf = intf;
+
+ /* cacheline-aligned scratch for i/o */
+ dev->buf = kmalloc(TBUF_SIZE, GFP_KERNEL);
+ if (dev->buf == NULL) {
+ kfree(dev);
+ return -ENOMEM;
+ }
+
+ /* NOTE this doesn't yet test the handful of difference that are
+ * visible with high speed interrupts: bigger maxpacket (1K) and
+ * "high bandwidth" modes (up to 3 packets/uframe).
+ */
+ rtest = wtest = "";
+ irtest = iwtest = "";
+ intrtest = intwtest = "";
+ if (force_interrupt || udev->speed == USB_SPEED_LOW) {
+ if (info->ep_in) {
+ dev->in_pipe = usb_rcvintpipe(udev, info->ep_in);
+ rtest = " intr-in";
+ }
+ if (info->ep_out) {
+ dev->out_pipe = usb_sndintpipe(udev, info->ep_out);
+ wtest = " intr-out";
+ }
+ } else {
+ if (override_alt >= 0 || info->autoconf) {
+ int status;
+
+ status = get_endpoints(dev, intf);
+ if (status < 0) {
+ WARNING(dev, "couldn't get endpoints, %d\n",
+ status);
+ kfree(dev->buf);
+ kfree(dev);
+ return status;
+ }
+ /* may find bulk or ISO pipes */
+ } else {
+ if (info->ep_in)
+ dev->in_pipe = usb_rcvbulkpipe(udev,
+ info->ep_in);
+ if (info->ep_out)
+ dev->out_pipe = usb_sndbulkpipe(udev,
+ info->ep_out);
+ }
+ if (dev->in_pipe)
+ rtest = " bulk-in";
+ if (dev->out_pipe)
+ wtest = " bulk-out";
+ if (dev->in_iso_pipe)
+ irtest = " iso-in";
+ if (dev->out_iso_pipe)
+ iwtest = " iso-out";
+ if (dev->in_int_pipe)
+ intrtest = " int-in";
+ if (dev->out_int_pipe)
+ intwtest = " int-out";
+ }
+
+ usb_set_intfdata(intf, dev);
+ dev_info(&intf->dev, "%s\n", info->name);
+ dev_info(&intf->dev, "%s {control%s%s%s%s%s%s%s} tests%s\n",
+ usb_speed_string(udev->speed),
+ info->ctrl_out ? " in/out" : "",
+ rtest, wtest,
+ irtest, iwtest,
+ intrtest, intwtest,
+ info->alt >= 0 ? " (+alt)" : "");
+ return 0;
+}
+
+static int usbtest_suspend(struct usb_interface *intf, pm_message_t message)
+{
+ return 0;
+}
+
+static int usbtest_resume(struct usb_interface *intf)
+{
+ return 0;
+}
+
+
+static void usbtest_disconnect(struct usb_interface *intf)
+{
+ struct usbtest_dev *dev = usb_get_intfdata(intf);
+
+ usb_set_intfdata(intf, NULL);
+ dev_dbg(&intf->dev, "disconnect\n");
+ kfree(dev->buf);
+ kfree(dev);
+}
+
+/* Basic testing only needs a device that can source or sink bulk traffic.
+ * Any device can test control transfers (default with GENERIC binding).
+ *
+ * Several entries work with the default EP0 implementation that's built
+ * into EZ-USB chips. There's a default vendor ID which can be overridden
+ * by (very) small config EEPROMS, but otherwise all these devices act
+ * identically until firmware is loaded: only EP0 works. It turns out
+ * to be easy to make other endpoints work, without modifying that EP0
+ * behavior. For now, we expect that kind of firmware.
+ */
+
+/* an21xx or fx versions of ez-usb */
+static struct usbtest_info ez1_info = {
+ .name = "EZ-USB device",
+ .ep_in = 2,
+ .ep_out = 2,
+ .alt = 1,
+};
+
+/* fx2 version of ez-usb */
+static struct usbtest_info ez2_info = {
+ .name = "FX2 device",
+ .ep_in = 6,
+ .ep_out = 2,
+ .alt = 1,
+};
+
+/* ezusb family device with dedicated usb test firmware,
+ */
+static struct usbtest_info fw_info = {
+ .name = "usb test device",
+ .ep_in = 2,
+ .ep_out = 2,
+ .alt = 1,
+ .autoconf = 1, /* iso and ctrl_out need autoconf */
+ .ctrl_out = 1,
+ .iso = 1, /* iso_ep's are #8 in/out */
+};
+
+/* peripheral running Linux and 'zero.c' test firmware, or
+ * its user-mode cousin. different versions of this use
+ * different hardware with the same vendor/product codes.
+ * host side MUST rely on the endpoint descriptors.
+ */
+static struct usbtest_info gz_info = {
+ .name = "Linux gadget zero",
+ .autoconf = 1,
+ .ctrl_out = 1,
+ .iso = 1,
+ .intr = 1,
+ .alt = 0,
+};
+
+static struct usbtest_info um_info = {
+ .name = "Linux user mode test driver",
+ .autoconf = 1,
+ .alt = -1,
+};
+
+static struct usbtest_info um2_info = {
+ .name = "Linux user mode ISO test driver",
+ .autoconf = 1,
+ .iso = 1,
+ .alt = -1,
+};
+
+#ifdef IBOT2
+/* this is a nice source of high speed bulk data;
+ * uses an FX2, with firmware provided in the device
+ */
+static struct usbtest_info ibot2_info = {
+ .name = "iBOT2 webcam",
+ .ep_in = 2,
+ .alt = -1,
+};
+#endif
+
+#ifdef GENERIC
+/* we can use any device to test control traffic */
+static struct usbtest_info generic_info = {
+ .name = "Generic USB device",
+ .alt = -1,
+};
+#endif
+
+
+static const struct usb_device_id id_table[] = {
+
+ /*-------------------------------------------------------------*/
+
+ /* EZ-USB devices which download firmware to replace (or in our
+ * case augment) the default device implementation.
+ */
+
+ /* generic EZ-USB FX controller */
+ { USB_DEVICE(0x0547, 0x2235),
+ .driver_info = (unsigned long) &ez1_info,
+ },
+
+ /* CY3671 development board with EZ-USB FX */
+ { USB_DEVICE(0x0547, 0x0080),
+ .driver_info = (unsigned long) &ez1_info,
+ },
+
+ /* generic EZ-USB FX2 controller (or development board) */
+ { USB_DEVICE(0x04b4, 0x8613),
+ .driver_info = (unsigned long) &ez2_info,
+ },
+
+ /* re-enumerated usb test device firmware */
+ { USB_DEVICE(0xfff0, 0xfff0),
+ .driver_info = (unsigned long) &fw_info,
+ },
+
+ /* "Gadget Zero" firmware runs under Linux */
+ { USB_DEVICE(0x0525, 0xa4a0),
+ .driver_info = (unsigned long) &gz_info,
+ },
+
+ /* so does a user-mode variant */
+ { USB_DEVICE(0x0525, 0xa4a4),
+ .driver_info = (unsigned long) &um_info,
+ },
+
+ /* ... and a user-mode variant that talks iso */
+ { USB_DEVICE(0x0525, 0xa4a3),
+ .driver_info = (unsigned long) &um2_info,
+ },
+
+#ifdef KEYSPAN_19Qi
+ /* Keyspan 19qi uses an21xx (original EZ-USB) */
+ /* this does not coexist with the real Keyspan 19qi driver! */
+ { USB_DEVICE(0x06cd, 0x010b),
+ .driver_info = (unsigned long) &ez1_info,
+ },
+#endif
+
+ /*-------------------------------------------------------------*/
+
+#ifdef IBOT2
+ /* iBOT2 makes a nice source of high speed bulk-in data */
+ /* this does not coexist with a real iBOT2 driver! */
+ { USB_DEVICE(0x0b62, 0x0059),
+ .driver_info = (unsigned long) &ibot2_info,
+ },
+#endif
+
+ /*-------------------------------------------------------------*/
+
+#ifdef GENERIC
+ /* module params can specify devices to use for control tests */
+ { .driver_info = (unsigned long) &generic_info, },
+#endif
+
+ /*-------------------------------------------------------------*/
+
+ { }
+};
+MODULE_DEVICE_TABLE(usb, id_table);
+
+static struct usb_driver usbtest_driver = {
+ .name = "usbtest",
+ .id_table = id_table,
+ .probe = usbtest_probe,
+ .unlocked_ioctl = usbtest_ioctl,
+ .disconnect = usbtest_disconnect,
+ .suspend = usbtest_suspend,
+ .resume = usbtest_resume,
+};
+
+/*-------------------------------------------------------------------------*/
+
+static int __init usbtest_init(void)
+{
+#ifdef GENERIC
+ if (vendor)
+ pr_debug("params: vend=0x%04x prod=0x%04x\n", vendor, product);
+#endif
+ return usb_register(&usbtest_driver);
+}
+module_init(usbtest_init);
+
+static void __exit usbtest_exit(void)
+{
+ usb_deregister(&usbtest_driver);
+}
+module_exit(usbtest_exit);
+
+MODULE_DESCRIPTION("USB Core/HCD Testing Driver");
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/usb/misc/uss720.c b/drivers/usb/misc/uss720.c
new file mode 100644
index 000000000..b00d92db5
--- /dev/null
+++ b/drivers/usb/misc/uss720.c
@@ -0,0 +1,824 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*****************************************************************************/
+
+/*
+ * uss720.c -- USS720 USB Parport Cable.
+ *
+ * Copyright (C) 1999, 2005, 2010
+ * Thomas Sailer (t.sailer@alumni.ethz.ch)
+ *
+ * Based on parport_pc.c
+ *
+ * History:
+ * 0.1 04.08.1999 Created
+ * 0.2 07.08.1999 Some fixes mainly suggested by Tim Waugh
+ * Interrupt handling currently disabled because
+ * usb_request_irq crashes somewhere within ohci.c
+ * for no apparent reason (that is for me, anyway)
+ * ECP currently untested
+ * 0.3 10.08.1999 fixing merge errors
+ * 0.4 13.08.1999 Added Vendor/Product ID of Brad Hard's cable
+ * 0.5 20.09.1999 usb_control_msg wrapper used
+ * Nov01.2000 usb_device_table support by Adam J. Richter
+ * 08.04.2001 Identify version on module load. gb
+ * 0.6 02.09.2005 Fix "scheduling in interrupt" problem by making save/restore
+ * context asynchronous
+ *
+ */
+
+/*****************************************************************************/
+
+#include <linux/module.h>
+#include <linux/socket.h>
+#include <linux/parport.h>
+#include <linux/init.h>
+#include <linux/usb.h>
+#include <linux/delay.h>
+#include <linux/completion.h>
+#include <linux/kref.h>
+#include <linux/slab.h>
+#include <linux/sched/signal.h>
+
+#define DRIVER_AUTHOR "Thomas M. Sailer, t.sailer@alumni.ethz.ch"
+#define DRIVER_DESC "USB Parport Cable driver for Cables using the Lucent Technologies USS720 Chip"
+
+/* --------------------------------------------------------------------- */
+
+struct parport_uss720_private {
+ struct usb_device *usbdev;
+ struct parport *pp;
+ struct kref ref_count;
+ __u8 reg[7]; /* USB registers */
+ struct list_head asynclist;
+ spinlock_t asynclock;
+};
+
+struct uss720_async_request {
+ struct parport_uss720_private *priv;
+ struct kref ref_count;
+ struct list_head asynclist;
+ struct completion compl;
+ struct urb *urb;
+ struct usb_ctrlrequest *dr;
+ __u8 reg[7];
+};
+
+/* --------------------------------------------------------------------- */
+
+static void destroy_priv(struct kref *kref)
+{
+ struct parport_uss720_private *priv = container_of(kref, struct parport_uss720_private, ref_count);
+
+ dev_dbg(&priv->usbdev->dev, "destroying priv datastructure\n");
+ usb_put_dev(priv->usbdev);
+ priv->usbdev = NULL;
+ kfree(priv);
+}
+
+static void destroy_async(struct kref *kref)
+{
+ struct uss720_async_request *rq = container_of(kref, struct uss720_async_request, ref_count);
+ struct parport_uss720_private *priv = rq->priv;
+ unsigned long flags;
+
+ if (likely(rq->urb))
+ usb_free_urb(rq->urb);
+ kfree(rq->dr);
+ spin_lock_irqsave(&priv->asynclock, flags);
+ list_del_init(&rq->asynclist);
+ spin_unlock_irqrestore(&priv->asynclock, flags);
+ kfree(rq);
+ kref_put(&priv->ref_count, destroy_priv);
+}
+
+/* --------------------------------------------------------------------- */
+
+static void async_complete(struct urb *urb)
+{
+ struct uss720_async_request *rq;
+ struct parport *pp;
+ struct parport_uss720_private *priv;
+ int status = urb->status;
+
+ rq = urb->context;
+ priv = rq->priv;
+ pp = priv->pp;
+ if (status) {
+ dev_err(&urb->dev->dev, "async_complete: urb error %d\n",
+ status);
+ } else if (rq->dr->bRequest == 3) {
+ memcpy(priv->reg, rq->reg, sizeof(priv->reg));
+#if 0
+ dev_dbg(&priv->usbdev->dev, "async_complete regs %7ph\n",
+ priv->reg);
+#endif
+ /* if nAck interrupts are enabled and we have an interrupt, call the interrupt procedure */
+ if (rq->reg[2] & rq->reg[1] & 0x10 && pp)
+ parport_generic_irq(pp);
+ }
+ complete(&rq->compl);
+ kref_put(&rq->ref_count, destroy_async);
+}
+
+static struct uss720_async_request *submit_async_request(struct parport_uss720_private *priv,
+ __u8 request, __u8 requesttype, __u16 value, __u16 index,
+ gfp_t mem_flags)
+{
+ struct usb_device *usbdev;
+ struct uss720_async_request *rq;
+ unsigned long flags;
+ int ret;
+
+ if (!priv)
+ return NULL;
+ usbdev = priv->usbdev;
+ if (!usbdev)
+ return NULL;
+ rq = kzalloc(sizeof(struct uss720_async_request), mem_flags);
+ if (!rq)
+ return NULL;
+ kref_init(&rq->ref_count);
+ INIT_LIST_HEAD(&rq->asynclist);
+ init_completion(&rq->compl);
+ kref_get(&priv->ref_count);
+ rq->priv = priv;
+ rq->urb = usb_alloc_urb(0, mem_flags);
+ if (!rq->urb) {
+ kref_put(&rq->ref_count, destroy_async);
+ return NULL;
+ }
+ rq->dr = kmalloc(sizeof(*rq->dr), mem_flags);
+ if (!rq->dr) {
+ kref_put(&rq->ref_count, destroy_async);
+ return NULL;
+ }
+ rq->dr->bRequestType = requesttype;
+ rq->dr->bRequest = request;
+ rq->dr->wValue = cpu_to_le16(value);
+ rq->dr->wIndex = cpu_to_le16(index);
+ rq->dr->wLength = cpu_to_le16((request == 3) ? sizeof(rq->reg) : 0);
+ usb_fill_control_urb(rq->urb, usbdev, (requesttype & 0x80) ? usb_rcvctrlpipe(usbdev, 0) : usb_sndctrlpipe(usbdev, 0),
+ (unsigned char *)rq->dr,
+ (request == 3) ? rq->reg : NULL, (request == 3) ? sizeof(rq->reg) : 0, async_complete, rq);
+ /* rq->urb->transfer_flags |= URB_ASYNC_UNLINK; */
+ spin_lock_irqsave(&priv->asynclock, flags);
+ list_add_tail(&rq->asynclist, &priv->asynclist);
+ spin_unlock_irqrestore(&priv->asynclock, flags);
+ kref_get(&rq->ref_count);
+ ret = usb_submit_urb(rq->urb, mem_flags);
+ if (!ret)
+ return rq;
+ destroy_async(&rq->ref_count);
+ dev_err(&usbdev->dev, "submit_async_request submit_urb failed with %d\n", ret);
+ return NULL;
+}
+
+static unsigned int kill_all_async_requests_priv(struct parport_uss720_private *priv)
+{
+ struct uss720_async_request *rq;
+ unsigned long flags;
+ unsigned int ret = 0;
+
+ spin_lock_irqsave(&priv->asynclock, flags);
+ list_for_each_entry(rq, &priv->asynclist, asynclist) {
+ usb_unlink_urb(rq->urb);
+ ret++;
+ }
+ spin_unlock_irqrestore(&priv->asynclock, flags);
+ return ret;
+}
+
+/* --------------------------------------------------------------------- */
+
+static int get_1284_register(struct parport *pp, unsigned char reg, unsigned char *val, gfp_t mem_flags)
+{
+ struct parport_uss720_private *priv;
+ struct uss720_async_request *rq;
+ static const unsigned char regindex[9] = {
+ 4, 0, 1, 5, 5, 0, 2, 3, 6
+ };
+ int ret;
+
+ if (!pp)
+ return -EIO;
+ priv = pp->private_data;
+ rq = submit_async_request(priv, 3, 0xc0, ((unsigned int)reg) << 8, 0, mem_flags);
+ if (!rq) {
+ dev_err(&priv->usbdev->dev, "get_1284_register(%u) failed",
+ (unsigned int)reg);
+ return -EIO;
+ }
+ if (!val) {
+ kref_put(&rq->ref_count, destroy_async);
+ return 0;
+ }
+ if (wait_for_completion_timeout(&rq->compl, HZ)) {
+ ret = rq->urb->status;
+ *val = priv->reg[(reg >= 9) ? 0 : regindex[reg]];
+ if (ret)
+ printk(KERN_WARNING "get_1284_register: "
+ "usb error %d\n", ret);
+ kref_put(&rq->ref_count, destroy_async);
+ return ret;
+ }
+ printk(KERN_WARNING "get_1284_register timeout\n");
+ kill_all_async_requests_priv(priv);
+ return -EIO;
+}
+
+static int set_1284_register(struct parport *pp, unsigned char reg, unsigned char val, gfp_t mem_flags)
+{
+ struct parport_uss720_private *priv;
+ struct uss720_async_request *rq;
+
+ if (!pp)
+ return -EIO;
+ priv = pp->private_data;
+ rq = submit_async_request(priv, 4, 0x40, (((unsigned int)reg) << 8) | val, 0, mem_flags);
+ if (!rq) {
+ dev_err(&priv->usbdev->dev, "set_1284_register(%u,%u) failed",
+ (unsigned int)reg, (unsigned int)val);
+ return -EIO;
+ }
+ kref_put(&rq->ref_count, destroy_async);
+ return 0;
+}
+
+/* --------------------------------------------------------------------- */
+
+/* ECR modes */
+#define ECR_SPP 00
+#define ECR_PS2 01
+#define ECR_PPF 02
+#define ECR_ECP 03
+#define ECR_EPP 04
+
+/* Safely change the mode bits in the ECR */
+static int change_mode(struct parport *pp, int m)
+{
+ struct parport_uss720_private *priv = pp->private_data;
+ int mode;
+ __u8 reg;
+
+ if (get_1284_register(pp, 6, &reg, GFP_KERNEL))
+ return -EIO;
+ /* Bits <7:5> contain the mode. */
+ mode = (priv->reg[2] >> 5) & 0x7;
+ if (mode == m)
+ return 0;
+ /* We have to go through mode 000 or 001 */
+ if (mode > ECR_PS2 && m > ECR_PS2)
+ if (change_mode(pp, ECR_PS2))
+ return -EIO;
+
+ if (m <= ECR_PS2 && !(priv->reg[1] & 0x20)) {
+ /* This mode resets the FIFO, so we may
+ * have to wait for it to drain first. */
+ unsigned long expire = jiffies + pp->physport->cad->timeout;
+ switch (mode) {
+ case ECR_PPF: /* Parallel Port FIFO mode */
+ case ECR_ECP: /* ECP Parallel Port mode */
+ /* Poll slowly. */
+ for (;;) {
+ if (get_1284_register(pp, 6, &reg, GFP_KERNEL))
+ return -EIO;
+ if (priv->reg[2] & 0x01)
+ break;
+ if (time_after_eq (jiffies, expire))
+ /* The FIFO is stuck. */
+ return -EBUSY;
+ msleep_interruptible(10);
+ if (signal_pending (current))
+ break;
+ }
+ }
+ }
+ /* Set the mode. */
+ if (set_1284_register(pp, 6, m << 5, GFP_KERNEL))
+ return -EIO;
+ if (get_1284_register(pp, 6, &reg, GFP_KERNEL))
+ return -EIO;
+ return 0;
+}
+
+/*
+ * Clear TIMEOUT BIT in EPP MODE
+ */
+static int clear_epp_timeout(struct parport *pp)
+{
+ unsigned char stat;
+
+ if (get_1284_register(pp, 1, &stat, GFP_KERNEL))
+ return 1;
+ return stat & 1;
+}
+
+/*
+ * Access functions.
+ */
+#if 0
+static int uss720_irq(int usbstatus, void *buffer, int len, void *dev_id)
+{
+ struct parport *pp = (struct parport *)dev_id;
+ struct parport_uss720_private *priv = pp->private_data;
+
+ if (usbstatus != 0 || len < 4 || !buffer)
+ return 1;
+ memcpy(priv->reg, buffer, 4);
+ /* if nAck interrupts are enabled and we have an interrupt, call the interrupt procedure */
+ if (priv->reg[2] & priv->reg[1] & 0x10)
+ parport_generic_irq(pp);
+ return 1;
+}
+#endif
+
+static void parport_uss720_write_data(struct parport *pp, unsigned char d)
+{
+ set_1284_register(pp, 0, d, GFP_KERNEL);
+}
+
+static unsigned char parport_uss720_read_data(struct parport *pp)
+{
+ unsigned char ret;
+
+ if (get_1284_register(pp, 0, &ret, GFP_KERNEL))
+ return 0;
+ return ret;
+}
+
+static void parport_uss720_write_control(struct parport *pp, unsigned char d)
+{
+ struct parport_uss720_private *priv = pp->private_data;
+
+ d = (d & 0xf) | (priv->reg[1] & 0xf0);
+ if (set_1284_register(pp, 2, d, GFP_KERNEL))
+ return;
+ priv->reg[1] = d;
+}
+
+static unsigned char parport_uss720_read_control(struct parport *pp)
+{
+ struct parport_uss720_private *priv = pp->private_data;
+ return priv->reg[1] & 0xf; /* Use soft copy */
+}
+
+static unsigned char parport_uss720_frob_control(struct parport *pp, unsigned char mask, unsigned char val)
+{
+ struct parport_uss720_private *priv = pp->private_data;
+ unsigned char d;
+
+ mask &= 0x0f;
+ val &= 0x0f;
+ d = (priv->reg[1] & (~mask)) ^ val;
+ if (set_1284_register(pp, 2, d, GFP_ATOMIC))
+ return 0;
+ priv->reg[1] = d;
+ return d & 0xf;
+}
+
+static unsigned char parport_uss720_read_status(struct parport *pp)
+{
+ unsigned char ret;
+
+ if (get_1284_register(pp, 1, &ret, GFP_ATOMIC))
+ return 0;
+ return ret & 0xf8;
+}
+
+static void parport_uss720_disable_irq(struct parport *pp)
+{
+ struct parport_uss720_private *priv = pp->private_data;
+ unsigned char d;
+
+ d = priv->reg[1] & ~0x10;
+ if (set_1284_register(pp, 2, d, GFP_KERNEL))
+ return;
+ priv->reg[1] = d;
+}
+
+static void parport_uss720_enable_irq(struct parport *pp)
+{
+ struct parport_uss720_private *priv = pp->private_data;
+ unsigned char d;
+
+ d = priv->reg[1] | 0x10;
+ if (set_1284_register(pp, 2, d, GFP_KERNEL))
+ return;
+ priv->reg[1] = d;
+}
+
+static void parport_uss720_data_forward (struct parport *pp)
+{
+ struct parport_uss720_private *priv = pp->private_data;
+ unsigned char d;
+
+ d = priv->reg[1] & ~0x20;
+ if (set_1284_register(pp, 2, d, GFP_KERNEL))
+ return;
+ priv->reg[1] = d;
+}
+
+static void parport_uss720_data_reverse (struct parport *pp)
+{
+ struct parport_uss720_private *priv = pp->private_data;
+ unsigned char d;
+
+ d = priv->reg[1] | 0x20;
+ if (set_1284_register(pp, 2, d, GFP_KERNEL))
+ return;
+ priv->reg[1] = d;
+}
+
+static void parport_uss720_init_state(struct pardevice *dev, struct parport_state *s)
+{
+ s->u.pc.ctr = 0xc | (dev->irq_func ? 0x10 : 0x0);
+ s->u.pc.ecr = 0x24;
+}
+
+static void parport_uss720_save_state(struct parport *pp, struct parport_state *s)
+{
+ struct parport_uss720_private *priv = pp->private_data;
+
+#if 0
+ if (get_1284_register(pp, 2, NULL, GFP_ATOMIC))
+ return;
+#endif
+ s->u.pc.ctr = priv->reg[1];
+ s->u.pc.ecr = priv->reg[2];
+}
+
+static void parport_uss720_restore_state(struct parport *pp, struct parport_state *s)
+{
+ struct parport_uss720_private *priv = pp->private_data;
+
+ set_1284_register(pp, 2, s->u.pc.ctr, GFP_ATOMIC);
+ set_1284_register(pp, 6, s->u.pc.ecr, GFP_ATOMIC);
+ get_1284_register(pp, 2, NULL, GFP_ATOMIC);
+ priv->reg[1] = s->u.pc.ctr;
+ priv->reg[2] = s->u.pc.ecr;
+}
+
+static size_t parport_uss720_epp_read_data(struct parport *pp, void *buf, size_t length, int flags)
+{
+ struct parport_uss720_private *priv = pp->private_data;
+ size_t got = 0;
+
+ if (change_mode(pp, ECR_EPP))
+ return 0;
+ for (; got < length; got++) {
+ if (get_1284_register(pp, 4, (char *)buf, GFP_KERNEL))
+ break;
+ buf++;
+ if (priv->reg[0] & 0x01) {
+ clear_epp_timeout(pp);
+ break;
+ }
+ }
+ change_mode(pp, ECR_PS2);
+ return got;
+}
+
+static size_t parport_uss720_epp_write_data(struct parport *pp, const void *buf, size_t length, int flags)
+{
+#if 0
+ struct parport_uss720_private *priv = pp->private_data;
+ size_t written = 0;
+
+ if (change_mode(pp, ECR_EPP))
+ return 0;
+ for (; written < length; written++) {
+ if (set_1284_register(pp, 4, (char *)buf, GFP_KERNEL))
+ break;
+ ((char*)buf)++;
+ if (get_1284_register(pp, 1, NULL, GFP_KERNEL))
+ break;
+ if (priv->reg[0] & 0x01) {
+ clear_epp_timeout(pp);
+ break;
+ }
+ }
+ change_mode(pp, ECR_PS2);
+ return written;
+#else
+ struct parport_uss720_private *priv = pp->private_data;
+ struct usb_device *usbdev = priv->usbdev;
+ int rlen = 0;
+ int i;
+
+ if (!usbdev)
+ return 0;
+ if (change_mode(pp, ECR_EPP))
+ return 0;
+ i = usb_bulk_msg(usbdev, usb_sndbulkpipe(usbdev, 1), (void *)buf, length, &rlen, 20000);
+ if (i)
+ printk(KERN_ERR "uss720: sendbulk ep 1 buf %p len %zu rlen %u\n", buf, length, rlen);
+ change_mode(pp, ECR_PS2);
+ return rlen;
+#endif
+}
+
+static size_t parport_uss720_epp_read_addr(struct parport *pp, void *buf, size_t length, int flags)
+{
+ struct parport_uss720_private *priv = pp->private_data;
+ size_t got = 0;
+
+ if (change_mode(pp, ECR_EPP))
+ return 0;
+ for (; got < length; got++) {
+ if (get_1284_register(pp, 3, (char *)buf, GFP_KERNEL))
+ break;
+ buf++;
+ if (priv->reg[0] & 0x01) {
+ clear_epp_timeout(pp);
+ break;
+ }
+ }
+ change_mode(pp, ECR_PS2);
+ return got;
+}
+
+static size_t parport_uss720_epp_write_addr(struct parport *pp, const void *buf, size_t length, int flags)
+{
+ struct parport_uss720_private *priv = pp->private_data;
+ size_t written = 0;
+
+ if (change_mode(pp, ECR_EPP))
+ return 0;
+ for (; written < length; written++) {
+ if (set_1284_register(pp, 3, *(char *)buf, GFP_KERNEL))
+ break;
+ buf++;
+ if (get_1284_register(pp, 1, NULL, GFP_KERNEL))
+ break;
+ if (priv->reg[0] & 0x01) {
+ clear_epp_timeout(pp);
+ break;
+ }
+ }
+ change_mode(pp, ECR_PS2);
+ return written;
+}
+
+static size_t parport_uss720_ecp_write_data(struct parport *pp, const void *buffer, size_t len, int flags)
+{
+ struct parport_uss720_private *priv = pp->private_data;
+ struct usb_device *usbdev = priv->usbdev;
+ int rlen = 0;
+ int i;
+
+ if (!usbdev)
+ return 0;
+ if (change_mode(pp, ECR_ECP))
+ return 0;
+ i = usb_bulk_msg(usbdev, usb_sndbulkpipe(usbdev, 1), (void *)buffer, len, &rlen, 20000);
+ if (i)
+ printk(KERN_ERR "uss720: sendbulk ep 1 buf %p len %zu rlen %u\n", buffer, len, rlen);
+ change_mode(pp, ECR_PS2);
+ return rlen;
+}
+
+static size_t parport_uss720_ecp_read_data(struct parport *pp, void *buffer, size_t len, int flags)
+{
+ struct parport_uss720_private *priv = pp->private_data;
+ struct usb_device *usbdev = priv->usbdev;
+ int rlen = 0;
+ int i;
+
+ if (!usbdev)
+ return 0;
+ if (change_mode(pp, ECR_ECP))
+ return 0;
+ i = usb_bulk_msg(usbdev, usb_rcvbulkpipe(usbdev, 2), buffer, len, &rlen, 20000);
+ if (i)
+ printk(KERN_ERR "uss720: recvbulk ep 2 buf %p len %zu rlen %u\n", buffer, len, rlen);
+ change_mode(pp, ECR_PS2);
+ return rlen;
+}
+
+static size_t parport_uss720_ecp_write_addr(struct parport *pp, const void *buffer, size_t len, int flags)
+{
+ size_t written = 0;
+
+ if (change_mode(pp, ECR_ECP))
+ return 0;
+ for (; written < len; written++) {
+ if (set_1284_register(pp, 5, *(char *)buffer, GFP_KERNEL))
+ break;
+ buffer++;
+ }
+ change_mode(pp, ECR_PS2);
+ return written;
+}
+
+static size_t parport_uss720_write_compat(struct parport *pp, const void *buffer, size_t len, int flags)
+{
+ struct parport_uss720_private *priv = pp->private_data;
+ struct usb_device *usbdev = priv->usbdev;
+ int rlen = 0;
+ int i;
+
+ if (!usbdev)
+ return 0;
+ if (change_mode(pp, ECR_PPF))
+ return 0;
+ i = usb_bulk_msg(usbdev, usb_sndbulkpipe(usbdev, 1), (void *)buffer, len, &rlen, 20000);
+ if (i)
+ printk(KERN_ERR "uss720: sendbulk ep 1 buf %p len %zu rlen %u\n", buffer, len, rlen);
+ change_mode(pp, ECR_PS2);
+ return rlen;
+}
+
+/* --------------------------------------------------------------------- */
+
+static struct parport_operations parport_uss720_ops =
+{
+ .owner = THIS_MODULE,
+ .write_data = parport_uss720_write_data,
+ .read_data = parport_uss720_read_data,
+
+ .write_control = parport_uss720_write_control,
+ .read_control = parport_uss720_read_control,
+ .frob_control = parport_uss720_frob_control,
+
+ .read_status = parport_uss720_read_status,
+
+ .enable_irq = parport_uss720_enable_irq,
+ .disable_irq = parport_uss720_disable_irq,
+
+ .data_forward = parport_uss720_data_forward,
+ .data_reverse = parport_uss720_data_reverse,
+
+ .init_state = parport_uss720_init_state,
+ .save_state = parport_uss720_save_state,
+ .restore_state = parport_uss720_restore_state,
+
+ .epp_write_data = parport_uss720_epp_write_data,
+ .epp_read_data = parport_uss720_epp_read_data,
+ .epp_write_addr = parport_uss720_epp_write_addr,
+ .epp_read_addr = parport_uss720_epp_read_addr,
+
+ .ecp_write_data = parport_uss720_ecp_write_data,
+ .ecp_read_data = parport_uss720_ecp_read_data,
+ .ecp_write_addr = parport_uss720_ecp_write_addr,
+
+ .compat_write_data = parport_uss720_write_compat,
+ .nibble_read_data = parport_ieee1284_read_nibble,
+ .byte_read_data = parport_ieee1284_read_byte,
+};
+
+/* --------------------------------------------------------------------- */
+
+static int uss720_probe(struct usb_interface *intf,
+ const struct usb_device_id *id)
+{
+ struct usb_device *usbdev = usb_get_dev(interface_to_usbdev(intf));
+ struct usb_host_interface *interface;
+ struct usb_endpoint_descriptor *epd;
+ struct parport_uss720_private *priv;
+ struct parport *pp;
+ unsigned char reg;
+ int i;
+
+ dev_dbg(&intf->dev, "probe: vendor id 0x%x, device id 0x%x\n",
+ le16_to_cpu(usbdev->descriptor.idVendor),
+ le16_to_cpu(usbdev->descriptor.idProduct));
+
+ /* our known interfaces have 3 alternate settings */
+ if (intf->num_altsetting != 3) {
+ usb_put_dev(usbdev);
+ return -ENODEV;
+ }
+ i = usb_set_interface(usbdev, intf->altsetting->desc.bInterfaceNumber, 2);
+ dev_dbg(&intf->dev, "set interface result %d\n", i);
+
+ interface = intf->cur_altsetting;
+
+ if (interface->desc.bNumEndpoints < 3) {
+ usb_put_dev(usbdev);
+ return -ENODEV;
+ }
+
+ /*
+ * Allocate parport interface
+ */
+ priv = kzalloc(sizeof(struct parport_uss720_private), GFP_KERNEL);
+ if (!priv) {
+ usb_put_dev(usbdev);
+ return -ENOMEM;
+ }
+ priv->pp = NULL;
+ priv->usbdev = usbdev;
+ kref_init(&priv->ref_count);
+ spin_lock_init(&priv->asynclock);
+ INIT_LIST_HEAD(&priv->asynclist);
+ pp = parport_register_port(0, PARPORT_IRQ_NONE, PARPORT_DMA_NONE, &parport_uss720_ops);
+ if (!pp) {
+ printk(KERN_WARNING "uss720: could not register parport\n");
+ goto probe_abort;
+ }
+
+ priv->pp = pp;
+ pp->private_data = priv;
+ pp->modes = PARPORT_MODE_PCSPP | PARPORT_MODE_TRISTATE | PARPORT_MODE_EPP | PARPORT_MODE_ECP | PARPORT_MODE_COMPAT;
+
+ /* set the USS720 control register to manual mode, no ECP compression, enable all ints */
+ set_1284_register(pp, 7, 0x00, GFP_KERNEL);
+ set_1284_register(pp, 6, 0x30, GFP_KERNEL); /* PS/2 mode */
+ set_1284_register(pp, 2, 0x0c, GFP_KERNEL);
+ /* debugging */
+ get_1284_register(pp, 0, &reg, GFP_KERNEL);
+ dev_dbg(&intf->dev, "reg: %7ph\n", priv->reg);
+
+ i = usb_find_last_int_in_endpoint(interface, &epd);
+ if (!i) {
+ dev_dbg(&intf->dev, "epaddr %d interval %d\n",
+ epd->bEndpointAddress, epd->bInterval);
+ }
+ parport_announce_port(pp);
+
+ usb_set_intfdata(intf, pp);
+ return 0;
+
+probe_abort:
+ kill_all_async_requests_priv(priv);
+ kref_put(&priv->ref_count, destroy_priv);
+ return -ENODEV;
+}
+
+static void uss720_disconnect(struct usb_interface *intf)
+{
+ struct parport *pp = usb_get_intfdata(intf);
+ struct parport_uss720_private *priv;
+
+ dev_dbg(&intf->dev, "disconnect\n");
+ usb_set_intfdata(intf, NULL);
+ if (pp) {
+ priv = pp->private_data;
+ priv->pp = NULL;
+ dev_dbg(&intf->dev, "parport_remove_port\n");
+ parport_remove_port(pp);
+ parport_put_port(pp);
+ kill_all_async_requests_priv(priv);
+ kref_put(&priv->ref_count, destroy_priv);
+ }
+ dev_dbg(&intf->dev, "disconnect done\n");
+}
+
+/* table of cables that work through this driver */
+static const struct usb_device_id uss720_table[] = {
+ { USB_DEVICE(0x047e, 0x1001) },
+ { USB_DEVICE(0x04b8, 0x0002) },
+ { USB_DEVICE(0x04b8, 0x0003) },
+ { USB_DEVICE(0x050d, 0x0002) },
+ { USB_DEVICE(0x050d, 0x1202) },
+ { USB_DEVICE(0x0557, 0x2001) },
+ { USB_DEVICE(0x05ab, 0x0002) },
+ { USB_DEVICE(0x06c6, 0x0100) },
+ { USB_DEVICE(0x0729, 0x1284) },
+ { USB_DEVICE(0x1293, 0x0002) },
+ { } /* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE (usb, uss720_table);
+
+
+static struct usb_driver uss720_driver = {
+ .name = "uss720",
+ .probe = uss720_probe,
+ .disconnect = uss720_disconnect,
+ .id_table = uss720_table,
+};
+
+/* --------------------------------------------------------------------- */
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL");
+
+static int __init uss720_init(void)
+{
+ int retval;
+ retval = usb_register(&uss720_driver);
+ if (retval)
+ goto out;
+
+ printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_DESC "\n");
+ printk(KERN_INFO KBUILD_MODNAME ": NOTE: this is a special purpose "
+ "driver to allow nonstandard\n");
+ printk(KERN_INFO KBUILD_MODNAME ": protocols (eg. bitbang) over "
+ "USS720 usb to parallel cables\n");
+ printk(KERN_INFO KBUILD_MODNAME ": If you just want to connect to a "
+ "printer, use usblp instead\n");
+out:
+ return retval;
+}
+
+static void __exit uss720_cleanup(void)
+{
+ usb_deregister(&uss720_driver);
+}
+
+module_init(uss720_init);
+module_exit(uss720_cleanup);
+
+/* --------------------------------------------------------------------- */
diff --git a/drivers/usb/misc/yurex.c b/drivers/usb/misc/yurex.c
new file mode 100644
index 000000000..c640f98d2
--- /dev/null
+++ b/drivers/usb/misc/yurex.c
@@ -0,0 +1,530 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Driver for Meywa-Denki & KAYAC YUREX
+ *
+ * Copyright (C) 2010 Tomoki Sekiyama (tomoki.sekiyama@gmail.com)
+ */
+
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/kref.h>
+#include <linux/mutex.h>
+#include <linux/uaccess.h>
+#include <linux/usb.h>
+#include <linux/hid.h>
+
+#define DRIVER_AUTHOR "Tomoki Sekiyama"
+#define DRIVER_DESC "Driver for Meywa-Denki & KAYAC YUREX"
+
+#define YUREX_VENDOR_ID 0x0c45
+#define YUREX_PRODUCT_ID 0x1010
+
+#define CMD_ACK '!'
+#define CMD_ANIMATE 'A'
+#define CMD_COUNT 'C'
+#define CMD_LED 'L'
+#define CMD_READ 'R'
+#define CMD_SET 'S'
+#define CMD_VERSION 'V'
+#define CMD_EOF 0x0d
+#define CMD_PADDING 0xff
+
+#define YUREX_BUF_SIZE 8
+#define YUREX_WRITE_TIMEOUT (HZ*2)
+
+/* table of devices that work with this driver */
+static struct usb_device_id yurex_table[] = {
+ { USB_DEVICE(YUREX_VENDOR_ID, YUREX_PRODUCT_ID) },
+ { } /* Terminating entry */
+};
+MODULE_DEVICE_TABLE(usb, yurex_table);
+
+#ifdef CONFIG_USB_DYNAMIC_MINORS
+#define YUREX_MINOR_BASE 0
+#else
+#define YUREX_MINOR_BASE 192
+#endif
+
+/* Structure to hold all of our device specific stuff */
+struct usb_yurex {
+ struct usb_device *udev;
+ struct usb_interface *interface;
+ __u8 int_in_endpointAddr;
+ struct urb *urb; /* URB for interrupt in */
+ unsigned char *int_buffer; /* buffer for intterupt in */
+ struct urb *cntl_urb; /* URB for control msg */
+ struct usb_ctrlrequest *cntl_req; /* req for control msg */
+ unsigned char *cntl_buffer; /* buffer for control msg */
+
+ struct kref kref;
+ struct mutex io_mutex;
+ unsigned long disconnected:1;
+ struct fasync_struct *async_queue;
+ wait_queue_head_t waitq;
+
+ spinlock_t lock;
+ __s64 bbu; /* BBU from device */
+};
+#define to_yurex_dev(d) container_of(d, struct usb_yurex, kref)
+
+static struct usb_driver yurex_driver;
+static const struct file_operations yurex_fops;
+
+
+static void yurex_control_callback(struct urb *urb)
+{
+ struct usb_yurex *dev = urb->context;
+ int status = urb->status;
+
+ if (status) {
+ dev_err(&urb->dev->dev, "%s - control failed: %d\n",
+ __func__, status);
+ wake_up_interruptible(&dev->waitq);
+ return;
+ }
+ /* on success, sender woken up by CMD_ACK int in, or timeout */
+}
+
+static void yurex_delete(struct kref *kref)
+{
+ struct usb_yurex *dev = to_yurex_dev(kref);
+
+ dev_dbg(&dev->interface->dev, "%s\n", __func__);
+
+ if (dev->cntl_urb) {
+ usb_kill_urb(dev->cntl_urb);
+ kfree(dev->cntl_req);
+ usb_free_coherent(dev->udev, YUREX_BUF_SIZE,
+ dev->cntl_buffer, dev->cntl_urb->transfer_dma);
+ usb_free_urb(dev->cntl_urb);
+ }
+ if (dev->urb) {
+ usb_kill_urb(dev->urb);
+ usb_free_coherent(dev->udev, YUREX_BUF_SIZE,
+ dev->int_buffer, dev->urb->transfer_dma);
+ usb_free_urb(dev->urb);
+ }
+ usb_put_intf(dev->interface);
+ usb_put_dev(dev->udev);
+ kfree(dev);
+}
+
+/*
+ * usb class driver info in order to get a minor number from the usb core,
+ * and to have the device registered with the driver core
+ */
+static struct usb_class_driver yurex_class = {
+ .name = "yurex%d",
+ .fops = &yurex_fops,
+ .minor_base = YUREX_MINOR_BASE,
+};
+
+static void yurex_interrupt(struct urb *urb)
+{
+ struct usb_yurex *dev = urb->context;
+ unsigned char *buf = dev->int_buffer;
+ int status = urb->status;
+ unsigned long flags;
+ int retval, i;
+
+ switch (status) {
+ case 0: /*success*/
+ break;
+ /* The device is terminated or messed up, give up */
+ case -EOVERFLOW:
+ dev_err(&dev->interface->dev,
+ "%s - overflow with length %d, actual length is %d\n",
+ __func__, YUREX_BUF_SIZE, dev->urb->actual_length);
+ return;
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ case -EILSEQ:
+ case -EPROTO:
+ case -ETIME:
+ return;
+ default:
+ dev_err(&dev->interface->dev,
+ "%s - unknown status received: %d\n", __func__, status);
+ return;
+ }
+
+ /* handle received message */
+ switch (buf[0]) {
+ case CMD_COUNT:
+ case CMD_READ:
+ if (buf[6] == CMD_EOF) {
+ spin_lock_irqsave(&dev->lock, flags);
+ dev->bbu = 0;
+ for (i = 1; i < 6; i++) {
+ dev->bbu += buf[i];
+ if (i != 5)
+ dev->bbu <<= 8;
+ }
+ dev_dbg(&dev->interface->dev, "%s count: %lld\n",
+ __func__, dev->bbu);
+ spin_unlock_irqrestore(&dev->lock, flags);
+
+ kill_fasync(&dev->async_queue, SIGIO, POLL_IN);
+ }
+ else
+ dev_dbg(&dev->interface->dev,
+ "data format error - no EOF\n");
+ break;
+ case CMD_ACK:
+ dev_dbg(&dev->interface->dev, "%s ack: %c\n",
+ __func__, buf[1]);
+ wake_up_interruptible(&dev->waitq);
+ break;
+ }
+
+ retval = usb_submit_urb(dev->urb, GFP_ATOMIC);
+ if (retval) {
+ dev_err(&dev->interface->dev, "%s - usb_submit_urb failed: %d\n",
+ __func__, retval);
+ }
+}
+
+static int yurex_probe(struct usb_interface *interface, const struct usb_device_id *id)
+{
+ struct usb_yurex *dev;
+ struct usb_host_interface *iface_desc;
+ struct usb_endpoint_descriptor *endpoint;
+ int retval = -ENOMEM;
+ DEFINE_WAIT(wait);
+ int res;
+
+ /* allocate memory for our device state and initialize it */
+ dev = kzalloc(sizeof(*dev), GFP_KERNEL);
+ if (!dev)
+ goto error;
+ kref_init(&dev->kref);
+ mutex_init(&dev->io_mutex);
+ spin_lock_init(&dev->lock);
+ init_waitqueue_head(&dev->waitq);
+
+ dev->udev = usb_get_dev(interface_to_usbdev(interface));
+ dev->interface = usb_get_intf(interface);
+
+ /* set up the endpoint information */
+ iface_desc = interface->cur_altsetting;
+ res = usb_find_int_in_endpoint(iface_desc, &endpoint);
+ if (res) {
+ dev_err(&interface->dev, "Could not find endpoints\n");
+ retval = res;
+ goto error;
+ }
+
+ dev->int_in_endpointAddr = endpoint->bEndpointAddress;
+
+ /* allocate control URB */
+ dev->cntl_urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!dev->cntl_urb)
+ goto error;
+
+ /* allocate buffer for control req */
+ dev->cntl_req = kmalloc(YUREX_BUF_SIZE, GFP_KERNEL);
+ if (!dev->cntl_req)
+ goto error;
+
+ /* allocate buffer for control msg */
+ dev->cntl_buffer = usb_alloc_coherent(dev->udev, YUREX_BUF_SIZE,
+ GFP_KERNEL,
+ &dev->cntl_urb->transfer_dma);
+ if (!dev->cntl_buffer) {
+ dev_err(&interface->dev, "Could not allocate cntl_buffer\n");
+ goto error;
+ }
+
+ /* configure control URB */
+ dev->cntl_req->bRequestType = USB_DIR_OUT | USB_TYPE_CLASS |
+ USB_RECIP_INTERFACE;
+ dev->cntl_req->bRequest = HID_REQ_SET_REPORT;
+ dev->cntl_req->wValue = cpu_to_le16((HID_OUTPUT_REPORT + 1) << 8);
+ dev->cntl_req->wIndex = cpu_to_le16(iface_desc->desc.bInterfaceNumber);
+ dev->cntl_req->wLength = cpu_to_le16(YUREX_BUF_SIZE);
+
+ usb_fill_control_urb(dev->cntl_urb, dev->udev,
+ usb_sndctrlpipe(dev->udev, 0),
+ (void *)dev->cntl_req, dev->cntl_buffer,
+ YUREX_BUF_SIZE, yurex_control_callback, dev);
+ dev->cntl_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+
+
+ /* allocate interrupt URB */
+ dev->urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!dev->urb)
+ goto error;
+
+ /* allocate buffer for interrupt in */
+ dev->int_buffer = usb_alloc_coherent(dev->udev, YUREX_BUF_SIZE,
+ GFP_KERNEL, &dev->urb->transfer_dma);
+ if (!dev->int_buffer) {
+ dev_err(&interface->dev, "Could not allocate int_buffer\n");
+ goto error;
+ }
+
+ /* configure interrupt URB */
+ usb_fill_int_urb(dev->urb, dev->udev,
+ usb_rcvintpipe(dev->udev, dev->int_in_endpointAddr),
+ dev->int_buffer, YUREX_BUF_SIZE, yurex_interrupt,
+ dev, 1);
+ dev->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+ if (usb_submit_urb(dev->urb, GFP_KERNEL)) {
+ retval = -EIO;
+ dev_err(&interface->dev, "Could not submitting URB\n");
+ goto error;
+ }
+
+ /* save our data pointer in this interface device */
+ usb_set_intfdata(interface, dev);
+ dev->bbu = -1;
+
+ /* we can register the device now, as it is ready */
+ retval = usb_register_dev(interface, &yurex_class);
+ if (retval) {
+ dev_err(&interface->dev,
+ "Not able to get a minor for this device.\n");
+ usb_set_intfdata(interface, NULL);
+ goto error;
+ }
+
+ dev_info(&interface->dev,
+ "USB YUREX device now attached to Yurex #%d\n",
+ interface->minor);
+
+ return 0;
+
+error:
+ if (dev)
+ /* this frees allocated memory */
+ kref_put(&dev->kref, yurex_delete);
+ return retval;
+}
+
+static void yurex_disconnect(struct usb_interface *interface)
+{
+ struct usb_yurex *dev;
+ int minor = interface->minor;
+
+ dev = usb_get_intfdata(interface);
+ usb_set_intfdata(interface, NULL);
+
+ /* give back our minor */
+ usb_deregister_dev(interface, &yurex_class);
+
+ /* prevent more I/O from starting */
+ usb_poison_urb(dev->urb);
+ usb_poison_urb(dev->cntl_urb);
+ mutex_lock(&dev->io_mutex);
+ dev->disconnected = 1;
+ mutex_unlock(&dev->io_mutex);
+
+ /* wakeup waiters */
+ kill_fasync(&dev->async_queue, SIGIO, POLL_IN);
+ wake_up_interruptible(&dev->waitq);
+
+ /* decrement our usage count */
+ kref_put(&dev->kref, yurex_delete);
+
+ dev_info(&interface->dev, "USB YUREX #%d now disconnected\n", minor);
+}
+
+static struct usb_driver yurex_driver = {
+ .name = "yurex",
+ .probe = yurex_probe,
+ .disconnect = yurex_disconnect,
+ .id_table = yurex_table,
+};
+
+
+static int yurex_fasync(int fd, struct file *file, int on)
+{
+ struct usb_yurex *dev;
+
+ dev = file->private_data;
+ return fasync_helper(fd, file, on, &dev->async_queue);
+}
+
+static int yurex_open(struct inode *inode, struct file *file)
+{
+ struct usb_yurex *dev;
+ struct usb_interface *interface;
+ int subminor;
+ int retval = 0;
+
+ subminor = iminor(inode);
+
+ interface = usb_find_interface(&yurex_driver, subminor);
+ if (!interface) {
+ printk(KERN_ERR "%s - error, can't find device for minor %d",
+ __func__, subminor);
+ retval = -ENODEV;
+ goto exit;
+ }
+
+ dev = usb_get_intfdata(interface);
+ if (!dev) {
+ retval = -ENODEV;
+ goto exit;
+ }
+
+ /* increment our usage count for the device */
+ kref_get(&dev->kref);
+
+ /* save our object in the file's private structure */
+ mutex_lock(&dev->io_mutex);
+ file->private_data = dev;
+ mutex_unlock(&dev->io_mutex);
+
+exit:
+ return retval;
+}
+
+static int yurex_release(struct inode *inode, struct file *file)
+{
+ struct usb_yurex *dev;
+
+ dev = file->private_data;
+ if (dev == NULL)
+ return -ENODEV;
+
+ /* decrement the count on our device */
+ kref_put(&dev->kref, yurex_delete);
+ return 0;
+}
+
+static ssize_t yurex_read(struct file *file, char __user *buffer, size_t count,
+ loff_t *ppos)
+{
+ struct usb_yurex *dev;
+ int len = 0;
+ char in_buffer[20];
+ unsigned long flags;
+
+ dev = file->private_data;
+
+ mutex_lock(&dev->io_mutex);
+ if (dev->disconnected) { /* already disconnected */
+ mutex_unlock(&dev->io_mutex);
+ return -ENODEV;
+ }
+
+ spin_lock_irqsave(&dev->lock, flags);
+ len = snprintf(in_buffer, 20, "%lld\n", dev->bbu);
+ spin_unlock_irqrestore(&dev->lock, flags);
+ mutex_unlock(&dev->io_mutex);
+
+ if (WARN_ON_ONCE(len >= sizeof(in_buffer)))
+ return -EIO;
+
+ return simple_read_from_buffer(buffer, count, ppos, in_buffer, len);
+}
+
+static ssize_t yurex_write(struct file *file, const char __user *user_buffer,
+ size_t count, loff_t *ppos)
+{
+ struct usb_yurex *dev;
+ int i, set = 0, retval = 0;
+ char buffer[16 + 1];
+ char *data = buffer;
+ unsigned long long c, c2 = 0;
+ signed long timeout = 0;
+ DEFINE_WAIT(wait);
+
+ count = min(sizeof(buffer) - 1, count);
+ dev = file->private_data;
+
+ /* verify that we actually have some data to write */
+ if (count == 0)
+ goto error;
+
+ mutex_lock(&dev->io_mutex);
+ if (dev->disconnected) { /* already disconnected */
+ mutex_unlock(&dev->io_mutex);
+ retval = -ENODEV;
+ goto error;
+ }
+
+ if (copy_from_user(buffer, user_buffer, count)) {
+ mutex_unlock(&dev->io_mutex);
+ retval = -EFAULT;
+ goto error;
+ }
+ buffer[count] = 0;
+ memset(dev->cntl_buffer, CMD_PADDING, YUREX_BUF_SIZE);
+
+ switch (buffer[0]) {
+ case CMD_ANIMATE:
+ case CMD_LED:
+ dev->cntl_buffer[0] = buffer[0];
+ dev->cntl_buffer[1] = buffer[1];
+ dev->cntl_buffer[2] = CMD_EOF;
+ break;
+ case CMD_READ:
+ case CMD_VERSION:
+ dev->cntl_buffer[0] = buffer[0];
+ dev->cntl_buffer[1] = 0x00;
+ dev->cntl_buffer[2] = CMD_EOF;
+ break;
+ case CMD_SET:
+ data++;
+ fallthrough;
+ case '0' ... '9':
+ set = 1;
+ c = c2 = simple_strtoull(data, NULL, 0);
+ dev->cntl_buffer[0] = CMD_SET;
+ for (i = 1; i < 6; i++) {
+ dev->cntl_buffer[i] = (c>>32) & 0xff;
+ c <<= 8;
+ }
+ buffer[6] = CMD_EOF;
+ break;
+ default:
+ mutex_unlock(&dev->io_mutex);
+ return -EINVAL;
+ }
+
+ /* send the data as the control msg */
+ prepare_to_wait(&dev->waitq, &wait, TASK_INTERRUPTIBLE);
+ dev_dbg(&dev->interface->dev, "%s - submit %c\n", __func__,
+ dev->cntl_buffer[0]);
+ retval = usb_submit_urb(dev->cntl_urb, GFP_ATOMIC);
+ if (retval >= 0)
+ timeout = schedule_timeout(YUREX_WRITE_TIMEOUT);
+ finish_wait(&dev->waitq, &wait);
+
+ /* make sure URB is idle after timeout or (spurious) CMD_ACK */
+ usb_kill_urb(dev->cntl_urb);
+
+ mutex_unlock(&dev->io_mutex);
+
+ if (retval < 0) {
+ dev_err(&dev->interface->dev,
+ "%s - failed to send bulk msg, error %d\n",
+ __func__, retval);
+ goto error;
+ }
+ if (set && timeout)
+ dev->bbu = c2;
+ return timeout ? count : -EIO;
+
+error:
+ return retval;
+}
+
+static const struct file_operations yurex_fops = {
+ .owner = THIS_MODULE,
+ .read = yurex_read,
+ .write = yurex_write,
+ .open = yurex_open,
+ .release = yurex_release,
+ .fasync = yurex_fasync,
+ .llseek = default_llseek,
+};
+
+module_usb_driver(yurex_driver);
+
+MODULE_LICENSE("GPL");