summaryrefslogtreecommitdiffstats
path: root/drivers/usb/misc
diff options
context:
space:
mode:
authorNiklas Cassel <cassel@kernel.org>2026-09-16 15:00:32 +0200
committerNiklas Cassel <cassel@kernel.org>2026-09-16 16:18:00 +0200
commit8d836581f9b1f57ceaa2b47654754ef1260b410b (patch)
tree1dbd4d0dfb99c2e238356cbe947e5b0be3e3a571 /drivers/usb/misc
downloadlinux-stable-8d836581f9b1f57ceaa2b47654754ef1260b410b.tar.gz
linux-stable-8d836581f9b1f57ceaa2b47654754ef1260b410b.zip
ata: libata-scsi: fix ata_dsm_trim_pages() kernel-docgrafted
make htmldocs fails with: Documentation/driver-api/libata:604: ./drivers/ata/libata-scsi.c:2301: ERROR: Unexpected indentation. [docutils] The Return: section of ata_dsm_trim_pages() ends its first sentence with a colon and continues with an indented bullet list. reStructuredText requires a blank line before an indented block, so docutils chokes on the list. Simply adding the missing blank line does not work either: Return: is a kernel-doc "special section", which is terminated by the first blank line, so the bullet list would end up in the Description section, detached from the sentence introducing it. Spell the two bounds out as prose instead, so that the Return: section stays self-contained. Documentation-only change. Fixes: e64e6b5dc867 ("ata: libata-scsi: scale DSM TRIM payload by MAX PAGES PER DSM COMMAND") Reported-by: Thomas Huth <thuth@redhat.com> Closes: https://lore.kernel.org/linux-ide/b0a0b8e8-cc4f-4c7b-8bc5-fee0712d405a@redhat.com/ Reviewed-by: Damien Le Moal <dlemoal@kernel.org> Reviewed-by: Thomas Huth <thuth@redhat.com> Link: https://lore.kernel.org/r/20260916130031.29990-2-cassel@kernel.org Signed-off-by: Niklas Cassel <cassel@kernel.org>
Diffstat (limited to 'drivers/usb/misc')
-rw-r--r--drivers/usb/misc/Kconfig361
-rw-r--r--drivers/usb/misc/Makefile37
-rw-r--r--drivers/usb/misc/adutux.c796
-rw-r--r--drivers/usb/misc/apple-mfi-fastcharge.c257
-rw-r--r--drivers/usb/misc/appledisplay.c354
-rw-r--r--drivers/usb/misc/brcmstb-usb-pinmap.c356
-rw-r--r--drivers/usb/misc/chaoskey.c605
-rw-r--r--drivers/usb/misc/cypress_cy7c63.c265
-rw-r--r--drivers/usb/misc/cytherm.c355
-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.c152
-rw-r--r--drivers/usb/misc/idmouse.c406
-rw-r--r--drivers/usb/misc/iowarrior.c926
-rw-r--r--drivers/usb/misc/isight_firmware.c132
-rw-r--r--drivers/usb/misc/ldusb.c794
-rw-r--r--drivers/usb/misc/legousbtower.c880
-rw-r--r--drivers/usb/misc/lvstest.c476
-rw-r--r--drivers/usb/misc/onboard_usb_dev.c732
-rw-r--r--drivers/usb/misc/onboard_usb_dev.h176
-rw-r--r--drivers/usb/misc/onboard_usb_dev_pdevs.c144
-rw-r--r--drivers/usb/misc/qcom_eud.c266
-rw-r--r--drivers/usb/misc/sisusbvga/Kconfig14
-rw-r--r--drivers/usb/misc/sisusbvga/Makefile6
-rw-r--r--drivers/usb/misc/sisusbvga/sisusb.h278
-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/sisusbvga/sisusbvga.c2969
-rw-r--r--drivers/usb/misc/trancevibrator.c127
-rw-r--r--drivers/usb/misc/usb-ljca.c906
-rw-r--r--drivers/usb/misc/usb251xb.c834
-rw-r--r--drivers/usb/misc/usb3503.c460
-rw-r--r--drivers/usb/misc/usb4604.c163
-rw-r--r--drivers/usb/misc/usbio.c756
-rw-r--r--drivers/usb/misc/usblcd.c450
-rw-r--r--drivers/usb/misc/usbsevseg.c395
-rw-r--r--drivers/usb/misc/usbtest.c3080
-rw-r--r--drivers/usb/misc/uss720.c839
-rw-r--r--drivers/usb/misc/yurex.c537
40 files changed, 21855 insertions, 0 deletions
diff --git a/drivers/usb/misc/Kconfig b/drivers/usb/misc/Kconfig
new file mode 100644
index 000000000..91b62acb5
--- /dev/null
+++ b/drivers/usb/misc/Kconfig
@@ -0,0 +1,361 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# USB Miscellaneous driver configuration
+#
+comment "USB Miscellaneous drivers"
+
+config USB_USS720
+ tristate "USS720 parport driver"
+ depends on PARPORT
+ select PARPORT_NOT_PC
+ help
+ This driver is for USB parallel port adapters that use the Lucent
+ Technologies USS-720 chip. These cables are plugged into your USB
+ port and provide USB compatibility to peripherals designed with
+ parallel port interfaces.
+
+ The chip has two modes: automatic mode and manual mode. In automatic
+ mode, it looks to the computer like a standard USB printer. Only
+ printers may be connected to the USS-720 in this mode. The generic
+ USB printer driver ("USB Printer support", above) may be used in
+ that mode, and you can say N here if you want to use the chip only
+ in this mode.
+
+ Manual mode is not limited to printers, any parallel port
+ device should work. This driver utilizes manual mode.
+ Note however that some operations are three orders of magnitude
+ slower than on a PCI/ISA Parallel Port, so timing critical
+ applications might not work.
+
+ Say Y here if you own an USS-720 USB->Parport cable and intend to
+ connect anything other than a printer to it.
+
+ To compile this driver as a module, choose M here: the
+ module will be called uss720.
+
+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_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 "Qualcomm Embedded USB Debugger(EUD) Driver"
+ depends on ARCH_QCOM || COMPILE_TEST
+ select QCOM_SCM
+ 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.
+
+config USB_LJCA
+ tristate "Intel La Jolla Cove Adapter support"
+ select AUXILIARY_BUS
+ depends on USB && ACPI
+ help
+ This adds support for Intel La Jolla Cove USB-I2C/SPI/GPIO
+ Master Adapter (LJCA). Additional drivers such as I2C_LJCA,
+ GPIO_LJCA and SPI_LJCA must be enabled in order to use the
+ functionality of the device.
+
+ This driver can also be built as a module. If so, the module
+ will be called usb-ljca.
+
+config USB_USBIO
+ tristate "Intel USBIO Bridge support"
+ depends on USB && ACPI
+ depends on X86 || COMPILE_TEST
+ select AUXILIARY_BUS
+ help
+ This adds support for Intel USBIO drivers.
+ This enables the USBIO bridge driver module in charge to talk
+ to the USB device. Additional drivers such as GPIO_USBIO and
+ I2C_USBIO must be enabled in order to use the device's full
+ functionality.
+
+ This driver can also be built as a module. If so, the module
+ will be called usbio.
+
+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 <https://www.usb.org/sites/default/files/EHSET_v1.01%281%29.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_DEV
+ tristate "Onboard USB device support"
+ depends on OF
+ help
+ Say Y here if you want to support discrete onboard USB devices
+ 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 device is the
+ Realtek RTS5411 hub.
+
+ 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_dev.
+
+config USB_ONBOARD_DEV_USB5744
+ bool "Onboard USB Microchip usb5744 hub with SMBus support"
+ depends on (USB_ONBOARD_DEV && I2C=y) || (USB_ONBOARD_DEV=m && I2C=m)
+ help
+ Say Y here if you want to support onboard USB Microchip usb5744
+ hub that requires SMBus initialization.
+
+ This options enables usb5744 i2c default initialization sequence
+ during hub start-up configuration stage. It is must to enable this
+ option on AMD Kria KR260 Robotics Starter Kit as this hub is
+ connected to USB-SD converter which mounts the root filesystem.
diff --git a/drivers/usb/misc/Makefile b/drivers/usb/misc/Makefile
new file mode 100644
index 000000000..494ab0377
--- /dev/null
+++ b/drivers/usb/misc/Makefile
@@ -0,0 +1,37 @@
+# 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_APPLE_MFI_FASTCHARGE) += apple-mfi-fastcharge.o
+obj-$(CONFIG_USB_LJCA) += usb-ljca.o
+obj-$(CONFIG_USB_USBIO) += usbio.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_DEV) += onboard_usb_dev.o
diff --git a/drivers/usb/misc/adutux.c b/drivers/usb/misc/adutux.c
new file mode 100644
index 000000000..369d0d2ee
--- /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_obj(struct adu_device);
+ 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..af266e19f
--- /dev/null
+++ b/drivers/usb/misc/apple-mfi-fastcharge.c
@@ -0,0 +1,257 @@
+// 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;
+ struct power_supply_desc battery_desc;
+ 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_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;
+ char *battery_name;
+ int err;
+
+ if (!mfi_fc_match(udev))
+ return -ENODEV;
+
+ mfi = kzalloc_obj(struct mfi_device);
+ if (!mfi)
+ return -ENOMEM;
+
+ battery_name = kasprintf(GFP_KERNEL, "apple_mfi_fastcharge_%d-%d",
+ udev->bus->busnum, udev->devnum);
+ if (!battery_name) {
+ err = -ENOMEM;
+ goto err_free_mfi;
+ }
+
+ mfi->battery_desc = apple_mfi_fc_desc;
+ mfi->battery_desc.name = battery_name;
+
+ battery_cfg.drv_data = mfi;
+
+ mfi->charge_type = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
+ mfi->battery = power_supply_register(&udev->dev,
+ &mfi->battery_desc,
+ &battery_cfg);
+ if (IS_ERR(mfi->battery)) {
+ dev_err(&udev->dev, "Can't register battery\n");
+ err = PTR_ERR(mfi->battery);
+ goto err_free_name;
+ }
+
+ mfi->udev = udev;
+ dev_set_drvdata(&udev->dev, mfi);
+
+ return 0;
+
+err_free_name:
+ kfree(battery_name);
+err_free_mfi:
+ kfree(mfi);
+ return err;
+}
+
+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);
+ kfree(mfi->battery_desc.name);
+ dev_set_drvdata(&udev->dev, NULL);
+ 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..16883592f
--- /dev/null
+++ b/drivers/usb/misc/appledisplay.c
@@ -0,0 +1,354 @@
+// 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/hid.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 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;
+ /*
+ * there is a window during which no device
+ * is registered
+ */
+ if (pdata->bd )
+ 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),
+ HID_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),
+ HID_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 backlight_device *backlight;
+ 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_obj(struct appledisplay);
+ 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;
+ backlight = backlight_device_register(bl_name, NULL, pdata,
+ &appledisplay_bl_data, &props);
+ if (IS_ERR(backlight)) {
+ dev_err(&iface->dev, "Backlight registration failed\n");
+ retval = PTR_ERR(backlight);
+ goto error;
+ }
+ pdata->bd = backlight;
+
+ /* 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..1ce885e41
--- /dev/null
+++ b/drivers/usb/misc/brcmstb-usb-pinmap.c
@@ -0,0 +1,356 @@
+// 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" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, brcmstb_usb_pinmap_of_match);
+
+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..9c06f7775
--- /dev/null
+++ b/drivers/usb/misc/chaoskey.c
@@ -0,0 +1,605 @@
+// 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);
+
+static DEFINE_MUTEX(chaoskey_list_lock);
+
+#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_obj(struct chaoskey);
+
+ 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_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(&chaoskey_list_lock);
+ 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);
+
+ mutex_unlock(&chaoskey_list_lock);
+ usb_dbg(interface, "disconnect done");
+}
+
+static int chaoskey_open(struct inode *inode, struct file *file)
+{
+ struct chaoskey *dev;
+ struct usb_interface *interface;
+ int rv = 0;
+
+ /* 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(&chaoskey_list_lock);
+ mutex_lock(&dev->lock);
+ if (dev->present)
+ ++dev->open;
+ else
+ rv = -ENODEV;
+ mutex_unlock(&dev->lock);
+ mutex_unlock(&chaoskey_list_lock);
+
+ return rv;
+}
+
+static int chaoskey_release(struct inode *inode, struct file *file)
+{
+ struct chaoskey *dev = file->private_data;
+ struct usb_interface *interface;
+ int rv = 0;
+
+ if (dev == NULL)
+ return -ENODEV;
+
+ interface = dev->interface;
+
+ usb_dbg(interface, "release");
+
+ mutex_lock(&chaoskey_list_lock);
+ 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);
+ rv = -ENODEV;
+ goto bail;
+ }
+
+ --dev->open;
+
+ if (!dev->present) {
+ if (dev->open == 0) {
+ mutex_unlock(&dev->lock);
+ chaoskey_free(dev);
+ goto destruction;
+ }
+ }
+bail:
+ mutex_unlock(&dev->lock);
+destruction:
+ mutex_unlock(&chaoskey_list_lock);
+ return rv;
+}
+
+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);
+
+ if (file->f_flags & O_NONBLOCK) {
+ result = mutex_trylock(&dev->lock);
+ if (result == 0) {
+ result = -EAGAIN;
+ goto bail;
+ } else {
+ result = 0;
+ }
+ } else {
+ 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..ecc81c93c
--- /dev/null
+++ b/drivers/usb/misc/cypress_cy7c63.c
@@ -0,0 +1,265 @@
+// 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);
+ /* we must not process garbage */
+ if (retval < 2) {
+ if (retval >= 0)
+ retval = -EIO;
+ goto err_buf;
+ }
+
+ /* 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;
+ }
+
+err_buf:
+ 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);
+ if (result < 0)
+ return 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;
+ int retval = -ENOMEM;
+
+ /* allocate memory for our device state and initialize it */
+ dev = kzalloc_obj(*dev);
+ if (!dev)
+ goto error_mem;
+
+ dev->udev = 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);
+
+ 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..b183df982
--- /dev/null
+++ b/drivers/usb/misc/cytherm.c
@@ -0,0 +1,355 @@
+// 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;
+ int retval = -ENOMEM;
+
+ dev = kzalloc_obj(struct usb_cytherm);
+ if (!dev)
+ goto error_mem;
+
+ dev->udev = 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);
+
+ 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..7ab4856ab
--- /dev/null
+++ b/drivers/usb/misc/ezusb.c
@@ -0,0 +1,152 @@
+// 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_DESCRIPTION("EZUSB device support");
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/misc/idmouse.c b/drivers/usb/misc/idmouse.c
new file mode 100644
index 000000000..3e37adf2b
--- /dev/null
+++ b/drivers/usb/misc/idmouse.c
@@ -0,0 +1,406 @@
+// 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 kref kref;
+
+ 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 kref *kref)
+{
+ struct usb_idmouse *dev = container_of(kref, struct usb_idmouse, kref);
+
+ 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;
+
+ kref_get(&dev->kref);
+
+ /* 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;
+ mutex_unlock(&dev->lock);
+
+ kref_put(&dev->kref, idmouse_delete);
+
+ 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_obj(*dev);
+ if (dev == NULL)
+ return -ENOMEM;
+
+ kref_init(&dev->kref);
+ 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");
+ goto err_put_kref;
+ }
+
+ 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) {
+ result = -ENOMEM;
+ goto err_put_kref;
+ }
+
+ /* 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");
+ goto err_put_kref;
+ }
+
+ /* be noisy */
+ dev_info(&interface->dev,"%s now attached\n",DRIVER_DESC);
+
+ return 0;
+
+err_put_kref:
+ kref_put(&dev->kref, idmouse_delete);
+
+ return result;
+}
+
+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;
+
+ mutex_unlock(&dev->lock);
+
+ kref_put(&dev->kref, idmouse_delete);
+
+ 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..de2b236ef
--- /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/hid.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 kref kref;
+ 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 */
+ 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 */
+/*--------------*/
+
+//#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),
+ HID_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
+
+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),
+ HID_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 */
+ kfree(urb->transfer_buffer);
+ /* 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 kref *kref)
+{
+ struct iowarrior *dev = container_of(kref, struct iowarrior, kref);
+
+ 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;
+ int retval;
+
+ dev = file->private_data;
+
+ if (file->f_flags & O_NONBLOCK) {
+ retval = mutex_trylock(&dev->mutex);
+ if (!retval)
+ return -EAGAIN;
+ } else {
+ retval = mutex_lock_interruptible(&dev->mutex);
+ if (retval)
+ return -ERESTARTSYS;
+ }
+
+ /* verify that the device wasn't unplugged */
+ if (!dev->present) {
+ retval = -ENODEV;
+ goto exit;
+ }
+
+ /* read count must be packet size (+ time stamp) */
+ if ((count != dev->report_size)
+ && (count != (dev->report_size + 1))) {
+ retval = -EINVAL;
+ goto exit;
+ }
+
+ /* 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) {
+ retval = -EAGAIN;
+ goto exit;
+ }
+ 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
+ retval = -ERESTART;
+ goto exit;
+ }
+ if (!dev->present) {
+ //The device was unplugged
+ retval = -ENODEV;
+ goto exit;
+ }
+ if (read_idx == -1) {
+ // Can this happen ???
+ retval = 0;
+ goto exit;
+ }
+ }
+ }
+
+ offset = read_idx * (dev->report_size + 1);
+ if (copy_to_user(buffer, dev->read_queue + offset, count)) {
+ retval = -EFAULT;
+ goto exit;
+ }
+ } while (atomic_read(&dev->overflow_flag));
+
+ read_idx = ++read_idx == MAX_INTERRUPT_BUFFER ? 0 : read_idx;
+ atomic_set(&dev->read_idx, read_idx);
+ mutex_unlock(&dev->mutex);
+ return count;
+
+exit:
+ mutex_unlock(&dev->mutex);
+ return retval;
+}
+
+/*
+ * 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;
+ char *buf = NULL; /* for IOW24 and IOW56 we need a buffer */
+ struct urb *int_out_urb = NULL;
+
+ dev = file->private_data;
+
+ retval = mutex_lock_interruptible(&dev->mutex);
+ if (retval < 0)
+ return -EINTR;
+
+ /* verify that the device wasn't unplugged */
+ if (!dev->present) {
+ retval = -ENODEV;
+ goto exit;
+ }
+ /* 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 = kmalloc(dev->report_size, GFP_KERNEL);
+ 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);
+ 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:
+ kfree(buf);
+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;
+ }
+
+ retval = 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;
+
+ kref_get(&dev->kref);
+
+ /* 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;
+
+ dev = file->private_data;
+ if (!dev)
+ return -ENODEV;
+
+ /* lock our device */
+ mutex_lock(&dev->mutex);
+ dev->opened = 0; /* we're closing now */
+
+ if (dev->present) {
+ usb_kill_urb(dev->int_in_urb);
+ wake_up_interruptible(&dev->read_wait);
+ wake_up_interruptible(&dev->write_wait);
+ }
+ mutex_unlock(&dev->mutex);
+
+ kref_put(&dev->kref, iowarrior_delete);
+
+ return 0;
+}
+
+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(const 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;
+ int minor;
+
+ /* allocate memory for our device state and initialize it */
+ dev = kzalloc_obj(struct iowarrior);
+ if (!dev)
+ return retval;
+
+ kref_init(&dev->kref);
+ 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;
+ }
+
+ 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, minor - IOWARRIOR_MINOR_BASE);
+ return retval;
+
+error:
+ kref_put(&dev->kref, iowarrior_delete);
+
+ 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);
+
+ usb_deregister_dev(interface, &iowarrior_class);
+
+ mutex_lock(&dev->mutex);
+
+ /* prevent device read, write and ioctl */
+ dev->present = 0;
+
+ /* write urbs are not stopped on close() so kill unconditionally */
+ usb_kill_anchored_urbs(&dev->submitted);
+
+ if (dev->opened) {
+ usb_kill_urb(dev->int_in_urb);
+ wake_up_interruptible(&dev->read_wait);
+ wake_up_interruptible(&dev->write_wait);
+ }
+
+ mutex_unlock(&dev->mutex);
+
+ kref_put(&dev->kref, iowarrior_delete);
+}
+
+/* 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..2d9af74a6
--- /dev/null
+++ b/drivers/usb/misc/isight_firmware.c
@@ -0,0 +1,132 @@
+// 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_DESCRIPTION("iSight firmware loading support");
+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..71132a15e
--- /dev/null
+++ b/drivers/usb/misc/ldusb.c
@@ -0,0 +1,794 @@
+// 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 kref kref;
+ 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 kref *kref)
+{
+ struct ld_usb *dev = container_of(kref, struct ld_usb, kref);
+
+ /* 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;
+ }
+
+ kref_get(&dev->kref);
+
+ /* 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->disconnected)
+ goto unlock_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);
+ kref_put(&dev->kref, ld_usb_delete);
+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,
+};
+
+/*
+ * 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_obj(*dev);
+ if (!dev)
+ goto exit;
+
+ kref_init(&dev->kref);
+ 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:
+ kref_put(&dev->kref, ld_usb_delete);
+
+ 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);
+
+ dev->disconnected = 1;
+
+ if (dev->open_count) {
+ /* wake up pollers */
+ wake_up_interruptible_all(&dev->read_wait);
+ wake_up_interruptible_all(&dev->write_wait);
+ }
+
+ mutex_unlock(&dev->mutex);
+
+ kref_put(&dev->kref, ld_usb_delete);
+
+ 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..18dd4115b
--- /dev/null
+++ b/drivers/usb/misc/legousbtower.c
@@ -0,0 +1,880 @@
+// 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 kref kref;
+ 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 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(const 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 kref *kref)
+{
+ struct lego_usb_tower *dev = container_of(kref, struct lego_usb_tower, kref);
+
+ /* 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;
+
+ kref_get(&dev->kref);
+
+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)
+ goto out_unlock;
+
+ /* 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;
+
+out_unlock:
+ mutex_unlock(&dev->lock);
+ kref_put(&dev->kref, tower_delete);
+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_obj(*dev);
+ if (!dev)
+ goto exit;
+
+ kref_init(&dev->kref);
+ 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:
+ kref_put(&dev->kref, tower_delete);
+ 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);
+
+ dev->disconnected = 1;
+
+ if (dev->open_count) {
+ /* wake up pollers */
+ wake_up_interruptible_all(&dev->read_wait);
+ wake_up_interruptible_all(&dev->write_wait);
+ }
+
+ mutex_unlock(&dev->lock);
+
+ kref_put(&dev->kref, tower_delete);
+
+ 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..9b6e10177
--- /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_obj(*descriptor);
+ 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_dev.c b/drivers/usb/misc/onboard_usb_dev.c
new file mode 100644
index 000000000..11508ed4d
--- /dev/null
+++ b/drivers/usb/misc/onboard_usb_dev.c
@@ -0,0 +1,732 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for onboard USB devices
+ *
+ * Copyright (c) 2022, Google LLC
+ */
+
+#include <linux/clk.h>
+#include <linux/device.h>
+#include <linux/export.h>
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/init.h>
+#include <linux/i2c.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_dev.h>
+#include <linux/workqueue.h>
+
+#include "onboard_usb_dev.h"
+
+/* USB5744 register offset and mask */
+#define USB5744_CMD_ATTACH 0xAA
+#define USB5744_CMD_ATTACH_LSB 0x56
+#define USB5744_CMD_CREG_ACCESS 0x99
+#define USB5744_CMD_CREG_ACCESS_LSB 0x37
+#define USB5744_CREG_MEM_ADDR 0x00
+#define USB5744_CREG_MEM_RD_ADDR 0x04
+#define USB5744_CREG_WRITE 0x00
+#define USB5744_CREG_READ 0x01
+#define USB5744_CREG_RUNTIMEFLAGS2 0x411D
+#define USB5744_CREG_BYPASS_UDC_SUSPEND BIT(3)
+
+static void onboard_dev_attach_usb_driver(struct work_struct *work);
+
+static struct usb_device_driver onboard_dev_usbdev_driver;
+static DECLARE_WORK(attach_usb_driver_work, onboard_dev_attach_usb_driver);
+
+/************************** Platform driver **************************/
+
+struct usbdev_node {
+ struct usb_device *udev;
+ struct list_head list;
+};
+
+struct onboard_dev {
+ struct regulator_bulk_data supplies[MAX_SUPPLIES];
+ struct device *dev;
+ const struct onboard_dev_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;
+ struct clk *clk;
+};
+
+static int onboard_dev_get_regulators(struct onboard_dev *onboard_dev)
+{
+ const char * const *supply_names = onboard_dev->pdata->supply_names;
+ unsigned int num_supplies = onboard_dev->pdata->num_supplies;
+ struct device *dev = onboard_dev->dev;
+ unsigned int i;
+ int err;
+
+ if (num_supplies > MAX_SUPPLIES)
+ return dev_err_probe(dev, -EINVAL, "max %d supplies supported!\n",
+ MAX_SUPPLIES);
+
+ for (i = 0; i < num_supplies; i++)
+ onboard_dev->supplies[i].supply = supply_names[i];
+
+ err = devm_regulator_bulk_get(dev, num_supplies, onboard_dev->supplies);
+ if (err)
+ dev_err(dev, "Failed to get regulator supplies: %pe\n",
+ ERR_PTR(err));
+
+ return err;
+}
+
+static int onboard_dev_power_on(struct onboard_dev *onboard_dev)
+{
+ int err;
+
+ err = clk_prepare_enable(onboard_dev->clk);
+ if (err) {
+ dev_err(onboard_dev->dev, "failed to enable clock: %pe\n",
+ ERR_PTR(err));
+ return err;
+ }
+
+ err = regulator_bulk_enable(onboard_dev->pdata->num_supplies,
+ onboard_dev->supplies);
+ if (err) {
+ dev_err(onboard_dev->dev, "failed to enable supplies: %pe\n",
+ ERR_PTR(err));
+ goto disable_clk;
+ }
+
+ fsleep(onboard_dev->pdata->reset_us);
+ gpiod_set_value_cansleep(onboard_dev->reset_gpio, 0);
+ fsleep(onboard_dev->pdata->power_on_delay_us);
+
+ onboard_dev->is_powered_on = true;
+
+ return 0;
+
+disable_clk:
+ clk_disable_unprepare(onboard_dev->clk);
+ return err;
+}
+
+static int onboard_dev_power_off(struct onboard_dev *onboard_dev)
+{
+ int err;
+
+ gpiod_set_value_cansleep(onboard_dev->reset_gpio, 1);
+
+ err = regulator_bulk_disable(onboard_dev->pdata->num_supplies,
+ onboard_dev->supplies);
+ if (err) {
+ dev_err(onboard_dev->dev, "failed to disable supplies: %pe\n",
+ ERR_PTR(err));
+ return err;
+ }
+
+ clk_disable_unprepare(onboard_dev->clk);
+
+ onboard_dev->is_powered_on = false;
+
+ return 0;
+}
+
+static int __maybe_unused onboard_dev_suspend(struct device *dev)
+{
+ struct onboard_dev *onboard_dev = dev_get_drvdata(dev);
+ struct usbdev_node *node;
+ bool power_off = true;
+
+ if (onboard_dev->always_powered_in_suspend)
+ return 0;
+
+ mutex_lock(&onboard_dev->lock);
+
+ list_for_each_entry(node, &onboard_dev->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(&onboard_dev->lock);
+
+ if (!power_off)
+ return 0;
+
+ return onboard_dev_power_off(onboard_dev);
+}
+
+static int __maybe_unused onboard_dev_resume(struct device *dev)
+{
+ struct onboard_dev *onboard_dev = dev_get_drvdata(dev);
+
+ if (onboard_dev->is_powered_on)
+ return 0;
+
+ return onboard_dev_power_on(onboard_dev);
+}
+
+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_dev_add_usbdev(struct onboard_dev *onboard_dev,
+ struct usb_device *udev)
+{
+ struct usbdev_node *node;
+ char link_name[64];
+ int err;
+
+ mutex_lock(&onboard_dev->lock);
+
+ if (onboard_dev->going_away) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ node = kzalloc_obj(*node);
+ if (!node) {
+ err = -ENOMEM;
+ goto error;
+ }
+
+ node->udev = udev;
+
+ list_add(&node->list, &onboard_dev->udev_list);
+
+ mutex_unlock(&onboard_dev->lock);
+
+ get_udev_link_name(udev, link_name, sizeof(link_name));
+ WARN_ON(sysfs_create_link(&onboard_dev->dev->kobj, &udev->dev.kobj,
+ link_name));
+
+ return 0;
+
+error:
+ mutex_unlock(&onboard_dev->lock);
+
+ return err;
+}
+
+static void onboard_dev_remove_usbdev(struct onboard_dev *onboard_dev,
+ 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(&onboard_dev->dev->kobj, link_name);
+
+ mutex_lock(&onboard_dev->lock);
+
+ list_for_each_entry(node, &onboard_dev->udev_list, list) {
+ if (node->udev == udev) {
+ list_del(&node->list);
+ kfree(node);
+ break;
+ }
+ }
+
+ mutex_unlock(&onboard_dev->lock);
+}
+
+static ssize_t always_powered_in_suspend_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ const struct onboard_dev *onboard_dev = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%d\n", onboard_dev->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_dev *onboard_dev = dev_get_drvdata(dev);
+ bool val;
+ int ret;
+
+ ret = kstrtobool(buf, &val);
+ if (ret < 0)
+ return ret;
+
+ onboard_dev->always_powered_in_suspend = val;
+
+ return count;
+}
+static DEVICE_ATTR_RW(always_powered_in_suspend);
+
+static struct attribute *onboard_dev_attrs[] = {
+ &dev_attr_always_powered_in_suspend.attr,
+ NULL,
+};
+
+static umode_t onboard_dev_attrs_are_visible(struct kobject *kobj,
+ struct attribute *attr,
+ int n)
+{
+ struct device *dev = kobj_to_dev(kobj);
+ struct onboard_dev *onboard_dev = dev_get_drvdata(dev);
+
+ if (attr == &dev_attr_always_powered_in_suspend.attr &&
+ !onboard_dev->pdata->is_hub)
+ return 0;
+
+ return attr->mode;
+}
+
+static const struct attribute_group onboard_dev_group = {
+ .is_visible = onboard_dev_attrs_are_visible,
+ .attrs = onboard_dev_attrs,
+};
+__ATTRIBUTE_GROUPS(onboard_dev);
+
+
+static void onboard_dev_attach_usb_driver(struct work_struct *work)
+{
+ int err;
+
+ err = driver_attach(&onboard_dev_usbdev_driver.driver);
+ if (err)
+ pr_err("Failed to attach USB driver: %pe\n", ERR_PTR(err));
+}
+
+#if IS_ENABLED(CONFIG_USB_ONBOARD_DEV_USB5744)
+static int onboard_dev_5744_i2c_read_byte(struct i2c_client *client, u16 addr, u8 *data)
+{
+ struct i2c_msg msg[2];
+ u8 rd_buf[3];
+ int ret;
+
+ u8 wr_buf[7] = {0, USB5744_CREG_MEM_ADDR, 4,
+ USB5744_CREG_READ, 1,
+ addr >> 8 & 0xff,
+ addr & 0xff};
+ msg[0].addr = client->addr;
+ msg[0].flags = 0;
+ msg[0].len = sizeof(wr_buf);
+ msg[0].buf = wr_buf;
+
+ ret = i2c_transfer(client->adapter, msg, 1);
+ if (ret < 0)
+ return ret;
+
+ wr_buf[0] = USB5744_CMD_CREG_ACCESS;
+ wr_buf[1] = USB5744_CMD_CREG_ACCESS_LSB;
+ wr_buf[2] = 0;
+ msg[0].len = 3;
+
+ ret = i2c_transfer(client->adapter, msg, 1);
+ if (ret < 0)
+ return ret;
+
+ wr_buf[0] = 0;
+ wr_buf[1] = USB5744_CREG_MEM_RD_ADDR;
+ msg[0].len = 2;
+
+ msg[1].addr = client->addr;
+ msg[1].flags = I2C_M_RD;
+ msg[1].len = 2;
+ msg[1].buf = rd_buf;
+
+ ret = i2c_transfer(client->adapter, msg, 2);
+ if (ret < 0)
+ return ret;
+ *data = rd_buf[1];
+
+ return 0;
+}
+
+static int onboard_dev_5744_i2c_write_byte(struct i2c_client *client, u16 addr, u8 data)
+{
+ struct i2c_msg msg[2];
+ int ret;
+
+ u8 wr_buf[8] = {0, USB5744_CREG_MEM_ADDR, 5,
+ USB5744_CREG_WRITE, 1,
+ addr >> 8 & 0xff,
+ addr & 0xff,
+ data};
+ msg[0].addr = client->addr;
+ msg[0].flags = 0;
+ msg[0].len = sizeof(wr_buf);
+ msg[0].buf = wr_buf;
+
+ ret = i2c_transfer(client->adapter, msg, 1);
+ if (ret < 0)
+ return ret;
+
+ msg[0].len = 3;
+ wr_buf[0] = USB5744_CMD_CREG_ACCESS;
+ wr_buf[1] = USB5744_CMD_CREG_ACCESS_LSB;
+ wr_buf[2] = 0;
+
+ ret = i2c_transfer(client->adapter, msg, 1);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int onboard_dev_5744_i2c_init(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ int ret;
+ u8 reg;
+
+ /*
+ * Set BYPASS_UDC_SUSPEND bit to ensure MCU is always enabled
+ * and ready to respond to SMBus runtime commands.
+ * The command writes 5 bytes to memory and single data byte in
+ * configuration register.
+ */
+ ret = onboard_dev_5744_i2c_read_byte(client,
+ USB5744_CREG_RUNTIMEFLAGS2, &reg);
+ if (ret)
+ return dev_err_probe(dev, ret, "CREG_RUNTIMEFLAGS2 read failed\n");
+
+ reg |= USB5744_CREG_BYPASS_UDC_SUSPEND;
+ ret = onboard_dev_5744_i2c_write_byte(client,
+ USB5744_CREG_RUNTIMEFLAGS2, reg);
+ if (ret)
+ return dev_err_probe(dev, ret, "BYPASS_UDC_SUSPEND bit configuration failed\n");
+
+ /* Send SMBus command to boot hub. */
+ ret = i2c_smbus_write_word_data(client, USB5744_CMD_ATTACH,
+ USB5744_CMD_ATTACH_LSB);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "USB Attach with SMBus command failed\n");
+
+ return ret;
+}
+#else
+static int onboard_dev_5744_i2c_init(struct i2c_client *client)
+{
+ return -ENODEV;
+}
+#endif
+
+static int onboard_dev_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct onboard_dev *onboard_dev;
+ struct device_node *i2c_node;
+ int err;
+
+ onboard_dev = devm_kzalloc(dev, sizeof(*onboard_dev), GFP_KERNEL);
+ if (!onboard_dev)
+ return -ENOMEM;
+
+ onboard_dev->pdata = device_get_match_data(dev);
+ if (!onboard_dev->pdata)
+ return -EINVAL;
+
+ if (!onboard_dev->pdata->is_hub)
+ onboard_dev->always_powered_in_suspend = true;
+
+ onboard_dev->dev = dev;
+
+ err = onboard_dev_get_regulators(onboard_dev);
+ if (err)
+ return err;
+
+ onboard_dev->clk = devm_clk_get_optional(dev, NULL);
+ if (IS_ERR(onboard_dev->clk))
+ return dev_err_probe(dev, PTR_ERR(onboard_dev->clk),
+ "failed to get clock\n");
+
+ onboard_dev->reset_gpio = devm_gpiod_get_optional(dev, "reset",
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(onboard_dev->reset_gpio))
+ return dev_err_probe(dev, PTR_ERR(onboard_dev->reset_gpio),
+ "failed to get reset GPIO\n");
+
+ mutex_init(&onboard_dev->lock);
+ INIT_LIST_HEAD(&onboard_dev->udev_list);
+
+ dev_set_drvdata(dev, onboard_dev);
+
+ err = onboard_dev_power_on(onboard_dev);
+ if (err)
+ return err;
+
+ i2c_node = of_parse_phandle(pdev->dev.of_node, "i2c-bus", 0);
+ if (i2c_node) {
+ struct i2c_client *client = NULL;
+
+#if IS_ENABLED(CONFIG_USB_ONBOARD_DEV_USB5744)
+ client = of_find_i2c_device_by_node(i2c_node);
+#endif
+ of_node_put(i2c_node);
+
+ if (!client) {
+ err = -EPROBE_DEFER;
+ goto err_power_off;
+ }
+
+ if (of_device_is_compatible(pdev->dev.of_node, "usb424,2744") ||
+ of_device_is_compatible(pdev->dev.of_node, "usb424,5744")) {
+ err = onboard_dev_5744_i2c_init(client);
+ onboard_dev->always_powered_in_suspend = true;
+ }
+
+ put_device(&client->dev);
+ if (err < 0)
+ goto err_power_off;
+ }
+
+ /*
+ * The USB driver might have been detached from the USB devices by
+ * onboard_dev_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;
+
+err_power_off:
+ onboard_dev_power_off(onboard_dev);
+ return err;
+}
+
+static void onboard_dev_remove(struct platform_device *pdev)
+{
+ struct onboard_dev *onboard_dev = dev_get_drvdata(&pdev->dev);
+ struct usbdev_node *node;
+ struct usb_device *udev;
+
+ onboard_dev->going_away = true;
+
+ mutex_lock(&onboard_dev->lock);
+
+ /* unbind the USB devices to avoid dangling references to this device */
+ while (!list_empty(&onboard_dev->udev_list)) {
+ node = list_first_entry(&onboard_dev->udev_list,
+ struct usbdev_node, list);
+ udev = node->udev;
+
+ /*
+ * Unbinding the driver will call onboard_dev_remove_usbdev(),
+ * which acquires onboard_dev->lock. We must release the lock
+ * first.
+ */
+ get_device(&udev->dev);
+ mutex_unlock(&onboard_dev->lock);
+ device_release_driver(&udev->dev);
+ put_device(&udev->dev);
+ mutex_lock(&onboard_dev->lock);
+ }
+
+ mutex_unlock(&onboard_dev->lock);
+
+ onboard_dev_power_off(onboard_dev);
+}
+
+MODULE_DEVICE_TABLE(of, onboard_dev_match);
+
+static const struct dev_pm_ops __maybe_unused onboard_dev_pm_ops = {
+ SET_LATE_SYSTEM_SLEEP_PM_OPS(onboard_dev_suspend, onboard_dev_resume)
+};
+
+static struct platform_driver onboard_dev_driver = {
+ .probe = onboard_dev_probe,
+ .remove = onboard_dev_remove,
+
+ .driver = {
+ .name = "onboard-usb-dev",
+ .of_match_table = onboard_dev_match,
+ .pm = pm_ptr(&onboard_dev_pm_ops),
+ .dev_groups = onboard_dev_groups,
+ },
+};
+
+/************************** USB driver **************************/
+
+#define VENDOR_ID_BISON 0x5986
+#define VENDOR_ID_CORECHIPS 0x3431
+#define VENDOR_ID_CYPRESS 0x04b4
+#define VENDOR_ID_GENESYS 0x05e3
+#define VENDOR_ID_MICROCHIP 0x0424
+#define VENDOR_ID_PARADE 0x1da0
+#define VENDOR_ID_REALTEK 0x0bda
+#define VENDOR_ID_TERMINUS 0x1a40
+#define VENDOR_ID_TI 0x0451
+#define VENDOR_ID_VIA 0x2109
+#define VENDOR_ID_XMOS 0x20B1
+
+/*
+ * Returns the onboard_dev platform device that is associated with the USB
+ * device passed as parameter.
+ */
+static struct onboard_dev *_find_onboard_dev(struct device *dev)
+{
+ struct platform_device *pdev;
+ struct device_node *np;
+ struct onboard_dev *onboard_dev;
+
+ 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);
+ }
+
+ onboard_dev = dev_get_drvdata(&pdev->dev);
+ put_device(&pdev->dev);
+
+ /*
+ * The presence of drvdata 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 (!onboard_dev)
+ return ERR_PTR(-EPROBE_DEFER);
+
+ return onboard_dev;
+}
+
+static bool onboard_dev_usbdev_match(struct usb_device *udev)
+{
+ /* Onboard devices using this driver must have a device tree node */
+ return !!udev->dev.of_node;
+}
+
+static int onboard_dev_usbdev_probe(struct usb_device *udev)
+{
+ struct device *dev = &udev->dev;
+ struct onboard_dev *onboard_dev;
+ int err;
+
+ onboard_dev = _find_onboard_dev(dev);
+ if (IS_ERR(onboard_dev))
+ return PTR_ERR(onboard_dev);
+
+ dev_set_drvdata(dev, onboard_dev);
+
+ err = onboard_dev_add_usbdev(onboard_dev, udev);
+ if (err)
+ return err;
+
+ return 0;
+}
+
+static void onboard_dev_usbdev_disconnect(struct usb_device *udev)
+{
+ struct onboard_dev *onboard_dev = dev_get_drvdata(&udev->dev);
+
+ onboard_dev_remove_usbdev(onboard_dev, udev);
+}
+
+static const struct usb_device_id onboard_dev_id_table[] = {
+ { USB_DEVICE(VENDOR_ID_BISON, 0x1198) }, /* Bison Electronics Inc. Integrated Camera */
+ { USB_DEVICE(VENDOR_ID_CORECHIPS, 0x6241) }, /* SL6341 2.0 HUB */
+ { USB_DEVICE(VENDOR_ID_CORECHIPS, 0x6341) }, /* SL6341 3.0 HUB */
+ { USB_DEVICE(VENDOR_ID_CYPRESS, 0x6500) }, /* CYUSB330x 3.0 HUB */
+ { USB_DEVICE(VENDOR_ID_CYPRESS, 0x6502) }, /* CYUSB330x 2.0 HUB */
+ { USB_DEVICE(VENDOR_ID_CYPRESS, 0x6503) }, /* CYUSB33{0,1}x 2.0 HUB, Vendor Mode */
+ { USB_DEVICE(VENDOR_ID_CYPRESS, 0x6504) }, /* CYUSB331x 3.0 HUB */
+ { USB_DEVICE(VENDOR_ID_CYPRESS, 0x6506) }, /* CYUSB331x 2.0 HUB */
+ { USB_DEVICE(VENDOR_ID_CYPRESS, 0x6507) }, /* CYUSB332x 2.0 HUB, Vendor Mode */
+ { USB_DEVICE(VENDOR_ID_CYPRESS, 0x6508) }, /* CYUSB332x 3.0 HUB */
+ { USB_DEVICE(VENDOR_ID_CYPRESS, 0x650a) }, /* CYUSB332x 2.0 HUB */
+ { USB_DEVICE(VENDOR_ID_CYPRESS, 0x6570) }, /* CY7C6563x 2.0 HUB */
+ { USB_DEVICE(VENDOR_ID_GENESYS, 0x0608) }, /* Genesys Logic GL850G USB 2.0 HUB */
+ { USB_DEVICE(VENDOR_ID_GENESYS, 0x0610) }, /* Genesys Logic GL852G USB 2.0 HUB */
+ { USB_DEVICE(VENDOR_ID_GENESYS, 0x0620) }, /* Genesys Logic GL3523 USB 3.1 HUB */
+ { USB_DEVICE(VENDOR_ID_GENESYS, 0x0625) }, /* Genesys Logic GL3590 USB 3.2 HUB */
+ { USB_DEVICE(VENDOR_ID_MICROCHIP, 0x2412) }, /* USB2412 USB 2.0 HUB */
+ { USB_DEVICE(VENDOR_ID_MICROCHIP, 0x2514) }, /* USB2514B USB 2.0 HUB */
+ { USB_DEVICE(VENDOR_ID_MICROCHIP, 0x2517) }, /* USB2517 USB 2.0 HUB */
+ { USB_DEVICE(VENDOR_ID_MICROCHIP, 0x2744) }, /* USB5744 USB 2.0 HUB */
+ { USB_DEVICE(VENDOR_ID_MICROCHIP, 0x5744) }, /* USB5744 USB 3.0 HUB */
+ { USB_DEVICE(VENDOR_ID_PARADE, 0x5511) }, /* PS5511 USB 3.2 */
+ { USB_DEVICE(VENDOR_ID_PARADE, 0x55a1) }, /* PS5511 USB 2.0 */
+ { USB_DEVICE(VENDOR_ID_REALTEK, 0x0411) }, /* RTS5411 USB 3.1 HUB */
+ { USB_DEVICE(VENDOR_ID_REALTEK, 0x5411) }, /* RTS5411 USB 2.1 HUB */
+ { USB_DEVICE(VENDOR_ID_REALTEK, 0x0414) }, /* RTS5414 USB 3.2 HUB */
+ { USB_DEVICE(VENDOR_ID_REALTEK, 0x5414) }, /* RTS5414 USB 2.1 HUB */
+ { USB_DEVICE(VENDOR_ID_REALTEK, 0x0179) }, /* RTL8188ETV 2.4GHz WiFi */
+ { USB_DEVICE(VENDOR_ID_TERMINUS, 0x0101) }, /* Terminus FE1.1s 2.0 HUB */
+ { USB_DEVICE(VENDOR_ID_TI, 0x8025) }, /* TI USB8020B 3.0 HUB */
+ { USB_DEVICE(VENDOR_ID_TI, 0x8027) }, /* TI USB8020B 2.0 HUB */
+ { USB_DEVICE(VENDOR_ID_TI, 0x8140) }, /* TI USB8041 3.0 HUB */
+ { USB_DEVICE(VENDOR_ID_TI, 0x8142) }, /* TI USB8041 2.0 HUB */
+ { USB_DEVICE(VENDOR_ID_TI, 0x8440) }, /* TI USB8044 3.0 HUB */
+ { USB_DEVICE(VENDOR_ID_TI, 0x8442) }, /* TI USB8044 2.0 HUB */
+ { USB_DEVICE(VENDOR_ID_VIA, 0x0817) }, /* VIA VL817 3.1 HUB */
+ { USB_DEVICE(VENDOR_ID_VIA, 0x2817) }, /* VIA VL817 2.0 HUB */
+ { USB_DEVICE(VENDOR_ID_XMOS, 0x0013) }, /* XMOS XVF3500 Voice Processor */
+ {}
+};
+MODULE_DEVICE_TABLE(usb, onboard_dev_id_table);
+
+static struct usb_device_driver onboard_dev_usbdev_driver = {
+ .name = "onboard-usb-dev",
+ .match = onboard_dev_usbdev_match,
+ .probe = onboard_dev_usbdev_probe,
+ .disconnect = onboard_dev_usbdev_disconnect,
+ .generic_subclass = 1,
+ .supports_autosuspend = 1,
+ .id_table = onboard_dev_id_table,
+};
+
+static int __init onboard_dev_init(void)
+{
+ int ret;
+
+ ret = usb_register_device_driver(&onboard_dev_usbdev_driver, THIS_MODULE);
+ if (ret)
+ return ret;
+
+ ret = platform_driver_register(&onboard_dev_driver);
+ if (ret)
+ usb_deregister_device_driver(&onboard_dev_usbdev_driver);
+
+ return ret;
+}
+module_init(onboard_dev_init);
+
+static void __exit onboard_dev_exit(void)
+{
+ usb_deregister_device_driver(&onboard_dev_usbdev_driver);
+ platform_driver_unregister(&onboard_dev_driver);
+
+ cancel_work_sync(&attach_usb_driver_work);
+}
+module_exit(onboard_dev_exit);
+
+MODULE_AUTHOR("Matthias Kaehlcke <mka@chromium.org>");
+MODULE_DESCRIPTION("Driver for discrete onboard USB devices");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/usb/misc/onboard_usb_dev.h b/drivers/usb/misc/onboard_usb_dev.h
new file mode 100644
index 000000000..3523f8f8a
--- /dev/null
+++ b/drivers/usb/misc/onboard_usb_dev.h
@@ -0,0 +1,176 @@
+/* SPDX-License-Identifier: GPL-2.0+ */
+/*
+ * Copyright (c) 2022, Google LLC
+ */
+
+#ifndef _USB_MISC_ONBOARD_USB_DEV_H
+#define _USB_MISC_ONBOARD_USB_DEV_H
+
+#define MAX_SUPPLIES 2
+
+struct onboard_dev_pdata {
+ unsigned long reset_us; /* reset pulse width in us */
+ unsigned long power_on_delay_us; /* power on delay in us */
+ unsigned int num_supplies; /* number of supplies */
+ const char * const supply_names[MAX_SUPPLIES];
+ bool is_hub;
+};
+
+static const struct onboard_dev_pdata microchip_usb424_data = {
+ .reset_us = 1,
+ .num_supplies = 1,
+ .supply_names = { "vdd" },
+ .is_hub = true,
+};
+
+static const struct onboard_dev_pdata microchip_usb2514_data = {
+ .reset_us = 1,
+ .num_supplies = 2,
+ .supply_names = { "vdd", "vdda" },
+ .is_hub = true,
+};
+
+static const struct onboard_dev_pdata microchip_usb5744_data = {
+ .reset_us = 0,
+ .power_on_delay_us = 10000,
+ .num_supplies = 2,
+ .supply_names = { "vdd", "vdd2" },
+ .is_hub = true,
+};
+
+static const struct onboard_dev_pdata parade_ps5511_data = {
+ .reset_us = 500,
+ .num_supplies = 2,
+ .supply_names = { "vddd11", "vdd33"},
+ .is_hub = true,
+};
+
+static const struct onboard_dev_pdata realtek_rts5411_data = {
+ .reset_us = 0,
+ .num_supplies = 1,
+ .supply_names = { "vdd" },
+ .is_hub = true,
+};
+
+static const struct onboard_dev_pdata realtek_rtl8188etv_data = {
+ .reset_us = 0,
+ .num_supplies = 1,
+ .supply_names = { "vdd" },
+ .is_hub = false,
+};
+
+static const struct onboard_dev_pdata ti_tusb8020b_data = {
+ .reset_us = 3000,
+ .num_supplies = 1,
+ .supply_names = { "vdd" },
+ .is_hub = true,
+};
+
+static const struct onboard_dev_pdata bison_intcamera_data = {
+ .reset_us = 1000,
+ .num_supplies = 1,
+ .supply_names = { "vdd" },
+ .is_hub = false,
+};
+
+static const struct onboard_dev_pdata corechips_sl6341_data = {
+ .reset_us = 10000,
+ .num_supplies = 2,
+ .supply_names = { "vdd1v1", "vdd3v3" },
+ .is_hub = true,
+};
+
+static const struct onboard_dev_pdata cypress_hx3_data = {
+ .reset_us = 10000,
+ .num_supplies = 2,
+ .supply_names = { "vdd", "vdd2" },
+ .is_hub = true,
+};
+
+static const struct onboard_dev_pdata genesys_gl850g_data = {
+ .reset_us = 3,
+ .num_supplies = 1,
+ .supply_names = { "vdd" },
+ .is_hub = true,
+};
+
+static const struct onboard_dev_pdata genesys_gl852g_data = {
+ .reset_us = 50,
+ .num_supplies = 1,
+ .supply_names = { "vdd" },
+ .is_hub = true,
+};
+
+static const struct onboard_dev_pdata genesys_gl3590_data = {
+ .reset_us = 50,
+ .num_supplies = 2,
+ .supply_names = { "vdd", "vdd12" },
+ .is_hub = true,
+};
+
+static const struct onboard_dev_pdata usb_a_conn_data = {
+ .num_supplies = 1,
+ .supply_names = { "vbus" },
+};
+
+static const struct onboard_dev_pdata vialab_vl817_data = {
+ .reset_us = 10,
+ .num_supplies = 1,
+ .supply_names = { "vdd" },
+ .is_hub = true,
+};
+
+static const struct onboard_dev_pdata wch_ch334_data = {
+ .reset_us = 14000,
+ .num_supplies = 2,
+ .supply_names = { "vdd33", "v5" },
+ .is_hub = true,
+};
+
+static const struct onboard_dev_pdata xmos_xvf3500_data = {
+ .reset_us = 1,
+ .num_supplies = 2,
+ .supply_names = { "vdd", "vddio" },
+ .is_hub = false,
+};
+
+static const struct of_device_id onboard_dev_match[] = {
+ { .compatible = "usb-a-connector", .data = &usb_a_conn_data, },
+ { .compatible = "usb424,2412", .data = &microchip_usb424_data, },
+ { .compatible = "usb424,2514", .data = &microchip_usb2514_data, },
+ { .compatible = "usb424,2517", .data = &microchip_usb424_data, },
+ { .compatible = "usb424,2744", .data = &microchip_usb5744_data, },
+ { .compatible = "usb424,5744", .data = &microchip_usb5744_data, },
+ { .compatible = "usb451,8025", .data = &ti_tusb8020b_data, },
+ { .compatible = "usb451,8027", .data = &ti_tusb8020b_data, },
+ { .compatible = "usb451,8140", .data = &ti_tusb8020b_data, },
+ { .compatible = "usb451,8142", .data = &ti_tusb8020b_data, },
+ { .compatible = "usb451,8440", .data = &ti_tusb8020b_data, },
+ { .compatible = "usb451,8442", .data = &ti_tusb8020b_data, },
+ { .compatible = "usb4b4,6504", .data = &cypress_hx3_data, },
+ { .compatible = "usb4b4,6506", .data = &cypress_hx3_data, },
+ { .compatible = "usb4b4,6570", .data = &microchip_usb424_data, },
+ { .compatible = "usb5e3,608", .data = &genesys_gl850g_data, },
+ { .compatible = "usb5e3,610", .data = &genesys_gl852g_data, },
+ { .compatible = "usb5e3,620", .data = &genesys_gl852g_data, },
+ { .compatible = "usb5e3,625", .data = &genesys_gl3590_data, },
+ { .compatible = "usb5e3,626", .data = &genesys_gl852g_data, },
+ { .compatible = "usbbda,179", .data = &realtek_rtl8188etv_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, },
+ { .compatible = "usb1a40,0101", .data = &vialab_vl817_data, },
+ { .compatible = "usb1a86,8091", .data = &wch_ch334_data, },
+ { .compatible = "usb1da0,5511", .data = &parade_ps5511_data, },
+ { .compatible = "usb1da0,55a1", .data = &parade_ps5511_data, },
+ { .compatible = "usb2109,817", .data = &vialab_vl817_data, },
+ { .compatible = "usb2109,2817", .data = &vialab_vl817_data, },
+ { .compatible = "usb20b1,0013", .data = &xmos_xvf3500_data, },
+ { .compatible = "usb3431,6241", .data = &corechips_sl6341_data, },
+ { .compatible = "usb3431,6341", .data = &corechips_sl6341_data, },
+ { .compatible = "usb5986,1198", .data = &bison_intcamera_data, },
+ {}
+};
+
+#endif /* _USB_MISC_ONBOARD_USB_DEV_H */
diff --git a/drivers/usb/misc/onboard_usb_dev_pdevs.c b/drivers/usb/misc/onboard_usb_dev_pdevs.c
new file mode 100644
index 000000000..cdb6949e1
--- /dev/null
+++ b/drivers/usb/misc/onboard_usb_dev_pdevs.c
@@ -0,0 +1,144 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * API for creating and destroying USB onboard 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_dev.h>
+
+#include "onboard_usb_dev.h"
+
+struct pdev_list_entry {
+ struct platform_device *pdev;
+ struct list_head node;
+};
+
+static bool of_is_onboard_usb_dev(struct device_node *np)
+{
+ return !!of_match_node(onboard_dev_match, np);
+}
+
+/**
+ * onboard_dev_create_pdevs -- create platform devices for onboard USB devices
+ * @parent_hub : parent hub to scan for connected onboard devices
+ * @pdev_list : list of onboard platform devices owned by the parent hub
+ *
+ * Creates a platform device for each supported onboard device that is connected
+ * to the given parent hub. The platform device is in charge of initializing the
+ * device (enable regulators, take the device out of reset, ...). For onboard
+ * hubs, it can optionally control whether the device 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 device, 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 devices
+ * 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_dev_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_dev(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 dev '%pOF'\n", np);
+ goto node_put;
+ }
+
+ pdle = kzalloc_obj(*pdle);
+ 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_dev_create_pdevs);
+
+/**
+ * onboard_dev_destroy_pdevs -- free resources of onboard platform devices
+ * @pdev_list : list of onboard platform devices
+ *
+ * Destroys the platform devices in the given list and frees the memory associated
+ * with the list entry.
+ */
+void onboard_dev_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_dev_destroy_pdevs);
diff --git a/drivers/usb/misc/qcom_eud.c b/drivers/usb/misc/qcom_eud.c
new file mode 100644
index 000000000..926419ca5
--- /dev/null
+++ b/drivers/usb/misc/qcom_eud.c
@@ -0,0 +1,266 @@
+// 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>
+#include <linux/firmware/qcom/qcom_scm.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;
+ phys_addr_t mode_mgr;
+ unsigned int int_status;
+ int irq;
+ bool enabled;
+ bool usb_attached;
+};
+
+static int enable_eud(struct eud_chip *priv)
+{
+ int ret;
+
+ ret = qcom_scm_io_writel(priv->mode_mgr + EUD_REG_EUD_EN2, 1);
+ if (ret)
+ return ret;
+
+ 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);
+
+ return usb_role_switch_set_role(priv->role_sw, USB_ROLE_DEVICE);
+}
+
+static int disable_eud(struct eud_chip *priv)
+{
+ int ret;
+
+ ret = qcom_scm_io_writel(priv->mode_mgr + EUD_REG_EUD_EN2, 0);
+ if (ret)
+ return ret;
+
+ writel(0, priv->base + EUD_REG_CSR_EUD_EN);
+ return 0;
+}
+
+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 {
+ ret = disable_eud(chip);
+ }
+
+ return ret < 0 ? ret : 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;
+ struct resource *res;
+ 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 ret;
+
+ chip->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(chip->base))
+ return PTR_ERR(chip->base);
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ if (!res)
+ return -ENODEV;
+ chip->mode_mgr = res->start;
+
+ chip->irq = platform_get_irq(pdev, 0);
+ if (chip->irq < 0)
+ return chip->irq;
+
+ 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 void 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);
+}
+
+static const struct of_device_id eud_dt_match[] = {
+ { .compatible = "qcom,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..42f81c8ea
--- /dev/null
+++ b/drivers/usb/misc/sisusbvga/Kconfig
@@ -0,0 +1,14 @@
+# 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)
+ 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.
diff --git a/drivers/usb/misc/sisusbvga/Makefile b/drivers/usb/misc/sisusbvga/Makefile
new file mode 100644
index 000000000..28aa1e6ef
--- /dev/null
+++ b/drivers/usb/misc/sisusbvga/Makefile
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for the sisusb driver (if driver is inside kernel tree).
+#
+
+obj-$(CONFIG_USB_SISUSBVGA) += sisusbvga.o
diff --git a/drivers/usb/misc/sisusbvga/sisusb.h b/drivers/usb/misc/sisusbvga/sisusb.h
new file mode 100644
index 000000000..e5b122865
--- /dev/null
+++ b/drivers/usb/misc/sisusbvga/sisusb.h
@@ -0,0 +1,278 @@
+/* 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/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;
+};
+
+#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_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/sisusbvga/sisusbvga.c b/drivers/usb/misc/sisusbvga/sisusbvga.c
new file mode 100644
index 000000000..3e75a7c24
--- /dev/null
+++ b/drivers/usb/misc/sisusbvga/sisusbvga.c
@@ -0,0 +1,2969 @@
+// 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"
+
+#define SISUSB_DONTSYNC
+
+/* Forward declarations / clean-up routines */
+
+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 */
+
+static 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;
+}
+
+static 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;
+}
+
+static 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;
+}
+
+static int sisusb_setidxregor(struct sisusb_usb_data *sisusb, u32 port,
+ u8 index, u8 myor)
+{
+ return sisusb_setidxregandor(sisusb, port, index, 0xff, myor);
+}
+
+static 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 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;
+}
+
+/* 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;
+}
+
+static 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);
+ 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;
+ break;
+
+ 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;
+ x.sisusb_conactive = 0;
+ 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_obj(*sisusb);
+ 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);
+
+ /* 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;
+ 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
+
+ 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;
+
+ 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,
+};
+
+module_usb_driver(sisusb_driver);
+
+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/trancevibrator.c b/drivers/usb/misc/trancevibrator.c
new file mode 100644
index 000000000..37f6b7988
--- /dev/null
+++ b/drivers/usb/misc/trancevibrator.c
@@ -0,0 +1,127 @@
+// 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_obj(struct trancevibrator);
+ if (!dev) {
+ retval = -ENOMEM;
+ goto error;
+ }
+
+ dev->udev = 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);
+ 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/usb-ljca.c b/drivers/usb/misc/usb-ljca.c
new file mode 100644
index 000000000..fcb627b49
--- /dev/null
+++ b/drivers/usb/misc/usb-ljca.c
@@ -0,0 +1,906 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Intel La Jolla Cove Adapter USB driver
+ *
+ * Copyright (c) 2023, Intel Corporation.
+ */
+
+#include <linux/acpi.h>
+#include <linux/auxiliary_bus.h>
+#include <linux/dev_printk.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/types.h>
+#include <linux/usb.h>
+#include <linux/usb/ljca.h>
+
+#include <linux/unaligned.h>
+
+/* command flags */
+#define LJCA_ACK_FLAG BIT(0)
+#define LJCA_RESP_FLAG BIT(1)
+#define LJCA_CMPL_FLAG BIT(2)
+
+#define LJCA_MAX_PACKET_SIZE 64u
+#define LJCA_MAX_PAYLOAD_SIZE \
+ (LJCA_MAX_PACKET_SIZE - sizeof(struct ljca_msg))
+
+#define LJCA_WRITE_TIMEOUT_MS 200
+#define LJCA_WRITE_ACK_TIMEOUT_MS 500
+#define LJCA_ENUM_CLIENT_TIMEOUT_MS 20
+
+/* ljca client type */
+enum ljca_client_type {
+ LJCA_CLIENT_MNG = 1,
+ LJCA_CLIENT_GPIO = 3,
+ LJCA_CLIENT_I2C = 4,
+ LJCA_CLIENT_SPI = 5,
+};
+
+/* MNG client commands */
+enum ljca_mng_cmd {
+ LJCA_MNG_RESET = 2,
+ LJCA_MNG_ENUM_GPIO = 4,
+ LJCA_MNG_ENUM_I2C = 5,
+ LJCA_MNG_ENUM_SPI = 8,
+};
+
+/* ljca client acpi _ADR */
+enum ljca_client_acpi_adr {
+ LJCA_GPIO_ACPI_ADR,
+ LJCA_I2C1_ACPI_ADR,
+ LJCA_I2C2_ACPI_ADR,
+ LJCA_SPI1_ACPI_ADR,
+ LJCA_SPI2_ACPI_ADR,
+ LJCA_CLIENT_ACPI_ADR_MAX,
+};
+
+/* ljca cmd message structure */
+struct ljca_msg {
+ u8 type;
+ u8 cmd;
+ u8 flags;
+ u8 len;
+ u8 data[] __counted_by(len);
+} __packed;
+
+struct ljca_i2c_ctr_info {
+ u8 id;
+ u8 capacity;
+ u8 intr_pin;
+} __packed;
+
+struct ljca_i2c_descriptor {
+ u8 num;
+ struct ljca_i2c_ctr_info info[] __counted_by(num);
+} __packed;
+
+struct ljca_spi_ctr_info {
+ u8 id;
+ u8 capacity;
+ u8 intr_pin;
+} __packed;
+
+struct ljca_spi_descriptor {
+ u8 num;
+ struct ljca_spi_ctr_info info[] __counted_by(num);
+} __packed;
+
+struct ljca_bank_descriptor {
+ u8 bank_id;
+ u8 pin_num;
+
+ /* 1 bit for each gpio, 1 means valid */
+ __le32 valid_pins;
+} __packed;
+
+struct ljca_gpio_descriptor {
+ u8 pins_per_bank;
+ u8 bank_num;
+ struct ljca_bank_descriptor bank_desc[] __counted_by(bank_num);
+} __packed;
+
+/**
+ * struct ljca_adapter - represent a ljca adapter
+ *
+ * @intf: the usb interface for this ljca adapter
+ * @usb_dev: the usb device for this ljca adapter
+ * @dev: the specific device info of the usb interface
+ * @rx_pipe: bulk in pipe for receive data from firmware
+ * @tx_pipe: bulk out pipe for send data to firmware
+ * @rx_urb: urb used for the bulk in pipe
+ * @rx_buf: buffer used to receive command response and event
+ * @rx_len: length of rx buffer
+ * @ex_buf: external buffer to save command response
+ * @ex_buf_len: length of external buffer
+ * @actual_length: actual length of data copied to external buffer
+ * @tx_buf: buffer used to download command to firmware
+ * @tx_buf_len: length of tx buffer
+ * @lock: spinlock to protect tx_buf and ex_buf
+ * @cmd_completion: completion object as the command receives ack
+ * @mutex: mutex to avoid command download concurrently
+ * @client_list: client device list
+ * @disconnect: usb disconnect ongoing or not
+ * @reset_id: used to reset firmware
+ */
+struct ljca_adapter {
+ struct usb_interface *intf;
+ struct usb_device *usb_dev;
+ struct device *dev;
+
+ unsigned int rx_pipe;
+ unsigned int tx_pipe;
+
+ struct urb *rx_urb;
+ void *rx_buf;
+ unsigned int rx_len;
+
+ u8 *ex_buf;
+ u8 ex_buf_len;
+ u8 actual_length;
+
+ void *tx_buf;
+ u8 tx_buf_len;
+
+ spinlock_t lock;
+
+ struct completion cmd_completion;
+ struct mutex mutex;
+
+ struct list_head client_list;
+
+ bool disconnect;
+
+ u32 reset_id;
+};
+
+struct ljca_match_ids_walk_data {
+ const struct acpi_device_id *ids;
+ const char *uid;
+ struct acpi_device *adev;
+};
+
+/*
+ * ACPI hardware IDs for LJCA client devices.
+ *
+ * [1] Some BIOS implementations use these IDs for denoting LJCA client devices
+ * even though the IDs have been allocated for USBIO. This isn't a problem
+ * as the usb-ljca driver is probed based on the USB device's vendor and
+ * product IDs and its client drivers are probed based on auxiliary device
+ * names, not these ACPI _HIDs. List of such systems:
+ *
+ * Dell Precision 5490
+ */
+static const struct acpi_device_id ljca_gpio_hids[] = {
+ { "INTC100B" }, /* RPL LJCA GPIO */
+ { "INTC1074" }, /* CVF LJCA GPIO */
+ { "INTC1096" }, /* ADL LJCA GPIO */
+ { "INTC10B5" }, /* LNL LJCA GPIO */
+ { "INTC10D1" }, /* MTL (CVF VSC) USBIO GPIO [1] */
+ {},
+};
+
+static const struct acpi_device_id ljca_i2c_hids[] = {
+ { "INTC100C" }, /* RPL LJCA I2C */
+ { "INTC1075" }, /* CVF LJCA I2C */
+ { "INTC1097" }, /* ADL LJCA I2C */
+ { "INTC10D2" }, /* MTL (CVF VSC) USBIO I2C [1] */
+ {},
+};
+
+static const struct acpi_device_id ljca_spi_hids[] = {
+ { "INTC100D" }, /* RPL LJCA SPI */
+ { "INTC1091" }, /* TGL/ADL LJCA SPI */
+ { "INTC1098" }, /* ADL LJCA SPI */
+ { "INTC10D3" }, /* MTL (CVF VSC) USBIO SPI [1] */
+ {},
+};
+
+static void ljca_handle_event(struct ljca_adapter *adap,
+ struct ljca_msg *header)
+{
+ struct ljca_client *client;
+
+ list_for_each_entry(client, &adap->client_list, link) {
+ /*
+ * Currently only GPIO register event callback, but
+ * firmware message structure should include id when
+ * multiple same type clients register event callback.
+ */
+ if (client->type == header->type) {
+ unsigned long flags;
+
+ spin_lock_irqsave(&client->event_cb_lock, flags);
+ client->event_cb(client->context, header->cmd,
+ header->data, header->len);
+ spin_unlock_irqrestore(&client->event_cb_lock, flags);
+
+ break;
+ }
+ }
+}
+
+/* process command ack and received data if available */
+static void ljca_handle_cmd_ack(struct ljca_adapter *adap, struct ljca_msg *header)
+{
+ struct ljca_msg *tx_header = adap->tx_buf;
+ u8 ibuf_len, actual_len = 0;
+ unsigned long flags;
+ u8 *ibuf;
+
+ spin_lock_irqsave(&adap->lock, flags);
+
+ if (tx_header->type != header->type || tx_header->cmd != header->cmd) {
+ spin_unlock_irqrestore(&adap->lock, flags);
+ dev_err(adap->dev, "cmd ack mismatch error\n");
+ return;
+ }
+
+ ibuf_len = adap->ex_buf_len;
+ ibuf = adap->ex_buf;
+
+ if (ibuf && ibuf_len) {
+ actual_len = min(header->len, ibuf_len);
+
+ /* copy received data to external buffer */
+ memcpy(ibuf, header->data, actual_len);
+ }
+ /* update copied data length */
+ adap->actual_length = actual_len;
+
+ spin_unlock_irqrestore(&adap->lock, flags);
+
+ complete(&adap->cmd_completion);
+}
+
+static void ljca_recv(struct urb *urb)
+{
+ struct ljca_msg *header = urb->transfer_buffer;
+ struct ljca_adapter *adap = urb->context;
+ int ret;
+
+ switch (urb->status) {
+ case 0:
+ /* success */
+ break;
+ case -ENOENT:
+ /*
+ * directly complete the possible ongoing transfer
+ * during disconnect
+ */
+ if (adap->disconnect)
+ complete(&adap->cmd_completion);
+ return;
+ case -ECONNRESET:
+ case -ESHUTDOWN:
+ case -EPIPE:
+ /* rx urb is terminated */
+ dev_dbg(adap->dev, "rx urb terminated with status: %d\n",
+ urb->status);
+ return;
+ default:
+ dev_dbg(adap->dev, "rx urb error: %d\n", urb->status);
+ goto resubmit;
+ }
+
+ if (header->len + sizeof(*header) != urb->actual_length)
+ goto resubmit;
+
+ if (header->flags & LJCA_ACK_FLAG)
+ ljca_handle_cmd_ack(adap, header);
+ else
+ ljca_handle_event(adap, header);
+
+resubmit:
+ ret = usb_submit_urb(urb, GFP_ATOMIC);
+ if (ret && ret != -EPERM)
+ dev_err(adap->dev, "resubmit rx urb error %d\n", ret);
+}
+
+static int ljca_send(struct ljca_adapter *adap, u8 type, u8 cmd,
+ const u8 *obuf, u8 obuf_len, u8 *ibuf, u8 ibuf_len,
+ bool ack, unsigned long timeout)
+{
+ unsigned int msg_len = sizeof(struct ljca_msg) + obuf_len;
+ struct ljca_msg *header = adap->tx_buf;
+ unsigned int transferred;
+ unsigned long flags;
+ int ret;
+
+ if (adap->disconnect)
+ return -ENODEV;
+
+ if (msg_len > adap->tx_buf_len)
+ return -EINVAL;
+
+ mutex_lock(&adap->mutex);
+
+ spin_lock_irqsave(&adap->lock, flags);
+
+ header->type = type;
+ header->cmd = cmd;
+ header->len = obuf_len;
+ if (obuf)
+ memcpy(header->data, obuf, obuf_len);
+
+ header->flags = LJCA_CMPL_FLAG | (ack ? LJCA_ACK_FLAG : 0);
+
+ adap->ex_buf = ibuf;
+ adap->ex_buf_len = ibuf_len;
+ adap->actual_length = 0;
+
+ spin_unlock_irqrestore(&adap->lock, flags);
+
+ reinit_completion(&adap->cmd_completion);
+
+ ret = usb_autopm_get_interface(adap->intf);
+ if (ret < 0)
+ goto out;
+
+ ret = usb_bulk_msg(adap->usb_dev, adap->tx_pipe, header,
+ msg_len, &transferred, LJCA_WRITE_TIMEOUT_MS);
+ if (ret < 0)
+ goto out_put;
+ if (transferred != msg_len) {
+ ret = -EIO;
+ goto out_put;
+ }
+
+ if (ack) {
+ ret = wait_for_completion_timeout(&adap->cmd_completion,
+ timeout);
+ if (!ret) {
+ ret = -ETIMEDOUT;
+ goto out_put;
+ }
+ }
+ ret = adap->actual_length;
+
+out_put:
+ usb_autopm_put_interface(adap->intf);
+
+out:
+ spin_lock_irqsave(&adap->lock, flags);
+ adap->ex_buf = NULL;
+ adap->ex_buf_len = 0;
+
+ memset(header, 0, sizeof(*header));
+ spin_unlock_irqrestore(&adap->lock, flags);
+
+ mutex_unlock(&adap->mutex);
+
+ return ret;
+}
+
+int ljca_transfer(struct ljca_client *client, u8 cmd, const u8 *obuf,
+ u8 obuf_len, u8 *ibuf, u8 ibuf_len)
+{
+ return ljca_send(client->adapter, client->type, cmd,
+ obuf, obuf_len, ibuf, ibuf_len, true,
+ LJCA_WRITE_ACK_TIMEOUT_MS);
+}
+EXPORT_SYMBOL_NS_GPL(ljca_transfer, "LJCA");
+
+int ljca_transfer_noack(struct ljca_client *client, u8 cmd, const u8 *obuf,
+ u8 obuf_len)
+{
+ return ljca_send(client->adapter, client->type, cmd, obuf,
+ obuf_len, NULL, 0, false, LJCA_WRITE_ACK_TIMEOUT_MS);
+}
+EXPORT_SYMBOL_NS_GPL(ljca_transfer_noack, "LJCA");
+
+int ljca_register_event_cb(struct ljca_client *client, ljca_event_cb_t event_cb,
+ void *context)
+{
+ unsigned long flags;
+
+ if (!event_cb)
+ return -EINVAL;
+
+ spin_lock_irqsave(&client->event_cb_lock, flags);
+
+ if (client->event_cb) {
+ spin_unlock_irqrestore(&client->event_cb_lock, flags);
+ return -EALREADY;
+ }
+
+ client->event_cb = event_cb;
+ client->context = context;
+
+ spin_unlock_irqrestore(&client->event_cb_lock, flags);
+
+ return 0;
+}
+EXPORT_SYMBOL_NS_GPL(ljca_register_event_cb, "LJCA");
+
+void ljca_unregister_event_cb(struct ljca_client *client)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&client->event_cb_lock, flags);
+
+ client->event_cb = NULL;
+ client->context = NULL;
+
+ spin_unlock_irqrestore(&client->event_cb_lock, flags);
+}
+EXPORT_SYMBOL_NS_GPL(ljca_unregister_event_cb, "LJCA");
+
+static int ljca_match_device_ids(struct acpi_device *adev, void *data)
+{
+ struct ljca_match_ids_walk_data *wd = data;
+ const char *uid = acpi_device_uid(adev);
+
+ if (acpi_match_device_ids(adev, wd->ids))
+ return 0;
+
+ if (!wd->uid)
+ goto match;
+
+ if (!uid)
+ /*
+ * Some DSDTs have only one ACPI companion for the two I2C
+ * controllers and they don't set a UID at all (e.g. Dell
+ * Latitude 9420). On these platforms only the first I2C
+ * controller is used, so if a HID match has no UID we use
+ * "0" as the UID and assign ACPI companion to the first
+ * I2C controller.
+ */
+ uid = "0";
+ else
+ uid = strchr(uid, wd->uid[0]);
+
+ if (!uid || strcmp(uid, wd->uid))
+ return 0;
+
+match:
+ wd->adev = adev;
+
+ return 1;
+}
+
+/* bind auxiliary device to acpi device */
+static void ljca_auxdev_acpi_bind(struct ljca_adapter *adap,
+ struct auxiliary_device *auxdev,
+ u64 adr, u8 id)
+{
+ struct ljca_match_ids_walk_data wd = { 0 };
+ struct device *dev = adap->dev;
+ struct acpi_device *parent;
+ char uid[4];
+
+ parent = ACPI_COMPANION(dev);
+ if (!parent)
+ return;
+
+ /*
+ * Currently LJCA hw doesn't use _ADR instead the shipped
+ * platforms use _HID to distinguish children devices.
+ */
+ switch (adr) {
+ case LJCA_GPIO_ACPI_ADR:
+ wd.ids = ljca_gpio_hids;
+ break;
+ case LJCA_I2C1_ACPI_ADR:
+ case LJCA_I2C2_ACPI_ADR:
+ snprintf(uid, sizeof(uid), "%d", id);
+ wd.uid = uid;
+ wd.ids = ljca_i2c_hids;
+ break;
+ case LJCA_SPI1_ACPI_ADR:
+ case LJCA_SPI2_ACPI_ADR:
+ wd.ids = ljca_spi_hids;
+ break;
+ default:
+ dev_warn(dev, "unsupported _ADR\n");
+ return;
+ }
+
+ acpi_dev_for_each_child(parent, ljca_match_device_ids, &wd);
+ if (wd.adev) {
+ ACPI_COMPANION_SET(&auxdev->dev, wd.adev);
+ return;
+ }
+
+ parent = ACPI_COMPANION(dev->parent->parent);
+ if (!parent)
+ return;
+
+ acpi_dev_for_each_child(parent, ljca_match_device_ids, &wd);
+ if (wd.adev)
+ ACPI_COMPANION_SET(&auxdev->dev, wd.adev);
+}
+
+static void ljca_auxdev_release(struct device *dev)
+{
+ struct auxiliary_device *auxdev = to_auxiliary_dev(dev);
+
+ kfree(auxdev->dev.platform_data);
+}
+
+static int ljca_new_client_device(struct ljca_adapter *adap, u8 type, u8 id,
+ char *name, void *data, u64 adr)
+{
+ struct auxiliary_device *auxdev;
+ struct ljca_client *client;
+ int ret;
+
+ client = kzalloc_obj(*client);
+ if (!client) {
+ kfree(data);
+ return -ENOMEM;
+ }
+
+ client->type = type;
+ client->id = id;
+ client->adapter = adap;
+ spin_lock_init(&client->event_cb_lock);
+
+ auxdev = &client->auxdev;
+ auxdev->name = name;
+ auxdev->id = id;
+
+ auxdev->dev.parent = adap->dev;
+ auxdev->dev.platform_data = data;
+ auxdev->dev.release = ljca_auxdev_release;
+
+ ret = auxiliary_device_init(auxdev);
+ if (ret) {
+ kfree(data);
+ goto err_free;
+ }
+
+ ljca_auxdev_acpi_bind(adap, auxdev, adr, id);
+
+ ret = auxiliary_device_add(auxdev);
+ if (ret)
+ goto err_uninit;
+
+ list_add_tail(&client->link, &adap->client_list);
+
+ return 0;
+
+err_uninit:
+ auxiliary_device_uninit(auxdev);
+
+err_free:
+ kfree(client);
+
+ return ret;
+}
+
+static int ljca_enumerate_gpio(struct ljca_adapter *adap)
+{
+ u32 valid_pin[LJCA_MAX_GPIO_NUM / BITS_PER_TYPE(u32)];
+ struct ljca_gpio_descriptor *desc;
+ struct ljca_gpio_info *gpio_info;
+ u8 buf[LJCA_MAX_PAYLOAD_SIZE];
+ int ret, gpio_num;
+ unsigned int i;
+
+ ret = ljca_send(adap, LJCA_CLIENT_MNG, LJCA_MNG_ENUM_GPIO, NULL, 0, buf,
+ sizeof(buf), true, LJCA_ENUM_CLIENT_TIMEOUT_MS);
+ if (ret < 0)
+ return ret;
+
+ /* check firmware response */
+ desc = (struct ljca_gpio_descriptor *)buf;
+ if (ret != struct_size(desc, bank_desc, desc->bank_num))
+ return -EINVAL;
+
+ gpio_num = desc->pins_per_bank * desc->bank_num;
+ if (gpio_num > LJCA_MAX_GPIO_NUM)
+ return -EINVAL;
+
+ if (desc->bank_num > ARRAY_SIZE(valid_pin))
+ return -EINVAL;
+
+ /* construct platform data */
+ gpio_info = kzalloc_obj(*gpio_info);
+ if (!gpio_info)
+ return -ENOMEM;
+ gpio_info->num = gpio_num;
+
+ for (i = 0; i < desc->bank_num; i++)
+ valid_pin[i] = get_unaligned_le32(&desc->bank_desc[i].valid_pins);
+ bitmap_from_arr32(gpio_info->valid_pin_map, valid_pin, gpio_num);
+
+ return ljca_new_client_device(adap, LJCA_CLIENT_GPIO, 0, "ljca-gpio",
+ gpio_info, LJCA_GPIO_ACPI_ADR);
+}
+
+static int ljca_enumerate_i2c(struct ljca_adapter *adap)
+{
+ struct ljca_i2c_descriptor *desc;
+ struct ljca_i2c_info *i2c_info;
+ u8 buf[LJCA_MAX_PAYLOAD_SIZE];
+ unsigned int i;
+ int ret;
+
+ ret = ljca_send(adap, LJCA_CLIENT_MNG, LJCA_MNG_ENUM_I2C, NULL, 0, buf,
+ sizeof(buf), true, LJCA_ENUM_CLIENT_TIMEOUT_MS);
+ if (ret < 0)
+ return ret;
+
+ /* check firmware response */
+ desc = (struct ljca_i2c_descriptor *)buf;
+ if (ret != struct_size(desc, info, desc->num))
+ return -EINVAL;
+
+ for (i = 0; i < desc->num; i++) {
+ /* construct platform data */
+ i2c_info = kzalloc_obj(*i2c_info);
+ if (!i2c_info)
+ return -ENOMEM;
+
+ i2c_info->id = desc->info[i].id;
+ i2c_info->capacity = desc->info[i].capacity;
+ i2c_info->intr_pin = desc->info[i].intr_pin;
+
+ ret = ljca_new_client_device(adap, LJCA_CLIENT_I2C, i,
+ "ljca-i2c", i2c_info,
+ LJCA_I2C1_ACPI_ADR + i);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int ljca_enumerate_spi(struct ljca_adapter *adap)
+{
+ struct ljca_spi_descriptor *desc;
+ struct ljca_spi_info *spi_info;
+ u8 buf[LJCA_MAX_PAYLOAD_SIZE];
+ unsigned int i;
+ int ret;
+
+ /* Not all LJCA chips implement SPI, a timeout reading the descriptors is normal */
+ ret = ljca_send(adap, LJCA_CLIENT_MNG, LJCA_MNG_ENUM_SPI, NULL, 0, buf,
+ sizeof(buf), true, LJCA_ENUM_CLIENT_TIMEOUT_MS);
+ if (ret < 0)
+ return (ret == -ETIMEDOUT) ? 0 : ret;
+
+ /* check firmware response */
+ desc = (struct ljca_spi_descriptor *)buf;
+ if (ret != struct_size(desc, info, desc->num))
+ return -EINVAL;
+
+ for (i = 0; i < desc->num; i++) {
+ /* construct platform data */
+ spi_info = kzalloc_obj(*spi_info);
+ if (!spi_info)
+ return -ENOMEM;
+
+ spi_info->id = desc->info[i].id;
+ spi_info->capacity = desc->info[i].capacity;
+
+ ret = ljca_new_client_device(adap, LJCA_CLIENT_SPI, i,
+ "ljca-spi", spi_info,
+ LJCA_SPI1_ACPI_ADR + i);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int ljca_reset_handshake(struct ljca_adapter *adap)
+{
+ __le32 reset_id = cpu_to_le32(adap->reset_id);
+ __le32 reset_id_ret = 0;
+ int ret;
+
+ adap->reset_id++;
+
+ ret = ljca_send(adap, LJCA_CLIENT_MNG, LJCA_MNG_RESET, (u8 *)&reset_id,
+ sizeof(__le32), (u8 *)&reset_id_ret, sizeof(__le32),
+ true, LJCA_WRITE_ACK_TIMEOUT_MS);
+ if (ret < 0)
+ return ret;
+
+ if (reset_id_ret != reset_id)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int ljca_enumerate_clients(struct ljca_adapter *adap)
+{
+ struct ljca_client *client, *next;
+ int ret;
+
+ ret = ljca_reset_handshake(adap);
+ if (ret)
+ goto err_kill;
+
+ ret = ljca_enumerate_gpio(adap);
+ if (ret) {
+ dev_err(adap->dev, "enumerate GPIO error\n");
+ goto err_kill;
+ }
+
+ ret = ljca_enumerate_i2c(adap);
+ if (ret) {
+ dev_err(adap->dev, "enumerate I2C error\n");
+ goto err_kill;
+ }
+
+ ret = ljca_enumerate_spi(adap);
+ if (ret) {
+ dev_err(adap->dev, "enumerate SPI error\n");
+ goto err_kill;
+ }
+
+ return 0;
+
+err_kill:
+ adap->disconnect = true;
+
+ usb_kill_urb(adap->rx_urb);
+
+ list_for_each_entry_safe_reverse(client, next, &adap->client_list, link) {
+ auxiliary_device_delete(&client->auxdev);
+ auxiliary_device_uninit(&client->auxdev);
+
+ list_del_init(&client->link);
+ kfree(client);
+ }
+
+ return ret;
+}
+
+static int ljca_probe(struct usb_interface *interface,
+ const struct usb_device_id *id)
+{
+ struct usb_device *usb_dev = interface_to_usbdev(interface);
+ struct usb_host_interface *alt = interface->cur_altsetting;
+ struct usb_endpoint_descriptor *ep_in, *ep_out;
+ struct device *dev = &interface->dev;
+ struct ljca_adapter *adap;
+ int ret;
+
+ adap = devm_kzalloc(dev, sizeof(*adap), GFP_KERNEL);
+ if (!adap)
+ return -ENOMEM;
+
+ /* separate tx buffer allocation for alignment */
+ adap->tx_buf = devm_kzalloc(dev, LJCA_MAX_PACKET_SIZE, GFP_KERNEL);
+ if (!adap->tx_buf)
+ return -ENOMEM;
+ adap->tx_buf_len = LJCA_MAX_PACKET_SIZE;
+
+ mutex_init(&adap->mutex);
+ spin_lock_init(&adap->lock);
+ init_completion(&adap->cmd_completion);
+ INIT_LIST_HEAD(&adap->client_list);
+
+ adap->intf = interface;
+ adap->usb_dev = usb_dev;
+ adap->dev = dev;
+
+ /*
+ * find the first bulk in and out endpoints.
+ * ignore any others.
+ */
+ ret = usb_find_common_endpoints(alt, &ep_in, &ep_out, NULL, NULL);
+ if (ret) {
+ dev_err(dev, "bulk endpoints not found\n");
+ goto err_destroy_mutex;
+ }
+ adap->rx_pipe = usb_rcvbulkpipe(usb_dev, usb_endpoint_num(ep_in));
+ adap->tx_pipe = usb_sndbulkpipe(usb_dev, usb_endpoint_num(ep_out));
+
+ /* setup rx buffer */
+ adap->rx_len = usb_endpoint_maxp(ep_in);
+ adap->rx_buf = devm_kzalloc(dev, adap->rx_len, GFP_KERNEL);
+ if (!adap->rx_buf) {
+ ret = -ENOMEM;
+ goto err_destroy_mutex;
+ }
+
+ /* alloc rx urb */
+ adap->rx_urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!adap->rx_urb) {
+ ret = -ENOMEM;
+ goto err_destroy_mutex;
+ }
+ usb_fill_bulk_urb(adap->rx_urb, usb_dev, adap->rx_pipe,
+ adap->rx_buf, adap->rx_len, ljca_recv, adap);
+
+ usb_set_intfdata(interface, adap);
+
+ /* submit rx urb before enumerate clients */
+ ret = usb_submit_urb(adap->rx_urb, GFP_KERNEL);
+ if (ret) {
+ dev_err(dev, "submit rx urb failed: %d\n", ret);
+ goto err_free;
+ }
+
+ ret = ljca_enumerate_clients(adap);
+ if (ret)
+ goto err_free;
+
+ /*
+ * This works around problems with ov2740 initialization on some
+ * Lenovo platforms. The autosuspend delay, has to be smaller than
+ * the delay after setting the reset_gpio line in ov2740_resume().
+ * Otherwise the sensor randomly fails to initialize.
+ */
+ pm_runtime_set_autosuspend_delay(&usb_dev->dev, 10);
+
+ usb_enable_autosuspend(usb_dev);
+
+ return 0;
+
+err_free:
+ usb_free_urb(adap->rx_urb);
+err_destroy_mutex:
+ mutex_destroy(&adap->mutex);
+
+ return ret;
+}
+
+static void ljca_disconnect(struct usb_interface *interface)
+{
+ struct ljca_adapter *adap = usb_get_intfdata(interface);
+ struct ljca_client *client, *next;
+
+ adap->disconnect = true;
+
+ usb_kill_urb(adap->rx_urb);
+
+ list_for_each_entry_safe_reverse(client, next, &adap->client_list, link) {
+ auxiliary_device_delete(&client->auxdev);
+ auxiliary_device_uninit(&client->auxdev);
+
+ list_del_init(&client->link);
+ kfree(client);
+ }
+
+ usb_free_urb(adap->rx_urb);
+
+ mutex_destroy(&adap->mutex);
+}
+
+static int ljca_suspend(struct usb_interface *interface, pm_message_t message)
+{
+ struct ljca_adapter *adap = usb_get_intfdata(interface);
+
+ usb_kill_urb(adap->rx_urb);
+
+ return 0;
+}
+
+static int ljca_resume(struct usb_interface *interface)
+{
+ struct ljca_adapter *adap = usb_get_intfdata(interface);
+
+ return usb_submit_urb(adap->rx_urb, GFP_KERNEL);
+}
+
+static const struct usb_device_id ljca_table[] = {
+ { USB_DEVICE(0x8086, 0x0b63) },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(usb, ljca_table);
+
+static struct usb_driver ljca_driver = {
+ .name = "ljca",
+ .id_table = ljca_table,
+ .probe = ljca_probe,
+ .disconnect = ljca_disconnect,
+ .suspend = ljca_suspend,
+ .resume = ljca_resume,
+ .supports_autosuspend = 1,
+};
+module_usb_driver(ljca_driver);
+
+MODULE_AUTHOR("Wentong Wu");
+MODULE_AUTHOR("Zhifeng Wang <zhifeng.wang@intel.com>");
+MODULE_AUTHOR("Lixu Zhang <lixu.zhang@intel.com>");
+MODULE_DESCRIPTION("Intel La Jolla Cove Adapter USB driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/misc/usb251xb.c b/drivers/usb/misc/usb251xb.c
new file mode 100644
index 000000000..fb0693742
--- /dev/null
+++ b/drivers/usb/misc/usb251xb.c
@@ -0,0 +1,834 @@
+// 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.h>
+#include <linux/platform_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;
+ int ret;
+
+ if (!hub->i2c)
+ return 0;
+
+ if (!hub->gpio_reset)
+ return 0;
+
+ if (!gc)
+ return -EINVAL;
+
+ adap = hub->i2c->adapter;
+ 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;
+
+ if (hub->i2c)
+ 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 */
+
+ if (hub->i2c)
+ 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];
+
+ if (!hub->i2c) {
+ usb251xb_reset(hub);
+ dev_info(dev, "hub is put in default configuration.\n");
+ return 0;
+ }
+
+ 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;
+}
+
+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;
+ u32 port;
+
+ of_property_for_each_u32(dev->of_node, prop_name, 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;
+ }
+
+ hub->skip_config = of_property_read_bool(np, "skip-config");
+
+ 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(np, "vendor-id", &hub->vendor_id))
+ hub->vendor_id = USB251XB_DEF_VENDOR_ID;
+
+ if (of_property_read_u16(np, "product-id", &hub->product_id))
+ hub->product_id = data->product_id;
+
+ if (of_property_read_u16(np, "device-id", &hub->device_id))
+ hub->device_id = USB251XB_DEF_DEVICE_ID;
+
+ hub->conf_data1 = USB251XB_DEF_CONFIG_DATA_1;
+ if (of_property_read_bool(np, "self-powered")) {
+ hub->conf_data1 |= BIT(7);
+
+ /* Configure Over-Current sens when self-powered */
+ hub->conf_data1 &= ~BIT(2);
+ if (of_property_read_bool(np, "ganged-sensing"))
+ hub->conf_data1 &= ~BIT(1);
+ else if (of_property_read_bool(np, "individual-sensing"))
+ hub->conf_data1 |= BIT(1);
+ } else if (of_property_read_bool(np, "bus-powered")) {
+ hub->conf_data1 &= ~BIT(7);
+
+ /* Disable Over-Current sense when bus-powered */
+ hub->conf_data1 |= BIT(2);
+ }
+
+ if (of_property_read_bool(np, "disable-hi-speed"))
+ hub->conf_data1 |= BIT(5);
+
+ if (of_property_read_bool(np, "multi-tt"))
+ hub->conf_data1 |= BIT(4);
+ else if (of_property_read_bool(np, "single-tt"))
+ hub->conf_data1 &= ~BIT(4);
+
+ if (of_property_read_bool(np, "disable-eop"))
+ hub->conf_data1 |= BIT(3);
+
+ if (of_property_read_bool(np, "individual-port-switching"))
+ hub->conf_data1 |= BIT(0);
+ else if (of_property_read_bool(np, "ganged-port-switching"))
+ hub->conf_data1 &= ~BIT(0);
+
+ hub->conf_data2 = USB251XB_DEF_CONFIG_DATA_2;
+ if (of_property_read_bool(np, "dynamic-power-switching"))
+ 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_property_read_bool(np, "compound-device"))
+ hub->conf_data2 |= BIT(3);
+
+ hub->conf_data3 = USB251XB_DEF_CONFIG_DATA_3;
+ if (of_property_read_bool(np, "port-mapping-mode"))
+ hub->conf_data3 |= BIT(3);
+
+ if (data->led_support && of_get_property(np, "led-usb-mode", NULL))
+ hub->conf_data3 &= ~BIT(1);
+
+ if (of_property_read_bool(np, "string-support"))
+ 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(np, "language-id", &hub->lang_id))
+ 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);
+
+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)
+ return dev_err_probe(dev, err, "failed to get ofdata\n");
+ }
+
+ /*
+ * 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)
+{
+ 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 usb251xb_suspend(struct usb251xb *hub)
+{
+ return regulator_disable(hub->vdd);
+}
+
+static int usb251xb_resume(struct usb251xb *hub)
+{
+ int err;
+
+ err = regulator_enable(hub->vdd);
+ if (err)
+ return err;
+
+ return usb251xb_connect(hub);
+}
+
+static int usb251xb_i2c_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct usb251xb *hub = i2c_get_clientdata(client);
+
+ return usb251xb_suspend(hub);
+}
+
+static int usb251xb_i2c_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct usb251xb *hub = i2c_get_clientdata(client);
+
+ return usb251xb_resume(hub);
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(usb251xb_i2c_pm_ops, usb251xb_i2c_suspend, usb251xb_i2c_resume);
+
+static const struct i2c_device_id usb251xb_id[] = {
+ { .name = "usb2422" },
+ { .name = "usb2512b" },
+ { .name = "usb2512bi" },
+ { .name = "usb2513b" },
+ { .name = "usb2513bi" },
+ { .name = "usb2514b" },
+ { .name = "usb2514bi" },
+ { .name = "usb2517" },
+ { .name = "usb2517i" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(i2c, usb251xb_id);
+
+static struct i2c_driver usb251xb_i2c_driver = {
+ .driver = {
+ .name = DRIVER_NAME,
+ .of_match_table = usb251xb_of_match,
+ .pm = pm_sleep_ptr(&usb251xb_i2c_pm_ops),
+ },
+ .probe = usb251xb_i2c_probe,
+ .id_table = usb251xb_id,
+};
+
+static int usb251xb_plat_probe(struct platform_device *pdev)
+{
+ struct usb251xb *hub;
+
+ hub = devm_kzalloc(&pdev->dev, sizeof(*hub), GFP_KERNEL);
+ if (!hub)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, hub);
+ hub->dev = &pdev->dev;
+
+ return usb251xb_probe(hub);
+}
+
+static int usb251xb_plat_suspend(struct device *dev)
+{
+ return usb251xb_suspend(dev_get_drvdata(dev));
+}
+
+static int usb251xb_plat_resume(struct device *dev)
+{
+ return usb251xb_resume(dev_get_drvdata(dev));
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(usb251xb_plat_pm_ops, usb251xb_plat_suspend, usb251xb_plat_resume);
+
+static struct platform_driver usb251xb_plat_driver = {
+ .driver = {
+ .name = DRIVER_NAME,
+ .of_match_table = usb251xb_of_match,
+ .pm = pm_sleep_ptr(&usb251xb_plat_pm_ops),
+ },
+ .probe = usb251xb_plat_probe,
+};
+
+static int __init usb251xb_init(void)
+{
+ int err;
+
+ err = i2c_add_driver(&usb251xb_i2c_driver);
+ if (err)
+ return err;
+
+ err = platform_driver_register(&usb251xb_plat_driver);
+ if (err) {
+ i2c_del_driver(&usb251xb_i2c_driver);
+ return err;
+ }
+
+ return 0;
+}
+module_init(usb251xb_init);
+
+static void __exit usb251xb_exit(void)
+{
+ platform_driver_unregister(&usb251xb_plat_driver);
+ i2c_del_driver(&usb251xb_i2c_driver);
+}
+module_exit(usb251xb_exit);
+
+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..759770a13
--- /dev/null
+++ b/drivers/usb/misc/usb3503.c
@@ -0,0 +1,460 @@
+// 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 *bypass;
+ struct gpio_desc *intn;
+ struct gpio_desc *reset;
+ struct gpio_desc *connect;
+ bool secondary_ref_clk;
+};
+
+static int usb3503_connect(struct usb3503 *hub)
+{
+ struct device *dev = hub->dev;
+ int err;
+
+ 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 rst, bypass, conn;
+
+ switch (mode) {
+ case USB3503_MODE_HUB:
+ conn = 1;
+ rst = 0;
+ bypass = 0;
+ break;
+ case USB3503_MODE_STANDBY:
+ conn = 0;
+ rst = 1;
+ bypass = 1;
+ dev_info(dev, "switched to STANDBY mode\n");
+ break;
+ case USB3503_MODE_BYPASS:
+ conn = 0;
+ rst = 0;
+ bypass = 1;
+ break;
+ default:
+ dev_err(dev, "unknown mode is requested\n");
+ return -EINVAL;
+ }
+
+ if (!conn && hub->connect)
+ gpiod_set_value_cansleep(hub->connect, 0);
+
+ if (hub->reset)
+ gpiod_set_value_cansleep(hub->reset, rst);
+
+ if (hub->bypass)
+ gpiod_set_value_cansleep(hub->bypass, bypass);
+
+ if (conn) {
+ /* Wait T_HUBINIT == 4ms for hub logic to stabilize */
+ usleep_range(4000, 10000);
+ return usb3503_connect(hub);
+ }
+
+ return 0;
+}
+
+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->bypass = devm_gpiod_get_optional(dev, "bypass", GPIOD_OUT_HIGH);
+ if (IS_ERR(hub->bypass)) {
+ err = PTR_ERR(hub->bypass);
+ goto err_clk;
+ }
+ if (hub->bypass)
+ gpiod_set_consumer_name(hub->bypass, "usb3503 bypass");
+
+ 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)
+{
+ 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 void usb3503_platform_remove(struct platform_device *pdev)
+{
+ struct usb3503 *hub;
+
+ hub = platform_get_drvdata(pdev);
+ clk_disable_unprepare(hub->clk);
+}
+
+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[] = {
+ { .name = USB3503_I2C_NAME },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, usb3503_id);
+
+#ifdef CONFIG_OF
+static const struct of_device_id usb3503_of_match[] = {
+ { .compatible = "smsc,usb3503", },
+ { .compatible = "smsc,usb3503a", },
+ { .compatible = "smsc,usb3803", },
+ {},
+};
+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..2ae965671
--- /dev/null
+++ b/drivers/usb/misc/usb4604.c
@@ -0,0 +1,163 @@
+// 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)
+{
+ 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[] = {
+ { .name = "usb4604" },
+ { }
+};
+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/usbio.c b/drivers/usb/misc/usbio.c
new file mode 100644
index 000000000..fe093e776
--- /dev/null
+++ b/drivers/usb/misc/usbio.c
@@ -0,0 +1,756 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Intel USBIO Bridge driver
+ *
+ * Copyright (c) 2025 Intel Corporation.
+ * Copyright (c) 2025 Red Hat, Inc.
+ */
+
+#include <linux/acpi.h>
+#include <linux/auxiliary_bus.h>
+#include <linux/byteorder/generic.h>
+#include <linux/cleanup.h>
+#include <linux/completion.h>
+#include <linux/dev_printk.h>
+#include <linux/device.h>
+#include <linux/lockdep.h>
+#include <linux/mutex.h>
+#include <linux/string.h>
+#include <linux/types.h>
+#include <linux/usb.h>
+#include <linux/usb/usbio.h>
+
+/*************************************
+ * USBIO Bridge Protocol Definitions *
+ *************************************/
+
+/* USBIO Control Commands */
+#define USBIO_CTRLCMD_PROTVER 0
+#define USBIO_CTRLCMD_FWVER 1
+#define USBIO_CTRLCMD_HS 2
+#define USBIO_CTRLCMD_ENUMGPIO 16
+#define USBIO_CTRLCMD_ENUMI2C 17
+
+/* USBIO Packet Flags */
+#define USBIO_PKTFLAG_ACK BIT(0)
+#define USBIO_PKTFLAG_RSP BIT(1)
+#define USBIO_PKTFLAG_CMP BIT(2)
+#define USBIO_PKTFLAG_ERR BIT(3)
+
+#define USBIO_PKTFLAGS_REQRESP (USBIO_PKTFLAG_CMP | USBIO_PKTFLAG_ACK)
+
+#define USBIO_CTRLXFER_TIMEOUT 0
+#define USBIO_BULKXFER_TIMEOUT 100
+
+struct usbio_protver {
+ u8 ver;
+} __packed;
+
+struct usbio_fwver {
+ u8 major;
+ u8 minor;
+ __le16 patch;
+ __le16 build;
+} __packed;
+
+/***********************************
+ * USBIO Bridge Device Definitions *
+ ***********************************/
+
+/**
+ * struct usbio_device - the usb device exposing IOs
+ *
+ * @dev: the device in the usb interface
+ * @udev: the detected usb device
+ * @intf: the usb interface
+ * @quirks: quirks
+ * @ctrl_mutex: protects ctrl_buf
+ * @ctrl_pipe: the control transfer pipe
+ * @ctrlbuf_len: the size of the control transfer pipe
+ * @ctrlbuf: the buffer used for control transfers
+ * @bulk_mutex: protects tx_buf, rx_buf and split bulk-transfers getting interrupted
+ * @tx_pipe: the bulk out pipe
+ * @txbuf_len: the size of the bulk out pipe
+ * @txbuf: the buffer used for bulk out transfers
+ * @rx_pipe: the bulk in pipe
+ * @rxbuf_len: the size of the bulk in pipe
+ * @rxdat_len: the data length at rx buffer
+ * @rxbuf: the buffer used for bulk in transfers
+ * @urb: the urb to read bulk pipe
+ * @done: completion object as request is done
+ * @cli_list: device's client list
+ * @nr_gpio_banks: Number of GPIO banks
+ * @gpios: GPIO bank descriptors
+ * @nr_gpio_banks: Number of I2C busses
+ * @gpios: I2C bank descriptors
+ */
+struct usbio_device {
+ struct device *dev;
+ struct usb_device *udev;
+ struct usb_interface *intf;
+ unsigned long quirks;
+
+ struct mutex ctrl_mutex;
+ unsigned int ctrl_pipe;
+ u16 ctrlbuf_len;
+ void *ctrlbuf;
+
+ struct mutex bulk_mutex;
+ unsigned int tx_pipe;
+ u16 txbuf_len;
+ void *txbuf;
+
+ unsigned int rx_pipe;
+ u16 rxbuf_len;
+ u16 rxdat_len;
+ void *rxbuf;
+ struct urb *urb;
+
+ struct completion done;
+
+ struct list_head cli_list;
+
+ unsigned int nr_gpio_banks;
+ struct usbio_gpio_bank_desc gpios[USBIO_MAX_GPIOBANKS];
+
+ unsigned int nr_i2c_buses;
+ struct usbio_i2c_bus_desc i2cs[USBIO_MAX_I2CBUSES];
+};
+
+/**
+ * struct usbio_client - represents a usbio client
+ *
+ * @auxdev: auxiliary device object
+ * @mutex: protects @bridge
+ * @bridge: usbio bridge who service the client
+ * @link: usbio bridge clients list member
+ */
+struct usbio_client {
+ struct auxiliary_device auxdev;
+ struct mutex mutex;
+ struct usbio_device *bridge;
+ struct list_head link;
+};
+
+#define adev_to_client(adev) container_of_const(adev, struct usbio_client, auxdev)
+
+static int usbio_ctrl_msg(struct usbio_device *usbio, u8 type, u8 cmd,
+ const void *obuf, u16 obuf_len, void *ibuf, u16 ibuf_len)
+{
+ u8 request = USB_TYPE_VENDOR | USB_RECIP_DEVICE;
+ struct usbio_ctrl_packet *cpkt;
+ unsigned int pipe;
+ u16 cpkt_len;
+ int ret;
+
+ lockdep_assert_held(&usbio->ctrl_mutex);
+
+ if ((obuf_len > (usbio->ctrlbuf_len - sizeof(*cpkt))) ||
+ (ibuf_len > (usbio->ctrlbuf_len - sizeof(*cpkt))))
+ return -EMSGSIZE;
+
+ /* Prepare Control Packet Header */
+ cpkt = usbio->ctrlbuf;
+ cpkt->header.type = type;
+ cpkt->header.cmd = cmd;
+ if (type == USBIO_PKTTYPE_CTRL || ibuf_len)
+ cpkt->header.flags = USBIO_PKTFLAGS_REQRESP;
+ else
+ cpkt->header.flags = USBIO_PKTFLAG_CMP;
+ cpkt->len = obuf_len;
+
+ /* Copy the data */
+ memcpy(cpkt->data, obuf, obuf_len);
+
+ pipe = usb_sndctrlpipe(usbio->udev, usbio->ctrl_pipe);
+ cpkt_len = sizeof(*cpkt) + obuf_len;
+ ret = usb_control_msg(usbio->udev, pipe, 0, request | USB_DIR_OUT, 0, 0,
+ cpkt, cpkt_len, USBIO_CTRLXFER_TIMEOUT);
+ dev_dbg(usbio->dev, "control out %d hdr %*phN data %*phN\n", ret,
+ (int)sizeof(*cpkt), cpkt, (int)cpkt->len, cpkt->data);
+
+ if (ret != cpkt_len) {
+ dev_err(usbio->dev, "USB control out failed: %d\n", ret);
+ return (ret < 0) ? ret : -EPROTO;
+ }
+
+ if (!(cpkt->header.flags & USBIO_PKTFLAG_ACK))
+ return 0;
+
+ pipe = usb_rcvctrlpipe(usbio->udev, usbio->ctrl_pipe);
+ cpkt_len = sizeof(*cpkt) + ibuf_len;
+ ret = usb_control_msg(usbio->udev, pipe, 0, request | USB_DIR_IN, 0, 0,
+ cpkt, cpkt_len, USBIO_CTRLXFER_TIMEOUT);
+ dev_dbg(usbio->dev, "control in %d hdr %*phN data %*phN\n", ret,
+ (int)sizeof(*cpkt), cpkt, (int)cpkt->len, cpkt->data);
+
+ if (ret < sizeof(*cpkt)) {
+ dev_err(usbio->dev, "USB control in failed: %d\n", ret);
+ return (ret < 0) ? ret : -EPROTO;
+ }
+
+ if (cpkt->header.type != type || cpkt->header.cmd != cmd ||
+ !(cpkt->header.flags & USBIO_PKTFLAG_RSP)) {
+ dev_err(usbio->dev, "Unexpected reply type: %u, cmd: %u, flags: %u\n",
+ cpkt->header.type, cpkt->header.cmd, cpkt->header.flags);
+ return -EPROTO;
+ }
+
+ if (cpkt->header.flags & USBIO_PKTFLAG_ERR)
+ return -EREMOTEIO;
+
+ if (ibuf_len < cpkt->len)
+ return -ENOSPC;
+
+ memcpy(ibuf, cpkt->data, cpkt->len);
+
+ return cpkt->len;
+}
+
+int usbio_control_msg(struct auxiliary_device *adev, u8 type, u8 cmd,
+ const void *obuf, u16 obuf_len, void *ibuf, u16 ibuf_len)
+{
+ struct usbio_client *client = adev_to_client(adev);
+ struct usbio_device *usbio;
+ int ret;
+
+ guard(mutex)(&client->mutex);
+
+ usbio = client->bridge;
+ if (!usbio)
+ return -ENODEV; /* Disconnected */
+
+ ret = usb_autopm_get_interface(usbio->intf);
+ if (ret)
+ return ret;
+
+ mutex_lock(&usbio->ctrl_mutex);
+
+ ret = usbio_ctrl_msg(client->bridge, type, cmd, obuf, obuf_len, ibuf, ibuf_len);
+
+ mutex_unlock(&usbio->ctrl_mutex);
+ usb_autopm_put_interface(usbio->intf);
+
+ return ret;
+}
+EXPORT_SYMBOL_NS_GPL(usbio_control_msg, "USBIO");
+
+static void usbio_bulk_recv(struct urb *urb)
+{
+ struct usbio_bulk_packet *bpkt = urb->transfer_buffer;
+ struct usbio_device *usbio = urb->context;
+
+ if (!urb->status) {
+ if (bpkt->header.flags & USBIO_PKTFLAG_RSP) {
+ usbio->rxdat_len = urb->actual_length;
+ complete(&usbio->done);
+ }
+ } else if (urb->status != -ENOENT) {
+ dev_err(usbio->dev, "Bulk in error %d\n", urb->status);
+ }
+
+ usb_submit_urb(usbio->urb, GFP_ATOMIC);
+}
+
+int usbio_bulk_msg(struct auxiliary_device *adev, u8 type, u8 cmd, bool last,
+ const void *obuf, u16 obuf_len, void *ibuf, u16 ibuf_len)
+{
+ struct usbio_client *client = adev_to_client(adev);
+ struct usbio_device *usbio = client->bridge;
+ struct usbio_bulk_packet *bpkt;
+ int ret, act = 0;
+ u16 bpkt_len;
+
+ lockdep_assert_held(&client->mutex);
+ lockdep_assert_held(&usbio->bulk_mutex);
+
+ if ((obuf_len > (usbio->txbuf_len - sizeof(*bpkt))) ||
+ (ibuf_len > (usbio->rxbuf_len - sizeof(*bpkt))))
+ return -EMSGSIZE;
+
+ if (ibuf_len)
+ reinit_completion(&usbio->done);
+
+ /* If no data to send, skip to read */
+ if (!obuf_len)
+ goto read;
+
+ /* Prepare Bulk Packet Header */
+ bpkt = usbio->txbuf;
+ bpkt->header.type = type;
+ bpkt->header.cmd = cmd;
+ if (!last)
+ bpkt->header.flags = 0;
+ else if (ibuf_len)
+ bpkt->header.flags = USBIO_PKTFLAGS_REQRESP;
+ else
+ bpkt->header.flags = USBIO_PKTFLAG_CMP;
+ bpkt->len = cpu_to_le16(obuf_len);
+
+ /* Copy the data */
+ memcpy(bpkt->data, obuf, obuf_len);
+
+ bpkt_len = sizeof(*bpkt) + obuf_len;
+ ret = usb_bulk_msg(usbio->udev, usbio->tx_pipe, bpkt, bpkt_len, &act,
+ USBIO_BULKXFER_TIMEOUT);
+ dev_dbg(usbio->dev, "bulk out %d hdr %*phN data %*phN\n", act,
+ (int)sizeof(*bpkt), bpkt, obuf_len, bpkt->data);
+
+ if (ret || act != bpkt_len) {
+ dev_err(usbio->dev, "Bulk out failed: %d\n", ret);
+ return ret ?: -EPROTO;
+ }
+
+ if (!(bpkt->header.flags & USBIO_PKTFLAG_ACK))
+ return obuf_len;
+
+read:
+ ret = wait_for_completion_timeout(&usbio->done, USBIO_BULKXFER_TIMEOUT);
+ if (ret <= 0) {
+ dev_err(usbio->dev, "Bulk in wait failed: %d\n", ret);
+ return ret ?: -ETIMEDOUT;
+ }
+
+ act = usbio->rxdat_len;
+ bpkt = usbio->rxbuf;
+ bpkt_len = le16_to_cpu(bpkt->len);
+ dev_dbg(usbio->dev, "bulk in %d hdr %*phN data %*phN\n", act,
+ (int)sizeof(*bpkt), bpkt, bpkt_len, bpkt->data);
+
+ /*
+ * Unsupported bulk commands get only an usbio_packet_header with
+ * the error flag set as reply. Return -EPIPE for this case.
+ */
+ if (act == sizeof(struct usbio_packet_header) &&
+ (bpkt->header.flags & USBIO_PKTFLAG_ERR))
+ return -EPIPE;
+
+ if (act < sizeof(*bpkt)) {
+ dev_err(usbio->dev, "Bulk in short read: %d\n", act);
+ return -EPROTO;
+ }
+
+ if (bpkt->header.type != type || bpkt->header.cmd != cmd ||
+ !(bpkt->header.flags & USBIO_PKTFLAG_RSP)) {
+ dev_err(usbio->dev,
+ "Unexpected bulk in type 0x%02x cmd 0x%02x flags 0x%02x\n",
+ bpkt->header.type, bpkt->header.cmd, bpkt->header.flags);
+ return -EPROTO;
+ }
+
+ if (bpkt->header.flags & USBIO_PKTFLAG_ERR)
+ return -EREMOTEIO;
+
+ if (ibuf_len < bpkt_len)
+ return -ENOSPC;
+
+ /* The device must not claim more payload than it actually sent. */
+ if (bpkt_len > act - sizeof(*bpkt))
+ return -EPROTO;
+
+ memcpy(ibuf, bpkt->data, bpkt_len);
+
+ return bpkt_len;
+}
+EXPORT_SYMBOL_NS_GPL(usbio_bulk_msg, "USBIO");
+
+int usbio_acquire(struct auxiliary_device *adev)
+{
+ struct usbio_client *client = adev_to_client(adev);
+ struct usbio_device *usbio;
+ int ret;
+
+ mutex_lock(&client->mutex);
+
+ usbio = client->bridge;
+ if (!usbio) {
+ ret = -ENODEV; /* Disconnected */
+ goto err_unlock;
+ }
+
+ ret = usb_autopm_get_interface(usbio->intf);
+ if (ret)
+ goto err_unlock;
+
+ mutex_lock(&usbio->bulk_mutex);
+
+ /* Leave client locked until release to avoid abba deadlock issues */
+ return 0;
+
+err_unlock:
+ mutex_unlock(&client->mutex);
+
+ return ret;
+}
+EXPORT_SYMBOL_NS_GPL(usbio_acquire, "USBIO");
+
+void usbio_release(struct auxiliary_device *adev)
+{
+ struct usbio_client *client = adev_to_client(adev);
+ struct usbio_device *usbio = client->bridge;
+
+ lockdep_assert_held(&client->mutex);
+
+ mutex_unlock(&usbio->bulk_mutex);
+ usb_autopm_put_interface(usbio->intf);
+ mutex_unlock(&client->mutex);
+}
+EXPORT_SYMBOL_NS_GPL(usbio_release, "USBIO");
+
+void usbio_get_txrxbuf_len(struct auxiliary_device *adev, u16 *txbuf_len, u16 *rxbuf_len)
+{
+ struct usbio_client *client = adev_to_client(adev);
+ struct usbio_device *usbio;
+
+ guard(mutex)(&client->mutex);
+
+ usbio = client->bridge;
+ if (!usbio)
+ return; /* Disconnected */
+
+ *txbuf_len = usbio->txbuf_len;
+ *rxbuf_len = usbio->rxbuf_len;
+}
+EXPORT_SYMBOL_NS_GPL(usbio_get_txrxbuf_len, "USBIO");
+
+unsigned long usbio_get_quirks(struct auxiliary_device *adev)
+{
+ struct usbio_client *client = adev_to_client(adev);
+ struct usbio_device *usbio;
+
+ guard(mutex)(&client->mutex);
+
+ usbio = client->bridge;
+ if (!usbio)
+ return 0; /* Disconnected */
+
+ return usbio->quirks;
+}
+EXPORT_SYMBOL_NS_GPL(usbio_get_quirks, "USBIO");
+
+static void usbio_auxdev_release(struct device *dev)
+{
+ struct auxiliary_device *adev = to_auxiliary_dev(dev);
+ struct usbio_client *client = adev_to_client(adev);
+
+ mutex_destroy(&client->mutex);
+ kfree(client);
+}
+
+static int usbio_add_client(struct usbio_device *usbio, char *name, u8 id, void *data)
+{
+ struct usbio_client *client;
+ struct auxiliary_device *adev;
+ int ret;
+
+ client = kzalloc_obj(*client);
+ if (!client)
+ return -ENOMEM;
+
+ mutex_init(&client->mutex);
+ client->bridge = usbio;
+ adev = &client->auxdev;
+ adev->name = name;
+ adev->id = id;
+
+ adev->dev.parent = usbio->dev;
+ adev->dev.platform_data = data;
+ adev->dev.release = usbio_auxdev_release;
+
+ ret = auxiliary_device_init(adev);
+ if (ret) {
+ usbio_auxdev_release(&adev->dev);
+ return ret;
+ }
+
+ ret = auxiliary_device_add(adev);
+ if (ret) {
+ auxiliary_device_uninit(adev);
+ return ret;
+ }
+
+ list_add_tail(&client->link, &usbio->cli_list);
+
+ return 0;
+}
+
+static int usbio_enum_gpios(struct usbio_device *usbio)
+{
+ struct usbio_gpio_bank_desc *gpio = usbio->gpios;
+
+ dev_dbg(usbio->dev, "GPIO Banks: %d\n", usbio->nr_gpio_banks);
+
+ for (unsigned int i = 0; i < usbio->nr_gpio_banks; i++)
+ dev_dbg(usbio->dev, "\tBank%d[%d] map: %#08x\n",
+ gpio[i].id, gpio[i].pins, gpio[i].bmap);
+
+ usbio_add_client(usbio, USBIO_GPIO_CLIENT, 0, gpio);
+
+ return 0;
+}
+
+static int usbio_enum_i2cs(struct usbio_device *usbio)
+{
+ struct usbio_i2c_bus_desc *i2c = usbio->i2cs;
+
+ dev_dbg(usbio->dev, "I2C Busses: %d\n", usbio->nr_i2c_buses);
+
+ for (unsigned int i = 0; i < usbio->nr_i2c_buses; i++) {
+ dev_dbg(usbio->dev, "\tBus%d caps: %#02x\n", i2c[i].id, i2c[i].caps);
+ usbio_add_client(usbio, USBIO_I2C_CLIENT, i, &i2c[i]);
+ }
+
+ return 0;
+}
+
+static int usbio_suspend(struct usb_interface *intf, pm_message_t msg)
+{
+ struct usbio_device *usbio = usb_get_intfdata(intf);
+
+ usb_kill_urb(usbio->urb);
+
+ return 0;
+}
+
+static int usbio_resume(struct usb_interface *intf)
+{
+ struct usbio_device *usbio = usb_get_intfdata(intf);
+
+ return usb_submit_urb(usbio->urb, GFP_KERNEL);
+}
+
+static void usbio_disconnect(struct usb_interface *intf)
+{
+ struct usbio_device *usbio = usb_get_intfdata(intf);
+ struct usbio_client *client, *next;
+
+ /* Wakeup any clients waiting for a reply */
+ usbio->rxdat_len = 0;
+ complete(&usbio->done);
+
+ /* Let clients know the bridge is gone */
+ list_for_each_entry(client, &usbio->cli_list, link) {
+ mutex_lock(&client->mutex);
+ client->bridge = NULL;
+ mutex_unlock(&client->mutex);
+ }
+
+ /* From here on clients will no longer touch struct usbio_device */
+ usb_kill_urb(usbio->urb);
+ usb_free_urb(usbio->urb);
+
+ list_for_each_entry_safe_reverse(client, next, &usbio->cli_list, link) {
+ auxiliary_device_delete(&client->auxdev);
+ auxiliary_device_uninit(&client->auxdev);
+ }
+}
+
+static int usbio_probe(struct usb_interface *intf, const struct usb_device_id *id)
+{
+ struct usb_device *udev = interface_to_usbdev(intf);
+ struct usb_endpoint_descriptor *ep_in, *ep_out;
+ struct device *dev = &intf->dev;
+ struct usbio_protver protver;
+ struct usbio_device *usbio;
+ struct usbio_fwver fwver;
+ int ret;
+
+ usbio = devm_kzalloc(dev, sizeof(*usbio), GFP_KERNEL);
+ if (!usbio)
+ return -ENOMEM;
+
+ ret = devm_mutex_init(dev, &usbio->ctrl_mutex);
+ if (ret)
+ return ret;
+
+ ret = devm_mutex_init(dev, &usbio->bulk_mutex);
+ if (ret)
+ return ret;
+
+ usbio->dev = dev;
+ usbio->udev = udev;
+ usbio->intf = intf;
+ usbio->quirks = id ? id->driver_info : 0;
+ init_completion(&usbio->done);
+ INIT_LIST_HEAD(&usbio->cli_list);
+ usb_set_intfdata(intf, usbio);
+
+ usbio->ctrl_pipe = usb_endpoint_num(&udev->ep0.desc);
+ usbio->ctrlbuf_len = usb_maxpacket(udev, usbio->ctrl_pipe);
+ usbio->ctrlbuf = devm_kzalloc(dev, usbio->ctrlbuf_len, GFP_KERNEL);
+ if (!usbio->ctrlbuf)
+ return -ENOMEM;
+
+ /* Find the first bulk-in and bulk-out endpoints */
+ ret = usb_find_common_endpoints(intf->cur_altsetting, &ep_in, &ep_out,
+ NULL, NULL);
+ if (ret) {
+ dev_err(dev, "Cannot find bulk endpoints: %d\n", ret);
+ return ret;
+ }
+
+ usbio->tx_pipe = usb_sndbulkpipe(udev, usb_endpoint_num(ep_out));
+
+ if (usbio->quirks & USBIO_QUIRK_BULK_MAXP_63)
+ usbio->txbuf_len = 63;
+ else
+ usbio->txbuf_len = usb_endpoint_maxp(ep_out);
+
+ usbio->txbuf = devm_kzalloc(dev, usbio->txbuf_len, GFP_KERNEL);
+ if (!usbio->txbuf)
+ return -ENOMEM;
+
+ usbio->rx_pipe = usb_rcvbulkpipe(udev, usb_endpoint_num(ep_in));
+
+ if (usbio->quirks & USBIO_QUIRK_BULK_MAXP_63)
+ usbio->rxbuf_len = 63;
+ else
+ usbio->rxbuf_len = usb_endpoint_maxp(ep_in);
+
+ usbio->rxbuf = devm_kzalloc(dev, usbio->rxbuf_len, GFP_KERNEL);
+ if (!usbio->rxbuf)
+ return -ENOMEM;
+
+ usbio->urb = usb_alloc_urb(0, GFP_KERNEL);
+ if (!usbio->urb)
+ return -ENOMEM;
+
+ usb_fill_bulk_urb(usbio->urb, udev, usbio->rx_pipe, usbio->rxbuf,
+ usbio->rxbuf_len, usbio_bulk_recv, usbio);
+ ret = usb_submit_urb(usbio->urb, GFP_KERNEL);
+ if (ret) {
+ dev_err_probe(dev, ret, "Submitting usb urb\n");
+ goto err_free_urb;
+ }
+
+ mutex_lock(&usbio->ctrl_mutex);
+
+ ret = usbio_ctrl_msg(usbio, USBIO_PKTTYPE_CTRL, USBIO_CTRLCMD_HS, NULL, 0, NULL, 0);
+ if (ret < 0)
+ goto err_unlock;
+
+ ret = usbio_ctrl_msg(usbio, USBIO_PKTTYPE_CTRL, USBIO_CTRLCMD_PROTVER, NULL, 0,
+ &protver, sizeof(protver));
+ if (ret < 0)
+ goto err_unlock;
+
+ ret = usbio_ctrl_msg(usbio, USBIO_PKTTYPE_CTRL, USBIO_CTRLCMD_FWVER, NULL, 0,
+ &fwver, sizeof(fwver));
+ if (ret < 0)
+ goto err_unlock;
+
+ ret = usbio_ctrl_msg(usbio, USBIO_PKTTYPE_CTRL, USBIO_CTRLCMD_ENUMGPIO, NULL, 0,
+ usbio->gpios, sizeof(usbio->gpios));
+ if (ret < 0 || ret % sizeof(struct usbio_gpio_bank_desc)) {
+ ret = (ret < 0) ? ret : -EPROTO;
+ goto err_unlock;
+ }
+ usbio->nr_gpio_banks = ret / sizeof(struct usbio_gpio_bank_desc);
+
+ ret = usbio_ctrl_msg(usbio, USBIO_PKTTYPE_CTRL, USBIO_CTRLCMD_ENUMI2C, NULL, 0,
+ usbio->i2cs, sizeof(usbio->i2cs));
+ if (ret < 0 || ret % sizeof(struct usbio_i2c_bus_desc)) {
+ ret = (ret < 0) ? ret : -EPROTO;
+ goto err_unlock;
+ }
+ usbio->nr_i2c_buses = ret / sizeof(struct usbio_i2c_bus_desc);
+
+ mutex_unlock(&usbio->ctrl_mutex);
+
+ dev_dbg(dev, "ProtVer(BCD): %02x FwVer: %d.%d.%d.%d\n",
+ protver.ver, fwver.major, fwver.minor,
+ le16_to_cpu(fwver.patch), le16_to_cpu(fwver.build));
+
+ usbio_enum_gpios(usbio);
+ usbio_enum_i2cs(usbio);
+
+ return 0;
+
+err_unlock:
+ mutex_unlock(&usbio->ctrl_mutex);
+ usb_kill_urb(usbio->urb);
+err_free_urb:
+ usb_free_urb(usbio->urb);
+
+ return ret;
+}
+
+static const struct usb_device_id usbio_table[] = {
+ { USB_DEVICE(0x2ac1, 0x20c1), /* Lattice NX40 */
+ .driver_info = USBIO_QUIRK_I2C_MAX_RW_LEN_52 },
+ { USB_DEVICE(0x2ac1, 0x20c9), /* Lattice NX33 */
+ .driver_info = USBIO_QUIRK_I2C_NO_INIT_ACK | USBIO_QUIRK_I2C_MAX_RW_LEN_52 |
+ USBIO_QUIRK_I2C_ALLOW_400KHZ },
+ { USB_DEVICE(0x2ac1, 0x20cb) }, /* Lattice NX33U */
+ { USB_DEVICE(0x06cb, 0x0701), /* Synaptics Sabre */
+ .driver_info = USBIO_QUIRK_BULK_MAXP_63 | USBIO_QUIRK_I2C_USE_CHUNK_LEN },
+ { }
+};
+MODULE_DEVICE_TABLE(usb, usbio_table);
+
+static struct usb_driver usbio_driver = {
+ .name = "usbio-bridge",
+ .probe = usbio_probe,
+ .disconnect = usbio_disconnect,
+ .suspend = usbio_suspend,
+ .resume = usbio_resume,
+ .id_table = usbio_table,
+ .supports_autosuspend = 1,
+};
+module_usb_driver(usbio_driver);
+
+struct usbio_match_ids_walk_data {
+ struct acpi_device *adev;
+ const struct acpi_device_id *hids;
+ unsigned int id;
+};
+
+static int usbio_match_device_ids(struct acpi_device *adev, void *data)
+{
+ struct usbio_match_ids_walk_data *wd = data;
+ unsigned int id = 0;
+ char *uid;
+
+ if (acpi_match_device_ids(adev, wd->hids))
+ return 0;
+
+ uid = acpi_device_uid(adev);
+ if (uid) {
+ for (int i = 0; i < strlen(uid); i++) {
+ if (!kstrtouint(&uid[i], 10, &id))
+ break;
+ }
+ }
+
+ if (!uid || wd->id == id) {
+ wd->adev = adev;
+ return 1;
+ }
+
+ return 0;
+}
+
+void usbio_acpi_bind(struct auxiliary_device *adev, const struct acpi_device_id *hids)
+{
+ struct device *dev = &adev->dev;
+ struct acpi_device *parent;
+ struct usbio_match_ids_walk_data wd = {
+ .adev = NULL,
+ .hids = hids,
+ .id = adev->id,
+ };
+
+ parent = ACPI_COMPANION(dev->parent);
+ if (!parent)
+ return;
+
+ acpi_dev_for_each_child(parent, usbio_match_device_ids, &wd);
+ if (wd.adev)
+ ACPI_COMPANION_SET(dev, wd.adev);
+}
+EXPORT_SYMBOL_NS_GPL(usbio_acpi_bind, "USBIO");
+
+MODULE_DESCRIPTION("Intel USBIO Bridge driver");
+MODULE_AUTHOR("Israel Cepeda <israel.a.cepeda.lopez@intel.com>");
+MODULE_AUTHOR("Hans de Goede <hansg@kernel.org>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/usb/misc/usblcd.c b/drivers/usb/misc/usblcd.c
new file mode 100644
index 000000000..f794695de
--- /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_obj(*dev);
+ 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..89d25fcef
--- /dev/null
+++ b/drivers/usb/misc/usbsevseg.c
@@ -0,0 +1,395 @@
+// 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;
+ int rc = -ENOMEM;
+
+ mydev = kzalloc_obj(struct usb_sevsegdev);
+ if (!mydev)
+ goto error_mem;
+
+ mydev->udev = 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);
+ 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..8759df49b
--- /dev/null
+++ b/drivers/usb/misc/usbtest.c
@@ -0,0 +1,3080 @@
+// 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_objs(*sg, nents);
+ 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 = timer_container_of(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 (!timer_delete_sync(&timeout.timer))
+ retval = -ETIMEDOUT;
+ else
+ retval = req->status;
+ timer_destroy_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 < (int)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 = kzalloc_objs(struct urb *, param->sglen);
+ 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_obj(*reqp);
+ 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 = kzalloc_objs(struct urb *, num);
+ 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_t(unsigned int,
+ 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 = kzalloc_objs(*urbs, param->sglen);
+ 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_obj(*dev);
+ 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,
+ .no_dynamic_id = 1,
+};
+
+/*-------------------------------------------------------------------------*/
+
+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..bd0ad7c32
--- /dev/null
+++ b/drivers/usb/misc/uss720.c
@@ -0,0 +1,839 @@
+// 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_obj(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_obj(*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);
+ kref_put(&rq->ref_count, destroy_async);
+ 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 ret = -ENODEV;
+
+ 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)
+ goto bail_out_early;
+
+ ret = usb_set_interface(usbdev, intf->altsetting->desc.bInterfaceNumber, 2);
+ dev_dbg(&intf->dev, "set interface result %d\n", ret);
+
+ interface = intf->cur_altsetting;
+
+ if (interface->desc.bNumEndpoints < 2)
+ goto bail_out_early;
+
+ /*
+ * Allocate parport interface
+ */
+ ret = -ENOMEM;
+ priv = kzalloc_obj(struct parport_uss720_private);
+ if (!priv)
+ goto bail_out_early;
+
+ 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_COMPAT;
+ if (interface->desc.bNumEndpoints >= 3)
+ pp->modes |= PARPORT_MODE_ECP;
+ pp->dev = &usbdev->dev;
+
+ /* 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);
+
+ /* The Belkin F5U002 Rev 2 P80453-B USB parallel port adapter shares the
+ * device ID 050d:0002 with some other device that works with this
+ * driver, but it itself does not. Detect and handle the bad cable
+ * here. */
+ ret = get_1284_register(pp, 0, &reg, GFP_KERNEL);
+ dev_dbg(&intf->dev, "reg: %7ph\n", priv->reg);
+ if (ret < 0) {
+ priv->pp = NULL;
+ parport_del_port(pp);
+ goto probe_abort;
+ }
+
+ ret = usb_find_last_int_in_endpoint(interface, &epd);
+ if (!ret) {
+ 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;
+
+bail_out_early:
+ usb_put_dev(usbdev);
+ return ret;
+}
+
+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) }, /* Infowave 901-0030 */
+ { USB_DEVICE(0x04b8, 0x0002) }, /* Epson CAEUL0002 ISD-103 */
+ { USB_DEVICE(0x04b8, 0x0003) }, /* Epson ISD-101 */
+ { USB_DEVICE(0x050d, 0x0002) },
+ { USB_DEVICE(0x050d, 0x1202) }, /* Belkin F5U120-PC */
+ { USB_DEVICE(0x0557, 0x2001) },
+ { USB_DEVICE(0x05ab, 0x0002) }, /* Belkin F5U002 ISD-101 */
+ { USB_DEVICE(0x05ab, 0x1001) }, /* Belkin F5U002 P80453-A */
+ { USB_DEVICE(0x06c6, 0x0100) }, /* Infowave ISD-103 */
+ { 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..7a482cdee
--- /dev/null
+++ b/drivers/usb/misc/yurex.c
@@ -0,0 +1,537 @@
+// 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_obj(*dev);
+ 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;
+ dev->bbu = -1;
+ 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);
+
+ /* 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;
+ 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;
+
+ retval = mutex_lock_interruptible(&dev->io_mutex);
+ if (retval < 0)
+ return -EINTR;
+
+ 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) {
+ spin_lock_irq(&dev->lock);
+ dev->bbu = c2;
+ spin_unlock_irq(&dev->lock);
+ }
+ 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_DESCRIPTION("USB YUREX driver support");
+MODULE_LICENSE("GPL");