diff options
Diffstat (limited to 'drivers/usb/misc')
38 files changed, 19643 insertions, 0 deletions
diff --git a/drivers/usb/misc/Kconfig b/drivers/usb/misc/Kconfig new file mode 100644 index 0000000000..99b15b77df --- /dev/null +++ b/drivers/usb/misc/Kconfig @@ -0,0 +1,320 @@ +# 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 "QCOM Embedded USB Debugger(EUD) Driver" + depends on ARCH_QCOM || COMPILE_TEST + select USB_ROLE_SWITCH + help + This module enables support for Qualcomm Technologies, Inc. + Embedded USB Debugger (EUD). The EUD is a control peripheral + which reports VBUS attach/detach events and has USB-based + debug and trace capabilities. On selecting m, the module name + that is built is qcom_eud.ko + +config APPLE_MFI_FASTCHARGE + tristate "Fast charge control for iOS devices" + select POWER_SUPPLY + help + Say Y here if you want to control whether iOS devices will + fast charge from the USB interface, as implemented in "MFi" + chargers. + + It is safe to say M here. + +source "drivers/usb/misc/sisusbvga/Kconfig" + +config USB_LD + tristate "USB LD driver" + help + This driver is for generic USB devices that use interrupt transfers, + like LD Didactic's USB devices. + + To compile this driver as a module, choose M here: the + module will be called ldusb. + +config USB_TRANCEVIBRATOR + tristate "PlayStation 2 Trance Vibrator driver support" + help + Say Y here if you want to connect a PlayStation 2 Trance Vibrator + device to your computer's USB port. + + To compile this driver as a module, choose M here: the + module will be called trancevibrator. + +config USB_IOWARRIOR + tristate "IO Warrior driver support" + help + Say Y here if you want to support the IO Warrior devices from Code + Mercenaries. This includes support for the following devices: + IO Warrior 40 + IO Warrior 24 + IO Warrior 56 + IO Warrior 24 Power Vampire + + To compile this driver as a module, choose M here: the + module will be called iowarrior. + +config USB_TEST + tristate "USB testing driver" + help + This driver is for testing host controller software. It is used + with specialized device firmware for regression and stress testing, + to help prevent problems from cropping up with "real" drivers. + + See <http://www.linux-usb.org/usbtest/> for more information, + including sample test device firmware and "how to use it". + +config USB_EHSET_TEST_FIXTURE + tristate "USB EHSET Test Fixture driver" + help + Say Y here if you want to support the special test fixture device + used for the USB-IF Embedded Host High-Speed Electrical Test procedure. + + When the test fixture is connected, it can enumerate as one of several + VID/PID pairs. This driver then initiates a corresponding test mode on + the downstream port to which the test fixture is attached. + + See <http://www.usb.org/developers/onthego/EHSET_v1.01.pdf> for more + information. + +config USB_ISIGHTFW + tristate "iSight firmware loading support" + select FW_LOADER + help + This driver loads firmware for USB Apple iSight cameras, allowing + them to be driven by the USB video class driver available at + http://linux-uvc.berlios.de + + The firmware for this driver must be extracted from the MacOS + driver beforehand. Tools for doing so are available at + http://bersace03.free.fr + +config USB_YUREX + tristate "USB YUREX driver support" + help + Say Y here if you want to connect a YUREX to your computer's + USB port. The YUREX is a leg-shakes sensor. See + <http://bbu.kayac.com/en/> for further information. + This driver supports read/write of leg-shakes counter and + fasync for the counter update via a device file /dev/yurex*. + + To compile this driver as a module, choose M here: the + module will be called yurex. + +config USB_EZUSB_FX2 + tristate "Functions for loading firmware on EZUSB chips" + help + Say Y here if you need EZUSB device support. + (Cypress FX/FX2/FX2LP microcontrollers) + +config USB_HUB_USB251XB + tristate "USB251XB Hub Controller Configuration Driver" + depends on I2C + help + This option enables support for configuration via SMBus of the + Microchip USB251x/xBi USB 2.0 Hub Controller series. Configuration + parameters may be set in devicetree or platform data. + Say Y or M here if you need to configure such a device via SMBus. + +config USB_HSIC_USB3503 + tristate "USB3503 HSIC to USB20 Driver" + depends on I2C + select REGMAP_I2C + help + This option enables support for SMSC USB3503 HSIC to USB 2.0 Driver. + +config USB_HSIC_USB4604 + tristate "USB4604 HSIC to USB20 Driver" + depends on I2C + help + This option enables support for SMSC USB4604 HSIC to USB 2.0 Driver. + +config USB_LINK_LAYER_TEST + tristate "USB Link Layer Test driver" + help + This driver is for generating specific traffic for Super Speed Link + Layer Test Device. Say Y only when you want to conduct USB Super Speed + Link Layer Test for host controllers. + +config USB_CHAOSKEY + tristate "ChaosKey random number generator driver support" + depends on HW_RANDOM + help + Say Y here if you want to connect an AltusMetrum ChaosKey or + Araneus Alea I to your computer's USB port. These devices + are hardware random number generators which hook into the + kernel entropy pool to ensure a large supply of entropy for + /dev/random and /dev/urandom and also provides direct access + via /dev/chaoskeyX + + To compile this driver as a module, choose M here: the + module will be called chaoskey. + +config BRCM_USB_PINMAP + tristate "Broadcom pinmap driver support" + depends on (ARCH_BRCMSTB && PHY_BRCM_USB) || COMPILE_TEST + default ARCH_BRCMSTB && PHY_BRCM_USB + help + This option enables support for remapping some USB external + signals, which are typically on dedicated pins on the chip, + to any gpio. + +config USB_ONBOARD_HUB + tristate "Onboard USB hub support" + depends on OF + help + Say Y here if you want to support discrete onboard USB hubs that + don't require an additional control bus for initialization, but + need some non-trivial form of initialization, such as enabling a + power regulator. An example for such a hub is the Realtek + RTS5411. + + This driver can be used as a module but its state (module vs + builtin) must match the state of the USB subsystem. Enabling + this config will enable the driver and it will automatically + match the state of the USB subsystem. If this driver is a + module it will be called onboard_usb_hub. diff --git a/drivers/usb/misc/Makefile b/drivers/usb/misc/Makefile new file mode 100644 index 0000000000..1992cc284d --- /dev/null +++ b/drivers/usb/misc/Makefile @@ -0,0 +1,35 @@ +# 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_IDMOUSE) += idmouse.o +obj-$(CONFIG_USB_IOWARRIOR) += iowarrior.o +obj-$(CONFIG_USB_ISIGHTFW) += isight_firmware.o +obj-$(CONFIG_USB_LCD) += usblcd.o +obj-$(CONFIG_USB_LD) += ldusb.o +obj-$(CONFIG_USB_LEGOTOWER) += legousbtower.o +obj-$(CONFIG_USB_QCOM_EUD) += qcom_eud.o +obj-$(CONFIG_USB_TEST) += usbtest.o +obj-$(CONFIG_USB_EHSET_TEST_FIXTURE) += ehset.o +obj-$(CONFIG_USB_TRANCEVIBRATOR) += trancevibrator.o +obj-$(CONFIG_USB_USS720) += uss720.o +obj-$(CONFIG_USB_SEVSEG) += usbsevseg.o +obj-$(CONFIG_USB_YUREX) += yurex.o +obj-$(CONFIG_USB_HUB_USB251XB) += usb251xb.o +obj-$(CONFIG_USB_HSIC_USB3503) += usb3503.o +obj-$(CONFIG_USB_HSIC_USB4604) += usb4604.o +obj-$(CONFIG_USB_CHAOSKEY) += chaoskey.o + +obj-$(CONFIG_USB_SISUSBVGA) += sisusbvga/ +obj-$(CONFIG_USB_LINK_LAYER_TEST) += lvstest.o +obj-$(CONFIG_BRCM_USB_PINMAP) += brcmstb-usb-pinmap.o +obj-$(CONFIG_USB_ONBOARD_HUB) += onboard_usb_hub.o diff --git a/drivers/usb/misc/adutux.c b/drivers/usb/misc/adutux.c new file mode 100644 index 0000000000..ed6a19254d --- /dev/null +++ b/drivers/usb/misc/adutux.c @@ -0,0 +1,796 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * adutux - driver for ADU devices from Ontrak Control Systems + * This is an experimental driver. Use at your own risk. + * This driver is not supported by Ontrak Control Systems. + * + * Copyright (c) 2003 John Homppi (SCO, leave this notice here) + * + * derived from the Lego USB Tower driver 0.56: + * Copyright (c) 2003 David Glance <davidgsf@sourceforge.net> + * 2001 Juergen Stuber <stuber@loria.fr> + * that was derived from USB Skeleton driver - 0.5 + * Copyright (c) 2001 Greg Kroah-Hartman (greg@kroah.com) + * + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/kernel.h> +#include <linux/sched/signal.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/usb.h> +#include <linux/mutex.h> +#include <linux/uaccess.h> + +#define DRIVER_AUTHOR "John Homppi" +#define DRIVER_DESC "adutux (see www.ontrak.net)" + +/* Define these values to match your device */ +#define ADU_VENDOR_ID 0x0a07 +#define ADU_PRODUCT_ID 0x0064 + +/* table of devices that work with this driver */ +static const struct usb_device_id device_table[] = { + { USB_DEVICE(ADU_VENDOR_ID, ADU_PRODUCT_ID) }, /* ADU100 */ + { USB_DEVICE(ADU_VENDOR_ID, ADU_PRODUCT_ID+20) }, /* ADU120 */ + { USB_DEVICE(ADU_VENDOR_ID, ADU_PRODUCT_ID+30) }, /* ADU130 */ + { USB_DEVICE(ADU_VENDOR_ID, ADU_PRODUCT_ID+100) }, /* ADU200 */ + { USB_DEVICE(ADU_VENDOR_ID, ADU_PRODUCT_ID+108) }, /* ADU208 */ + { USB_DEVICE(ADU_VENDOR_ID, ADU_PRODUCT_ID+118) }, /* ADU218 */ + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, device_table); + +#ifdef CONFIG_USB_DYNAMIC_MINORS +#define ADU_MINOR_BASE 0 +#else +#define ADU_MINOR_BASE 67 +#endif + +/* we can have up to this number of device plugged in at once */ +#define MAX_DEVICES 16 + +#define COMMAND_TIMEOUT (2*HZ) + +/* + * The locking scheme is a vanilla 3-lock: + * adu_device.buflock: A spinlock, covers what IRQs touch. + * adutux_mutex: A Static lock to cover open_count. It would also cover + * any globals, but we don't have them in 2.6. + * adu_device.mtx: A mutex to hold across sleepers like copy_from_user. + * It covers all of adu_device, except the open_count + * and what .buflock covers. + */ + +/* Structure to hold all of our device specific stuff */ +struct adu_device { + struct mutex mtx; + struct usb_device *udev; /* save off the usb device pointer */ + struct usb_interface *interface; + unsigned int minor; /* the starting minor number for this device */ + char serial_number[8]; + + int open_count; /* number of times this port has been opened */ + unsigned long disconnected:1; + + char *read_buffer_primary; + int read_buffer_length; + char *read_buffer_secondary; + int secondary_head; + int secondary_tail; + spinlock_t buflock; + + wait_queue_head_t read_wait; + wait_queue_head_t write_wait; + + char *interrupt_in_buffer; + struct usb_endpoint_descriptor *interrupt_in_endpoint; + struct urb *interrupt_in_urb; + int read_urb_finished; + + char *interrupt_out_buffer; + struct usb_endpoint_descriptor *interrupt_out_endpoint; + struct urb *interrupt_out_urb; + int out_urb_finished; +}; + +static DEFINE_MUTEX(adutux_mutex); + +static struct usb_driver adu_driver; + +static inline void adu_debug_data(struct device *dev, const char *function, + int size, const unsigned char *data) +{ + dev_dbg(dev, "%s - length = %d, data = %*ph\n", + function, size, size, data); +} + +/* + * adu_abort_transfers + * aborts transfers and frees associated data structures + */ +static void adu_abort_transfers(struct adu_device *dev) +{ + unsigned long flags; + + if (dev->disconnected) + return; + + /* shutdown transfer */ + + /* XXX Anchor these instead */ + spin_lock_irqsave(&dev->buflock, flags); + if (!dev->read_urb_finished) { + spin_unlock_irqrestore(&dev->buflock, flags); + usb_kill_urb(dev->interrupt_in_urb); + } else + spin_unlock_irqrestore(&dev->buflock, flags); + + spin_lock_irqsave(&dev->buflock, flags); + if (!dev->out_urb_finished) { + spin_unlock_irqrestore(&dev->buflock, flags); + wait_event_timeout(dev->write_wait, dev->out_urb_finished, + COMMAND_TIMEOUT); + usb_kill_urb(dev->interrupt_out_urb); + } else + spin_unlock_irqrestore(&dev->buflock, flags); +} + +static void adu_delete(struct adu_device *dev) +{ + /* free data structures */ + usb_free_urb(dev->interrupt_in_urb); + usb_free_urb(dev->interrupt_out_urb); + kfree(dev->read_buffer_primary); + kfree(dev->read_buffer_secondary); + kfree(dev->interrupt_in_buffer); + kfree(dev->interrupt_out_buffer); + usb_put_dev(dev->udev); + kfree(dev); +} + +static void adu_interrupt_in_callback(struct urb *urb) +{ + struct adu_device *dev = urb->context; + int status = urb->status; + unsigned long flags; + + adu_debug_data(&dev->udev->dev, __func__, + urb->actual_length, urb->transfer_buffer); + + spin_lock_irqsave(&dev->buflock, flags); + + if (status != 0) { + if ((status != -ENOENT) && (status != -ECONNRESET) && + (status != -ESHUTDOWN)) { + dev_dbg(&dev->udev->dev, + "%s : nonzero status received: %d\n", + __func__, status); + } + goto exit; + } + + if (urb->actual_length > 0 && dev->interrupt_in_buffer[0] != 0x00) { + if (dev->read_buffer_length < + (4 * usb_endpoint_maxp(dev->interrupt_in_endpoint)) - + (urb->actual_length)) { + memcpy (dev->read_buffer_primary + + dev->read_buffer_length, + dev->interrupt_in_buffer, urb->actual_length); + + dev->read_buffer_length += urb->actual_length; + dev_dbg(&dev->udev->dev, "%s reading %d\n", __func__, + urb->actual_length); + } else { + dev_dbg(&dev->udev->dev, "%s : read_buffer overflow\n", + __func__); + } + } + +exit: + dev->read_urb_finished = 1; + spin_unlock_irqrestore(&dev->buflock, flags); + /* always wake up so we recover from errors */ + wake_up_interruptible(&dev->read_wait); +} + +static void adu_interrupt_out_callback(struct urb *urb) +{ + struct adu_device *dev = urb->context; + int status = urb->status; + unsigned long flags; + + adu_debug_data(&dev->udev->dev, __func__, + urb->actual_length, urb->transfer_buffer); + + if (status != 0) { + if ((status != -ENOENT) && + (status != -ESHUTDOWN) && + (status != -ECONNRESET)) { + dev_dbg(&dev->udev->dev, + "%s :nonzero status received: %d\n", __func__, + status); + } + return; + } + + spin_lock_irqsave(&dev->buflock, flags); + dev->out_urb_finished = 1; + wake_up(&dev->write_wait); + spin_unlock_irqrestore(&dev->buflock, flags); +} + +static int adu_open(struct inode *inode, struct file *file) +{ + struct adu_device *dev = NULL; + struct usb_interface *interface; + int subminor; + int retval; + + subminor = iminor(inode); + + retval = mutex_lock_interruptible(&adutux_mutex); + if (retval) + goto exit_no_lock; + + interface = usb_find_interface(&adu_driver, subminor); + if (!interface) { + pr_err("%s - error, can't find device for minor %d\n", + __func__, subminor); + retval = -ENODEV; + goto exit_no_device; + } + + dev = usb_get_intfdata(interface); + if (!dev) { + retval = -ENODEV; + goto exit_no_device; + } + + /* check that nobody else is using the device */ + if (dev->open_count) { + retval = -EBUSY; + goto exit_no_device; + } + + ++dev->open_count; + dev_dbg(&dev->udev->dev, "%s: open count %d\n", __func__, + dev->open_count); + + /* save device in the file's private structure */ + file->private_data = dev; + + /* initialize in direction */ + dev->read_buffer_length = 0; + + /* fixup first read by having urb waiting for it */ + usb_fill_int_urb(dev->interrupt_in_urb, dev->udev, + usb_rcvintpipe(dev->udev, + dev->interrupt_in_endpoint->bEndpointAddress), + dev->interrupt_in_buffer, + usb_endpoint_maxp(dev->interrupt_in_endpoint), + adu_interrupt_in_callback, dev, + dev->interrupt_in_endpoint->bInterval); + dev->read_urb_finished = 0; + if (usb_submit_urb(dev->interrupt_in_urb, GFP_KERNEL)) + dev->read_urb_finished = 1; + /* we ignore failure */ + /* end of fixup for first read */ + + /* initialize out direction */ + dev->out_urb_finished = 1; + + retval = 0; + +exit_no_device: + mutex_unlock(&adutux_mutex); +exit_no_lock: + return retval; +} + +static void adu_release_internal(struct adu_device *dev) +{ + /* decrement our usage count for the device */ + --dev->open_count; + dev_dbg(&dev->udev->dev, "%s : open count %d\n", __func__, + dev->open_count); + if (dev->open_count <= 0) { + adu_abort_transfers(dev); + dev->open_count = 0; + } +} + +static int adu_release(struct inode *inode, struct file *file) +{ + struct adu_device *dev; + int retval = 0; + + if (file == NULL) { + retval = -ENODEV; + goto exit; + } + + dev = file->private_data; + if (dev == NULL) { + retval = -ENODEV; + goto exit; + } + + mutex_lock(&adutux_mutex); /* not interruptible */ + + if (dev->open_count <= 0) { + dev_dbg(&dev->udev->dev, "%s : device not opened\n", __func__); + retval = -ENODEV; + goto unlock; + } + + adu_release_internal(dev); + if (dev->disconnected) { + /* the device was unplugged before the file was released */ + if (!dev->open_count) /* ... and we're the last user */ + adu_delete(dev); + } +unlock: + mutex_unlock(&adutux_mutex); +exit: + return retval; +} + +static ssize_t adu_read(struct file *file, __user char *buffer, size_t count, + loff_t *ppos) +{ + struct adu_device *dev; + size_t bytes_read = 0; + size_t bytes_to_read = count; + int retval = 0; + int timeout = 0; + int should_submit = 0; + unsigned long flags; + DECLARE_WAITQUEUE(wait, current); + + dev = file->private_data; + if (mutex_lock_interruptible(&dev->mtx)) + return -ERESTARTSYS; + + /* verify that the device wasn't unplugged */ + if (dev->disconnected) { + retval = -ENODEV; + pr_err("No device or device unplugged %d\n", retval); + goto exit; + } + + /* verify that some data was requested */ + if (count == 0) { + dev_dbg(&dev->udev->dev, "%s : read request of 0 bytes\n", + __func__); + goto exit; + } + + timeout = COMMAND_TIMEOUT; + dev_dbg(&dev->udev->dev, "%s : about to start looping\n", __func__); + while (bytes_to_read) { + size_t data_in_secondary = dev->secondary_tail - dev->secondary_head; + dev_dbg(&dev->udev->dev, + "%s : while, data_in_secondary=%zu, status=%d\n", + __func__, data_in_secondary, + dev->interrupt_in_urb->status); + + if (data_in_secondary) { + /* drain secondary buffer */ + size_t amount = min(bytes_to_read, data_in_secondary); + if (copy_to_user(buffer, dev->read_buffer_secondary+dev->secondary_head, amount)) { + retval = -EFAULT; + goto exit; + } + dev->secondary_head += amount; + bytes_read += amount; + bytes_to_read -= amount; + } else { + /* we check the primary buffer */ + spin_lock_irqsave (&dev->buflock, flags); + if (dev->read_buffer_length) { + /* we secure access to the primary */ + dev_dbg(&dev->udev->dev, + "%s : swap, read_buffer_length = %d\n", + __func__, dev->read_buffer_length); + swap(dev->read_buffer_primary, dev->read_buffer_secondary); + dev->secondary_head = 0; + dev->secondary_tail = dev->read_buffer_length; + dev->read_buffer_length = 0; + spin_unlock_irqrestore(&dev->buflock, flags); + /* we have a free buffer so use it */ + should_submit = 1; + } else { + /* even the primary was empty - we may need to do IO */ + if (!dev->read_urb_finished) { + /* somebody is doing IO */ + spin_unlock_irqrestore(&dev->buflock, flags); + dev_dbg(&dev->udev->dev, + "%s : submitted already\n", + __func__); + } else { + /* we must initiate input */ + dev_dbg(&dev->udev->dev, + "%s : initiate input\n", + __func__); + dev->read_urb_finished = 0; + spin_unlock_irqrestore(&dev->buflock, flags); + + usb_fill_int_urb(dev->interrupt_in_urb, dev->udev, + usb_rcvintpipe(dev->udev, + dev->interrupt_in_endpoint->bEndpointAddress), + dev->interrupt_in_buffer, + usb_endpoint_maxp(dev->interrupt_in_endpoint), + adu_interrupt_in_callback, + dev, + dev->interrupt_in_endpoint->bInterval); + retval = usb_submit_urb(dev->interrupt_in_urb, GFP_KERNEL); + if (retval) { + dev->read_urb_finished = 1; + if (retval == -ENOMEM) { + retval = bytes_read ? bytes_read : -ENOMEM; + } + dev_dbg(&dev->udev->dev, + "%s : submit failed\n", + __func__); + goto exit; + } + } + + /* we wait for I/O to complete */ + set_current_state(TASK_INTERRUPTIBLE); + add_wait_queue(&dev->read_wait, &wait); + spin_lock_irqsave(&dev->buflock, flags); + if (!dev->read_urb_finished) { + spin_unlock_irqrestore(&dev->buflock, flags); + timeout = schedule_timeout(COMMAND_TIMEOUT); + } else { + spin_unlock_irqrestore(&dev->buflock, flags); + set_current_state(TASK_RUNNING); + } + remove_wait_queue(&dev->read_wait, &wait); + + if (timeout <= 0) { + dev_dbg(&dev->udev->dev, + "%s : timeout\n", __func__); + retval = bytes_read ? bytes_read : -ETIMEDOUT; + goto exit; + } + + if (signal_pending(current)) { + dev_dbg(&dev->udev->dev, + "%s : signal pending\n", + __func__); + retval = bytes_read ? bytes_read : -EINTR; + goto exit; + } + } + } + } + + retval = bytes_read; + /* if the primary buffer is empty then use it */ + spin_lock_irqsave(&dev->buflock, flags); + if (should_submit && dev->read_urb_finished) { + dev->read_urb_finished = 0; + spin_unlock_irqrestore(&dev->buflock, flags); + usb_fill_int_urb(dev->interrupt_in_urb, dev->udev, + usb_rcvintpipe(dev->udev, + dev->interrupt_in_endpoint->bEndpointAddress), + dev->interrupt_in_buffer, + usb_endpoint_maxp(dev->interrupt_in_endpoint), + adu_interrupt_in_callback, + dev, + dev->interrupt_in_endpoint->bInterval); + if (usb_submit_urb(dev->interrupt_in_urb, GFP_KERNEL) != 0) + dev->read_urb_finished = 1; + /* we ignore failure */ + } else { + spin_unlock_irqrestore(&dev->buflock, flags); + } + +exit: + /* unlock the device */ + mutex_unlock(&dev->mtx); + + return retval; +} + +static ssize_t adu_write(struct file *file, const __user char *buffer, + size_t count, loff_t *ppos) +{ + DECLARE_WAITQUEUE(waita, current); + struct adu_device *dev; + size_t bytes_written = 0; + size_t bytes_to_write; + size_t buffer_size; + unsigned long flags; + int retval; + + dev = file->private_data; + + retval = mutex_lock_interruptible(&dev->mtx); + if (retval) + goto exit_nolock; + + /* verify that the device wasn't unplugged */ + if (dev->disconnected) { + retval = -ENODEV; + pr_err("No device or device unplugged %d\n", retval); + goto exit; + } + + /* verify that we actually have some data to write */ + if (count == 0) { + dev_dbg(&dev->udev->dev, "%s : write request of 0 bytes\n", + __func__); + goto exit; + } + + while (count > 0) { + add_wait_queue(&dev->write_wait, &waita); + set_current_state(TASK_INTERRUPTIBLE); + spin_lock_irqsave(&dev->buflock, flags); + if (!dev->out_urb_finished) { + spin_unlock_irqrestore(&dev->buflock, flags); + + mutex_unlock(&dev->mtx); + if (signal_pending(current)) { + dev_dbg(&dev->udev->dev, "%s : interrupted\n", + __func__); + set_current_state(TASK_RUNNING); + retval = -EINTR; + goto exit_onqueue; + } + if (schedule_timeout(COMMAND_TIMEOUT) == 0) { + dev_dbg(&dev->udev->dev, + "%s - command timed out.\n", __func__); + retval = -ETIMEDOUT; + goto exit_onqueue; + } + remove_wait_queue(&dev->write_wait, &waita); + retval = mutex_lock_interruptible(&dev->mtx); + if (retval) { + retval = bytes_written ? bytes_written : retval; + goto exit_nolock; + } + + dev_dbg(&dev->udev->dev, + "%s : in progress, count = %zd\n", + __func__, count); + } else { + spin_unlock_irqrestore(&dev->buflock, flags); + set_current_state(TASK_RUNNING); + remove_wait_queue(&dev->write_wait, &waita); + dev_dbg(&dev->udev->dev, "%s : sending, count = %zd\n", + __func__, count); + + /* write the data into interrupt_out_buffer from userspace */ + buffer_size = usb_endpoint_maxp(dev->interrupt_out_endpoint); + bytes_to_write = count > buffer_size ? buffer_size : count; + dev_dbg(&dev->udev->dev, + "%s : buffer_size = %zd, count = %zd, bytes_to_write = %zd\n", + __func__, buffer_size, count, bytes_to_write); + + if (copy_from_user(dev->interrupt_out_buffer, buffer, bytes_to_write) != 0) { + retval = -EFAULT; + goto exit; + } + + /* send off the urb */ + usb_fill_int_urb( + dev->interrupt_out_urb, + dev->udev, + usb_sndintpipe(dev->udev, dev->interrupt_out_endpoint->bEndpointAddress), + dev->interrupt_out_buffer, + bytes_to_write, + adu_interrupt_out_callback, + dev, + dev->interrupt_out_endpoint->bInterval); + dev->interrupt_out_urb->actual_length = bytes_to_write; + dev->out_urb_finished = 0; + retval = usb_submit_urb(dev->interrupt_out_urb, GFP_KERNEL); + if (retval < 0) { + dev->out_urb_finished = 1; + dev_err(&dev->udev->dev, "Couldn't submit " + "interrupt_out_urb %d\n", retval); + goto exit; + } + + buffer += bytes_to_write; + count -= bytes_to_write; + + bytes_written += bytes_to_write; + } + } + mutex_unlock(&dev->mtx); + return bytes_written; + +exit: + mutex_unlock(&dev->mtx); +exit_nolock: + return retval; + +exit_onqueue: + remove_wait_queue(&dev->write_wait, &waita); + return retval; +} + +/* file operations needed when we register this driver */ +static const struct file_operations adu_fops = { + .owner = THIS_MODULE, + .read = adu_read, + .write = adu_write, + .open = adu_open, + .release = adu_release, + .llseek = noop_llseek, +}; + +/* + * usb class driver info in order to get a minor number from the usb core, + * and to have the device registered with devfs and the driver core + */ +static struct usb_class_driver adu_class = { + .name = "usb/adutux%d", + .fops = &adu_fops, + .minor_base = ADU_MINOR_BASE, +}; + +/* + * adu_probe + * + * Called by the usb core when a new device is connected that it thinks + * this driver might be interested in. + */ +static int adu_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(interface); + struct adu_device *dev = NULL; + int retval = -ENOMEM; + int in_end_size; + int out_end_size; + int res; + + /* allocate memory for our device state and initialize it */ + dev = kzalloc(sizeof(struct adu_device), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + mutex_init(&dev->mtx); + spin_lock_init(&dev->buflock); + dev->udev = usb_get_dev(udev); + init_waitqueue_head(&dev->read_wait); + init_waitqueue_head(&dev->write_wait); + + res = usb_find_common_endpoints_reverse(interface->cur_altsetting, + NULL, NULL, + &dev->interrupt_in_endpoint, + &dev->interrupt_out_endpoint); + if (res) { + dev_err(&interface->dev, "interrupt endpoints not found\n"); + retval = res; + goto error; + } + + in_end_size = usb_endpoint_maxp(dev->interrupt_in_endpoint); + out_end_size = usb_endpoint_maxp(dev->interrupt_out_endpoint); + + dev->read_buffer_primary = kmalloc((4 * in_end_size), GFP_KERNEL); + if (!dev->read_buffer_primary) + goto error; + + /* debug code prime the buffer */ + memset(dev->read_buffer_primary, 'a', in_end_size); + memset(dev->read_buffer_primary + in_end_size, 'b', in_end_size); + memset(dev->read_buffer_primary + (2 * in_end_size), 'c', in_end_size); + memset(dev->read_buffer_primary + (3 * in_end_size), 'd', in_end_size); + + dev->read_buffer_secondary = kmalloc((4 * in_end_size), GFP_KERNEL); + if (!dev->read_buffer_secondary) + goto error; + + /* debug code prime the buffer */ + memset(dev->read_buffer_secondary, 'e', in_end_size); + memset(dev->read_buffer_secondary + in_end_size, 'f', in_end_size); + memset(dev->read_buffer_secondary + (2 * in_end_size), 'g', in_end_size); + memset(dev->read_buffer_secondary + (3 * in_end_size), 'h', in_end_size); + + dev->interrupt_in_buffer = kmalloc(in_end_size, GFP_KERNEL); + if (!dev->interrupt_in_buffer) + goto error; + + /* debug code prime the buffer */ + memset(dev->interrupt_in_buffer, 'i', in_end_size); + + dev->interrupt_in_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!dev->interrupt_in_urb) + goto error; + dev->interrupt_out_buffer = kmalloc(out_end_size, GFP_KERNEL); + if (!dev->interrupt_out_buffer) + goto error; + dev->interrupt_out_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!dev->interrupt_out_urb) + goto error; + + if (!usb_string(udev, udev->descriptor.iSerialNumber, dev->serial_number, + sizeof(dev->serial_number))) { + dev_err(&interface->dev, "Could not retrieve serial number\n"); + retval = -EIO; + goto error; + } + dev_dbg(&interface->dev, "serial_number=%s", dev->serial_number); + + /* we can register the device now, as it is ready */ + usb_set_intfdata(interface, dev); + + retval = usb_register_dev(interface, &adu_class); + + if (retval) { + /* something prevented us from registering this driver */ + dev_err(&interface->dev, "Not able to get a minor for this device.\n"); + usb_set_intfdata(interface, NULL); + goto error; + } + + dev->minor = interface->minor; + + /* let the user know what node this device is now attached to */ + dev_info(&interface->dev, "ADU%d %s now attached to /dev/usb/adutux%d\n", + le16_to_cpu(udev->descriptor.idProduct), dev->serial_number, + (dev->minor - ADU_MINOR_BASE)); + + return 0; + +error: + adu_delete(dev); + return retval; +} + +/* + * adu_disconnect + * + * Called by the usb core when the device is removed from the system. + */ +static void adu_disconnect(struct usb_interface *interface) +{ + struct adu_device *dev; + + dev = usb_get_intfdata(interface); + + usb_deregister_dev(interface, &adu_class); + + usb_poison_urb(dev->interrupt_in_urb); + usb_poison_urb(dev->interrupt_out_urb); + + mutex_lock(&adutux_mutex); + usb_set_intfdata(interface, NULL); + + mutex_lock(&dev->mtx); /* not interruptible */ + dev->disconnected = 1; + mutex_unlock(&dev->mtx); + + /* if the device is not opened, then we clean up right now */ + if (!dev->open_count) + adu_delete(dev); + + mutex_unlock(&adutux_mutex); +} + +/* usb specific object needed to register this driver with the usb subsystem */ +static struct usb_driver adu_driver = { + .name = "adutux", + .probe = adu_probe, + .disconnect = adu_disconnect, + .id_table = device_table, +}; + +module_usb_driver(adu_driver); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/misc/apple-mfi-fastcharge.c b/drivers/usb/misc/apple-mfi-fastcharge.c new file mode 100644 index 0000000000..ac8695195c --- /dev/null +++ b/drivers/usb/misc/apple-mfi-fastcharge.c @@ -0,0 +1,241 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Fast-charge control for Apple "MFi" devices + * + * Copyright (C) 2019 Bastien Nocera <hadess@hadess.net> + */ + +/* Standard include files */ +#include <linux/module.h> +#include <linux/power_supply.h> +#include <linux/slab.h> +#include <linux/usb.h> + +MODULE_AUTHOR("Bastien Nocera <hadess@hadess.net>"); +MODULE_DESCRIPTION("Fast-charge control for Apple \"MFi\" devices"); +MODULE_LICENSE("GPL"); + +#define TRICKLE_CURRENT_MA 0 +#define FAST_CURRENT_MA 2500 + +#define APPLE_VENDOR_ID 0x05ac /* Apple */ + +/* The product ID is defined as starting with 0x12nn, as per the + * "Choosing an Apple Device USB Configuration" section in + * release R9 (2012) of the "MFi Accessory Hardware Specification" + * + * To distinguish an Apple device, a USB host can check the device + * descriptor of attached USB devices for the following fields: + * ■ Vendor ID: 0x05AC + * ■ Product ID: 0x12nn + * + * Those checks will be done in .match() and .probe(). + */ + +static const struct usb_device_id mfi_fc_id_table[] = { + { .idVendor = APPLE_VENDOR_ID, + .match_flags = USB_DEVICE_ID_MATCH_VENDOR }, + {}, +}; + +MODULE_DEVICE_TABLE(usb, mfi_fc_id_table); + +/* Driver-local specific stuff */ +struct mfi_device { + struct usb_device *udev; + struct power_supply *battery; + int charge_type; +}; + +static int apple_mfi_fc_set_charge_type(struct mfi_device *mfi, + const union power_supply_propval *val) +{ + int current_ma; + int retval; + __u8 request_type; + + if (mfi->charge_type == val->intval) { + dev_dbg(&mfi->udev->dev, "charge type %d already set\n", + mfi->charge_type); + return 0; + } + + switch (val->intval) { + case POWER_SUPPLY_CHARGE_TYPE_TRICKLE: + current_ma = TRICKLE_CURRENT_MA; + break; + case POWER_SUPPLY_CHARGE_TYPE_FAST: + current_ma = FAST_CURRENT_MA; + break; + default: + return -EINVAL; + } + + request_type = USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE; + retval = usb_control_msg(mfi->udev, usb_sndctrlpipe(mfi->udev, 0), + 0x40, /* Vendor‐defined power request */ + request_type, + current_ma, /* wValue, current offset */ + current_ma, /* wIndex, current offset */ + NULL, 0, USB_CTRL_GET_TIMEOUT); + if (retval) { + dev_dbg(&mfi->udev->dev, "retval = %d\n", retval); + return retval; + } + + mfi->charge_type = val->intval; + + return 0; +} + +static int apple_mfi_fc_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct mfi_device *mfi = power_supply_get_drvdata(psy); + + dev_dbg(&mfi->udev->dev, "prop: %d\n", psp); + + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_TYPE: + val->intval = mfi->charge_type; + break; + case POWER_SUPPLY_PROP_SCOPE: + val->intval = POWER_SUPPLY_SCOPE_DEVICE; + break; + default: + return -ENODATA; + } + + return 0; +} + +static int apple_mfi_fc_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct mfi_device *mfi = power_supply_get_drvdata(psy); + int ret; + + dev_dbg(&mfi->udev->dev, "prop: %d\n", psp); + + ret = pm_runtime_get_sync(&mfi->udev->dev); + if (ret < 0) { + pm_runtime_put_noidle(&mfi->udev->dev); + return ret; + } + + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_TYPE: + ret = apple_mfi_fc_set_charge_type(mfi, val); + break; + default: + ret = -EINVAL; + } + + pm_runtime_mark_last_busy(&mfi->udev->dev); + pm_runtime_put_autosuspend(&mfi->udev->dev); + + return ret; +} + +static int apple_mfi_fc_property_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_TYPE: + return 1; + default: + return 0; + } +} + +static enum power_supply_property apple_mfi_fc_properties[] = { + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_SCOPE +}; + +static const struct power_supply_desc apple_mfi_fc_desc = { + .name = "apple_mfi_fastcharge", + .type = POWER_SUPPLY_TYPE_BATTERY, + .properties = apple_mfi_fc_properties, + .num_properties = ARRAY_SIZE(apple_mfi_fc_properties), + .get_property = apple_mfi_fc_get_property, + .set_property = apple_mfi_fc_set_property, + .property_is_writeable = apple_mfi_fc_property_is_writeable +}; + +static bool mfi_fc_match(struct usb_device *udev) +{ + int idProduct; + + idProduct = le16_to_cpu(udev->descriptor.idProduct); + /* See comment above mfi_fc_id_table[] */ + return (idProduct >= 0x1200 && idProduct <= 0x12ff); +} + +static int mfi_fc_probe(struct usb_device *udev) +{ + struct power_supply_config battery_cfg = {}; + struct mfi_device *mfi = NULL; + int err; + + if (!mfi_fc_match(udev)) + return -ENODEV; + + mfi = kzalloc(sizeof(struct mfi_device), GFP_KERNEL); + if (!mfi) + return -ENOMEM; + + battery_cfg.drv_data = mfi; + + mfi->charge_type = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + mfi->battery = power_supply_register(&udev->dev, + &apple_mfi_fc_desc, + &battery_cfg); + if (IS_ERR(mfi->battery)) { + dev_err(&udev->dev, "Can't register battery\n"); + err = PTR_ERR(mfi->battery); + kfree(mfi); + return err; + } + + mfi->udev = usb_get_dev(udev); + dev_set_drvdata(&udev->dev, mfi); + + return 0; +} + +static void mfi_fc_disconnect(struct usb_device *udev) +{ + struct mfi_device *mfi; + + mfi = dev_get_drvdata(&udev->dev); + if (mfi->battery) + power_supply_unregister(mfi->battery); + dev_set_drvdata(&udev->dev, NULL); + usb_put_dev(mfi->udev); + kfree(mfi); +} + +static struct usb_device_driver mfi_fc_driver = { + .name = "apple-mfi-fastcharge", + .probe = mfi_fc_probe, + .disconnect = mfi_fc_disconnect, + .id_table = mfi_fc_id_table, + .match = mfi_fc_match, + .generic_subclass = 1, +}; + +static int __init mfi_fc_driver_init(void) +{ + return usb_register_device_driver(&mfi_fc_driver, THIS_MODULE); +} + +static void __exit mfi_fc_driver_exit(void) +{ + usb_deregister_device_driver(&mfi_fc_driver); +} + +module_init(mfi_fc_driver_init); +module_exit(mfi_fc_driver_exit); diff --git a/drivers/usb/misc/appledisplay.c b/drivers/usb/misc/appledisplay.c new file mode 100644 index 0000000000..c8098e9b43 --- /dev/null +++ b/drivers/usb/misc/appledisplay.c @@ -0,0 +1,349 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Apple Cinema Display driver + * + * Copyright (C) 2006 Michael Hanselmann (linux-kernel@hansmi.ch) + * + * Thanks to Caskey L. Dickson for his work with acdctl. + */ + +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/usb.h> +#include <linux/backlight.h> +#include <linux/timer.h> +#include <linux/workqueue.h> +#include <linux/atomic.h> + +#define APPLE_VENDOR_ID 0x05AC + +#define USB_REQ_GET_REPORT 0x01 +#define USB_REQ_SET_REPORT 0x09 + +#define ACD_USB_TIMEOUT 250 + +#define ACD_USB_EDID 0x0302 +#define ACD_USB_BRIGHTNESS 0x0310 + +#define ACD_BTN_NONE 0 +#define ACD_BTN_BRIGHT_UP 3 +#define ACD_BTN_BRIGHT_DOWN 4 + +#define ACD_URB_BUFFER_LEN 2 +#define ACD_MSG_BUFFER_LEN 2 + +#define APPLEDISPLAY_DEVICE(prod) \ + .match_flags = USB_DEVICE_ID_MATCH_DEVICE | \ + USB_DEVICE_ID_MATCH_INT_CLASS | \ + USB_DEVICE_ID_MATCH_INT_PROTOCOL, \ + .idVendor = APPLE_VENDOR_ID, \ + .idProduct = (prod), \ + .bInterfaceClass = USB_CLASS_HID, \ + .bInterfaceProtocol = 0x00 + +/* table of devices that work with this driver */ +static const struct usb_device_id appledisplay_table[] = { + { APPLEDISPLAY_DEVICE(0x9218) }, + { APPLEDISPLAY_DEVICE(0x9219) }, + { APPLEDISPLAY_DEVICE(0x921c) }, + { APPLEDISPLAY_DEVICE(0x921d) }, + { APPLEDISPLAY_DEVICE(0x9222) }, + { APPLEDISPLAY_DEVICE(0x9226) }, + { APPLEDISPLAY_DEVICE(0x9236) }, + + /* Terminating entry */ + { } +}; +MODULE_DEVICE_TABLE(usb, appledisplay_table); + +/* Structure to hold all of our device specific stuff */ +struct appledisplay { + struct usb_device *udev; /* usb device */ + struct urb *urb; /* usb request block */ + struct backlight_device *bd; /* backlight device */ + u8 *urbdata; /* interrupt URB data buffer */ + u8 *msgdata; /* control message data buffer */ + + struct delayed_work work; + int button_pressed; + struct mutex sysfslock; /* concurrent read and write */ +}; + +static atomic_t count_displays = ATOMIC_INIT(0); + +static void appledisplay_complete(struct urb *urb) +{ + struct appledisplay *pdata = urb->context; + struct device *dev = &pdata->udev->dev; + int status = urb->status; + int retval; + + switch (status) { + case 0: + /* success */ + break; + case -EOVERFLOW: + dev_err(dev, + "OVERFLOW with data length %d, actual length is %d\n", + ACD_URB_BUFFER_LEN, pdata->urb->actual_length); + fallthrough; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* This urb is terminated, clean up */ + dev_dbg(dev, "%s - urb shuttingdown with status: %d\n", + __func__, status); + return; + default: + dev_dbg(dev, "%s - nonzero urb status received: %d\n", + __func__, status); + goto exit; + } + + switch(pdata->urbdata[1]) { + case ACD_BTN_BRIGHT_UP: + case ACD_BTN_BRIGHT_DOWN: + pdata->button_pressed = 1; + schedule_delayed_work(&pdata->work, 0); + break; + case ACD_BTN_NONE: + default: + pdata->button_pressed = 0; + break; + } + +exit: + retval = usb_submit_urb(pdata->urb, GFP_ATOMIC); + if (retval) { + dev_err(dev, "%s - usb_submit_urb failed with result %d\n", + __func__, retval); + } +} + +static int appledisplay_bl_update_status(struct backlight_device *bd) +{ + struct appledisplay *pdata = bl_get_data(bd); + int retval; + + mutex_lock(&pdata->sysfslock); + pdata->msgdata[0] = 0x10; + pdata->msgdata[1] = bd->props.brightness; + + retval = usb_control_msg( + pdata->udev, + usb_sndctrlpipe(pdata->udev, 0), + USB_REQ_SET_REPORT, + USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + ACD_USB_BRIGHTNESS, + 0, + pdata->msgdata, 2, + ACD_USB_TIMEOUT); + mutex_unlock(&pdata->sysfslock); + + if (retval < 0) + return retval; + else + return 0; +} + +static int appledisplay_bl_get_brightness(struct backlight_device *bd) +{ + struct appledisplay *pdata = bl_get_data(bd); + int retval, brightness; + + mutex_lock(&pdata->sysfslock); + retval = usb_control_msg( + pdata->udev, + usb_rcvctrlpipe(pdata->udev, 0), + USB_REQ_GET_REPORT, + USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + ACD_USB_BRIGHTNESS, + 0, + pdata->msgdata, 2, + ACD_USB_TIMEOUT); + if (retval < 2) { + if (retval >= 0) + retval = -EMSGSIZE; + } else { + brightness = pdata->msgdata[1]; + } + mutex_unlock(&pdata->sysfslock); + + if (retval < 0) + return retval; + else + return brightness; +} + +static const struct backlight_ops appledisplay_bl_data = { + .get_brightness = appledisplay_bl_get_brightness, + .update_status = appledisplay_bl_update_status, +}; + +static void appledisplay_work(struct work_struct *work) +{ + struct appledisplay *pdata = + container_of(work, struct appledisplay, work.work); + int retval; + + retval = appledisplay_bl_get_brightness(pdata->bd); + if (retval >= 0) + pdata->bd->props.brightness = retval; + + /* Poll again in about 125ms if there's still a button pressed */ + if (pdata->button_pressed) + schedule_delayed_work(&pdata->work, HZ / 8); +} + +static int appledisplay_probe(struct usb_interface *iface, + const struct usb_device_id *id) +{ + struct backlight_properties props; + struct appledisplay *pdata; + struct usb_device *udev = interface_to_usbdev(iface); + struct usb_endpoint_descriptor *endpoint; + int int_in_endpointAddr = 0; + int retval, brightness; + char bl_name[20]; + + /* set up the endpoint information */ + /* use only the first interrupt-in endpoint */ + retval = usb_find_int_in_endpoint(iface->cur_altsetting, &endpoint); + if (retval) { + dev_err(&iface->dev, "Could not find int-in endpoint\n"); + return retval; + } + + int_in_endpointAddr = endpoint->bEndpointAddress; + + /* allocate memory for our device state and initialize it */ + pdata = kzalloc(sizeof(struct appledisplay), GFP_KERNEL); + if (!pdata) { + retval = -ENOMEM; + goto error; + } + + pdata->udev = udev; + + INIT_DELAYED_WORK(&pdata->work, appledisplay_work); + mutex_init(&pdata->sysfslock); + + /* Allocate buffer for control messages */ + pdata->msgdata = kmalloc(ACD_MSG_BUFFER_LEN, GFP_KERNEL); + if (!pdata->msgdata) { + retval = -ENOMEM; + goto error; + } + + /* Allocate interrupt URB */ + pdata->urb = usb_alloc_urb(0, GFP_KERNEL); + if (!pdata->urb) { + retval = -ENOMEM; + goto error; + } + + /* Allocate buffer for interrupt data */ + pdata->urbdata = usb_alloc_coherent(pdata->udev, ACD_URB_BUFFER_LEN, + GFP_KERNEL, &pdata->urb->transfer_dma); + if (!pdata->urbdata) { + retval = -ENOMEM; + dev_err(&iface->dev, "Allocating URB buffer failed\n"); + goto error; + } + + /* Configure interrupt URB */ + usb_fill_int_urb(pdata->urb, udev, + usb_rcvintpipe(udev, int_in_endpointAddr), + pdata->urbdata, ACD_URB_BUFFER_LEN, appledisplay_complete, + pdata, 1); + pdata->urb->transfer_flags = URB_NO_TRANSFER_DMA_MAP; + if (usb_submit_urb(pdata->urb, GFP_KERNEL)) { + retval = -EIO; + dev_err(&iface->dev, "Submitting URB failed\n"); + goto error; + } + + /* Register backlight device */ + snprintf(bl_name, sizeof(bl_name), "appledisplay%d", + atomic_inc_return(&count_displays) - 1); + memset(&props, 0, sizeof(struct backlight_properties)); + props.type = BACKLIGHT_RAW; + props.max_brightness = 0xff; + pdata->bd = backlight_device_register(bl_name, NULL, pdata, + &appledisplay_bl_data, &props); + if (IS_ERR(pdata->bd)) { + dev_err(&iface->dev, "Backlight registration failed\n"); + retval = PTR_ERR(pdata->bd); + goto error; + } + + /* Try to get brightness */ + brightness = appledisplay_bl_get_brightness(pdata->bd); + + if (brightness < 0) { + retval = brightness; + dev_err(&iface->dev, + "Error while getting initial brightness: %d\n", retval); + goto error; + } + + /* Set brightness in backlight device */ + pdata->bd->props.brightness = brightness; + + /* save our data pointer in the interface device */ + usb_set_intfdata(iface, pdata); + + printk(KERN_INFO "appledisplay: Apple Cinema Display connected\n"); + + return 0; + +error: + if (pdata) { + if (pdata->urb) { + usb_kill_urb(pdata->urb); + cancel_delayed_work_sync(&pdata->work); + usb_free_coherent(pdata->udev, ACD_URB_BUFFER_LEN, + pdata->urbdata, pdata->urb->transfer_dma); + usb_free_urb(pdata->urb); + } + if (!IS_ERR(pdata->bd)) + backlight_device_unregister(pdata->bd); + kfree(pdata->msgdata); + } + usb_set_intfdata(iface, NULL); + kfree(pdata); + return retval; +} + +static void appledisplay_disconnect(struct usb_interface *iface) +{ + struct appledisplay *pdata = usb_get_intfdata(iface); + + if (pdata) { + usb_kill_urb(pdata->urb); + cancel_delayed_work_sync(&pdata->work); + backlight_device_unregister(pdata->bd); + usb_free_coherent(pdata->udev, ACD_URB_BUFFER_LEN, + pdata->urbdata, pdata->urb->transfer_dma); + usb_free_urb(pdata->urb); + kfree(pdata->msgdata); + kfree(pdata); + } + + printk(KERN_INFO "appledisplay: Apple Cinema Display disconnected\n"); +} + +static struct usb_driver appledisplay_driver = { + .name = "appledisplay", + .probe = appledisplay_probe, + .disconnect = appledisplay_disconnect, + .id_table = appledisplay_table, +}; +module_usb_driver(appledisplay_driver); + +MODULE_AUTHOR("Michael Hanselmann"); +MODULE_DESCRIPTION("Apple Cinema Display driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/misc/brcmstb-usb-pinmap.c b/drivers/usb/misc/brcmstb-usb-pinmap.c new file mode 100644 index 0000000000..2b2019c19c --- /dev/null +++ b/drivers/usb/misc/brcmstb-usb-pinmap.c @@ -0,0 +1,355 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2020, Broadcom */ + +#include <linux/init.h> +#include <linux/types.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/device.h> +#include <linux/of.h> +#include <linux/kernel.h> +#include <linux/kdebug.h> +#include <linux/gpio/consumer.h> + +struct out_pin { + u32 enable_mask; + u32 value_mask; + u32 changed_mask; + u32 clr_changed_mask; + struct gpio_desc *gpiod; + const char *name; +}; + +struct in_pin { + u32 enable_mask; + u32 value_mask; + struct gpio_desc *gpiod; + const char *name; + struct brcmstb_usb_pinmap_data *pdata; +}; + +struct brcmstb_usb_pinmap_data { + void __iomem *regs; + int in_count; + struct in_pin *in_pins; + int out_count; + struct out_pin *out_pins; +}; + + +static void pinmap_set(void __iomem *reg, u32 mask) +{ + u32 val; + + val = readl(reg); + val |= mask; + writel(val, reg); +} + +static void pinmap_unset(void __iomem *reg, u32 mask) +{ + u32 val; + + val = readl(reg); + val &= ~mask; + writel(val, reg); +} + +static void sync_in_pin(struct in_pin *pin) +{ + u32 val; + + val = gpiod_get_value(pin->gpiod); + if (val) + pinmap_set(pin->pdata->regs, pin->value_mask); + else + pinmap_unset(pin->pdata->regs, pin->value_mask); +} + +/* + * Interrupt from override register, propagate from override bit + * to GPIO. + */ +static irqreturn_t brcmstb_usb_pinmap_ovr_isr(int irq, void *dev_id) +{ + struct brcmstb_usb_pinmap_data *pdata = dev_id; + struct out_pin *pout; + u32 val; + u32 bit; + int x; + + pr_debug("%s: reg: 0x%x\n", __func__, readl(pdata->regs)); + pout = pdata->out_pins; + for (x = 0; x < pdata->out_count; x++) { + val = readl(pdata->regs); + if (val & pout->changed_mask) { + pinmap_set(pdata->regs, pout->clr_changed_mask); + pinmap_unset(pdata->regs, pout->clr_changed_mask); + bit = val & pout->value_mask; + gpiod_set_value(pout->gpiod, bit ? 1 : 0); + pr_debug("%s: %s bit changed state to %d\n", + __func__, pout->name, bit ? 1 : 0); + } + } + return IRQ_HANDLED; +} + +/* + * Interrupt from GPIO, propagate from GPIO to override bit. + */ +static irqreturn_t brcmstb_usb_pinmap_gpio_isr(int irq, void *dev_id) +{ + struct in_pin *pin = dev_id; + + pr_debug("%s: %s pin changed state\n", __func__, pin->name); + sync_in_pin(pin); + return IRQ_HANDLED; +} + + +static void get_pin_counts(struct device_node *dn, int *in_count, + int *out_count) +{ + int in; + int out; + + *in_count = 0; + *out_count = 0; + in = of_property_count_strings(dn, "brcm,in-functions"); + if (in < 0) + return; + out = of_property_count_strings(dn, "brcm,out-functions"); + if (out < 0) + return; + *in_count = in; + *out_count = out; +} + +static int parse_pins(struct device *dev, struct device_node *dn, + struct brcmstb_usb_pinmap_data *pdata) +{ + struct out_pin *pout; + struct in_pin *pin; + int index; + int res; + int x; + + pin = pdata->in_pins; + for (x = 0, index = 0; x < pdata->in_count; x++) { + pin->gpiod = devm_gpiod_get_index(dev, "in", x, GPIOD_IN); + if (IS_ERR(pin->gpiod)) { + dev_err(dev, "Error getting gpio %s\n", pin->name); + return PTR_ERR(pin->gpiod); + + } + res = of_property_read_string_index(dn, "brcm,in-functions", x, + &pin->name); + if (res < 0) { + dev_err(dev, "Error getting brcm,in-functions for %s\n", + pin->name); + return res; + } + res = of_property_read_u32_index(dn, "brcm,in-masks", index++, + &pin->enable_mask); + if (res < 0) { + dev_err(dev, "Error getting 1st brcm,in-masks for %s\n", + pin->name); + return res; + } + res = of_property_read_u32_index(dn, "brcm,in-masks", index++, + &pin->value_mask); + if (res < 0) { + dev_err(dev, "Error getting 2nd brcm,in-masks for %s\n", + pin->name); + return res; + } + pin->pdata = pdata; + pin++; + } + pout = pdata->out_pins; + for (x = 0, index = 0; x < pdata->out_count; x++) { + pout->gpiod = devm_gpiod_get_index(dev, "out", x, + GPIOD_OUT_HIGH); + if (IS_ERR(pout->gpiod)) { + dev_err(dev, "Error getting gpio %s\n", pin->name); + return PTR_ERR(pout->gpiod); + } + res = of_property_read_string_index(dn, "brcm,out-functions", x, + &pout->name); + if (res < 0) { + dev_err(dev, "Error getting brcm,out-functions for %s\n", + pout->name); + return res; + } + res = of_property_read_u32_index(dn, "brcm,out-masks", index++, + &pout->enable_mask); + if (res < 0) { + dev_err(dev, "Error getting 1st brcm,out-masks for %s\n", + pout->name); + return res; + } + res = of_property_read_u32_index(dn, "brcm,out-masks", index++, + &pout->value_mask); + if (res < 0) { + dev_err(dev, "Error getting 2nd brcm,out-masks for %s\n", + pout->name); + return res; + } + res = of_property_read_u32_index(dn, "brcm,out-masks", index++, + &pout->changed_mask); + if (res < 0) { + dev_err(dev, "Error getting 3rd brcm,out-masks for %s\n", + pout->name); + return res; + } + res = of_property_read_u32_index(dn, "brcm,out-masks", index++, + &pout->clr_changed_mask); + if (res < 0) { + dev_err(dev, "Error getting 4th out-masks for %s\n", + pout->name); + return res; + } + pout++; + } + return 0; +} + +static void sync_all_pins(struct brcmstb_usb_pinmap_data *pdata) +{ + struct out_pin *pout; + struct in_pin *pin; + int val; + int x; + + /* + * Enable the override, clear any changed condition and + * propagate the state to the GPIO for all out pins. + */ + pout = pdata->out_pins; + for (x = 0; x < pdata->out_count; x++) { + pinmap_set(pdata->regs, pout->enable_mask); + pinmap_set(pdata->regs, pout->clr_changed_mask); + pinmap_unset(pdata->regs, pout->clr_changed_mask); + val = readl(pdata->regs) & pout->value_mask; + gpiod_set_value(pout->gpiod, val ? 1 : 0); + pout++; + } + + /* sync and enable all in pins. */ + pin = pdata->in_pins; + for (x = 0; x < pdata->in_count; x++) { + sync_in_pin(pin); + pinmap_set(pdata->regs, pin->enable_mask); + pin++; + } +} + +static int __init brcmstb_usb_pinmap_probe(struct platform_device *pdev) +{ + struct device_node *dn = pdev->dev.of_node; + struct brcmstb_usb_pinmap_data *pdata; + struct in_pin *pin; + struct resource *r; + int out_count; + int in_count; + int err; + int irq; + int x; + + get_pin_counts(dn, &in_count, &out_count); + if ((in_count + out_count) == 0) + return -EINVAL; + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!r) + return -EINVAL; + + pdata = devm_kzalloc(&pdev->dev, + sizeof(*pdata) + + (sizeof(struct in_pin) * in_count) + + (sizeof(struct out_pin) * out_count), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + pdata->in_count = in_count; + pdata->out_count = out_count; + pdata->in_pins = (struct in_pin *)(pdata + 1); + pdata->out_pins = (struct out_pin *)(pdata->in_pins + in_count); + + pdata->regs = devm_ioremap(&pdev->dev, r->start, resource_size(r)); + if (!pdata->regs) + return -ENOMEM; + platform_set_drvdata(pdev, pdata); + + err = parse_pins(&pdev->dev, dn, pdata); + if (err) + return err; + + sync_all_pins(pdata); + + if (out_count) { + + /* Enable interrupt for out pins */ + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + err = devm_request_irq(&pdev->dev, irq, + brcmstb_usb_pinmap_ovr_isr, + IRQF_TRIGGER_RISING, + pdev->name, pdata); + if (err < 0) { + dev_err(&pdev->dev, "Error requesting IRQ\n"); + return err; + } + } + + for (x = 0, pin = pdata->in_pins; x < pdata->in_count; x++, pin++) { + irq = gpiod_to_irq(pin->gpiod); + if (irq < 0) { + dev_err(&pdev->dev, "Error getting IRQ for %s pin\n", + pin->name); + return irq; + } + err = devm_request_irq(&pdev->dev, irq, + brcmstb_usb_pinmap_gpio_isr, + IRQF_SHARED | IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING, + pdev->name, pin); + if (err < 0) { + dev_err(&pdev->dev, "Error requesting IRQ for %s pin\n", + pin->name); + return err; + } + } + + dev_dbg(&pdev->dev, "Driver probe succeeded\n"); + dev_dbg(&pdev->dev, "In pin count: %d, out pin count: %d\n", + pdata->in_count, pdata->out_count); + return 0; +} + + +static const struct of_device_id brcmstb_usb_pinmap_of_match[] = { + { .compatible = "brcm,usb-pinmap" }, + { }, +}; + +static struct platform_driver brcmstb_usb_pinmap_driver = { + .driver = { + .name = "brcm-usb-pinmap", + .of_match_table = brcmstb_usb_pinmap_of_match, + }, +}; + +static int __init brcmstb_usb_pinmap_init(void) +{ + return platform_driver_probe(&brcmstb_usb_pinmap_driver, + brcmstb_usb_pinmap_probe); +} + +module_init(brcmstb_usb_pinmap_init); +MODULE_AUTHOR("Al Cooper <alcooperx@gmail.com>"); +MODULE_DESCRIPTION("Broadcom USB Pinmap Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/misc/chaoskey.c b/drivers/usb/misc/chaoskey.c new file mode 100644 index 0000000000..6fb5140e29 --- /dev/null +++ b/drivers/usb/misc/chaoskey.c @@ -0,0 +1,583 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * chaoskey - driver for ChaosKey device from Altus Metrum. + * + * This device provides true random numbers using a noise source based + * on a reverse-biased p-n junction in avalanche breakdown. More + * details can be found at http://chaoskey.org + * + * The driver connects to the kernel hardware RNG interface to provide + * entropy for /dev/random and other kernel activities. It also offers + * a separate /dev/ entry to allow for direct access to the random + * bit stream. + * + * Copyright © 2015 Keith Packard <keithp@keithp.com> + */ + +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/usb.h> +#include <linux/wait.h> +#include <linux/hw_random.h> +#include <linux/mutex.h> +#include <linux/uaccess.h> + +static struct usb_driver chaoskey_driver; +static struct usb_class_driver chaoskey_class; +static int chaoskey_rng_read(struct hwrng *rng, void *data, + size_t max, bool wait); + +#define usb_dbg(usb_if, format, arg...) \ + dev_dbg(&(usb_if)->dev, format, ## arg) + +#define usb_err(usb_if, format, arg...) \ + dev_err(&(usb_if)->dev, format, ## arg) + +/* Version Information */ +#define DRIVER_AUTHOR "Keith Packard, keithp@keithp.com" +#define DRIVER_DESC "Altus Metrum ChaosKey driver" +#define DRIVER_SHORT "chaoskey" + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +#define CHAOSKEY_VENDOR_ID 0x1d50 /* OpenMoko */ +#define CHAOSKEY_PRODUCT_ID 0x60c6 /* ChaosKey */ + +#define ALEA_VENDOR_ID 0x12d8 /* Araneus */ +#define ALEA_PRODUCT_ID 0x0001 /* Alea I */ + +#define CHAOSKEY_BUF_LEN 64 /* max size of USB full speed packet */ + +#define NAK_TIMEOUT (HZ) /* normal stall/wait timeout */ +#define ALEA_FIRST_TIMEOUT (HZ*3) /* first stall/wait timeout for Alea */ + +#ifdef CONFIG_USB_DYNAMIC_MINORS +#define USB_CHAOSKEY_MINOR_BASE 0 +#else + +/* IOWARRIOR_MINOR_BASE + 16, not official yet */ +#define USB_CHAOSKEY_MINOR_BASE 224 +#endif + +static const struct usb_device_id chaoskey_table[] = { + { USB_DEVICE(CHAOSKEY_VENDOR_ID, CHAOSKEY_PRODUCT_ID) }, + { USB_DEVICE(ALEA_VENDOR_ID, ALEA_PRODUCT_ID) }, + { }, +}; +MODULE_DEVICE_TABLE(usb, chaoskey_table); + +static void chaos_read_callback(struct urb *urb); + +/* Driver-local specific stuff */ +struct chaoskey { + struct usb_interface *interface; + char in_ep; + struct mutex lock; + struct mutex rng_lock; + int open; /* open count */ + bool present; /* device not disconnected */ + bool reading; /* ongoing IO */ + bool reads_started; /* track first read for Alea */ + int size; /* size of buf */ + int valid; /* bytes of buf read */ + int used; /* bytes of buf consumed */ + char *name; /* product + serial */ + struct hwrng hwrng; /* Embedded struct for hwrng */ + int hwrng_registered; /* registered with hwrng API */ + wait_queue_head_t wait_q; /* for timeouts */ + struct urb *urb; /* for performing IO */ + char *buf; +}; + +static void chaoskey_free(struct chaoskey *dev) +{ + if (dev) { + usb_dbg(dev->interface, "free"); + usb_free_urb(dev->urb); + kfree(dev->name); + kfree(dev->buf); + usb_put_intf(dev->interface); + kfree(dev); + } +} + +static int chaoskey_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(interface); + struct usb_host_interface *altsetting = interface->cur_altsetting; + struct usb_endpoint_descriptor *epd; + int in_ep; + struct chaoskey *dev; + int result = -ENOMEM; + int size; + int res; + + usb_dbg(interface, "probe %s-%s", udev->product, udev->serial); + + /* Find the first bulk IN endpoint and its packet size */ + res = usb_find_bulk_in_endpoint(altsetting, &epd); + if (res) { + usb_dbg(interface, "no IN endpoint found"); + return res; + } + + in_ep = usb_endpoint_num(epd); + size = usb_endpoint_maxp(epd); + + /* Validate endpoint and size */ + if (size <= 0) { + usb_dbg(interface, "invalid size (%d)", size); + return -ENODEV; + } + + if (size > CHAOSKEY_BUF_LEN) { + usb_dbg(interface, "size reduced from %d to %d\n", + size, CHAOSKEY_BUF_LEN); + size = CHAOSKEY_BUF_LEN; + } + + /* Looks good, allocate and initialize */ + + dev = kzalloc(sizeof(struct chaoskey), GFP_KERNEL); + + if (dev == NULL) + goto out; + + dev->interface = usb_get_intf(interface); + + dev->buf = kmalloc(size, GFP_KERNEL); + + if (dev->buf == NULL) + goto out; + + dev->urb = usb_alloc_urb(0, GFP_KERNEL); + + if (!dev->urb) + goto out; + + usb_fill_bulk_urb(dev->urb, + udev, + usb_rcvbulkpipe(udev, in_ep), + dev->buf, + size, + chaos_read_callback, + dev); + + /* Construct a name using the product and serial values. Each + * device needs a unique name for the hwrng code + */ + + if (udev->product && udev->serial) { + dev->name = kasprintf(GFP_KERNEL, "%s-%s", udev->product, + udev->serial); + if (dev->name == NULL) + goto out; + } + + dev->in_ep = in_ep; + + if (le16_to_cpu(udev->descriptor.idVendor) != ALEA_VENDOR_ID) + dev->reads_started = true; + + dev->size = size; + dev->present = true; + + init_waitqueue_head(&dev->wait_q); + + mutex_init(&dev->lock); + mutex_init(&dev->rng_lock); + + usb_set_intfdata(interface, dev); + + result = usb_register_dev(interface, &chaoskey_class); + if (result) { + usb_err(interface, "Unable to allocate minor number."); + goto out; + } + + dev->hwrng.name = dev->name ? dev->name : chaoskey_driver.name; + dev->hwrng.read = chaoskey_rng_read; + + dev->hwrng_registered = (hwrng_register(&dev->hwrng) == 0); + if (!dev->hwrng_registered) + usb_err(interface, "Unable to register with hwrng"); + + usb_enable_autosuspend(udev); + + usb_dbg(interface, "chaoskey probe success, size %d", dev->size); + return 0; + +out: + usb_set_intfdata(interface, NULL); + chaoskey_free(dev); + return result; +} + +static void chaoskey_disconnect(struct usb_interface *interface) +{ + struct chaoskey *dev; + + usb_dbg(interface, "disconnect"); + dev = usb_get_intfdata(interface); + if (!dev) { + usb_dbg(interface, "disconnect failed - no dev"); + return; + } + + if (dev->hwrng_registered) + hwrng_unregister(&dev->hwrng); + + usb_deregister_dev(interface, &chaoskey_class); + + usb_set_intfdata(interface, NULL); + mutex_lock(&dev->lock); + + dev->present = false; + usb_poison_urb(dev->urb); + + if (!dev->open) { + mutex_unlock(&dev->lock); + chaoskey_free(dev); + } else + mutex_unlock(&dev->lock); + + usb_dbg(interface, "disconnect done"); +} + +static int chaoskey_open(struct inode *inode, struct file *file) +{ + struct chaoskey *dev; + struct usb_interface *interface; + + /* get the interface from minor number and driver information */ + interface = usb_find_interface(&chaoskey_driver, iminor(inode)); + if (!interface) + return -ENODEV; + + usb_dbg(interface, "open"); + + dev = usb_get_intfdata(interface); + if (!dev) { + usb_dbg(interface, "open (dev)"); + return -ENODEV; + } + + file->private_data = dev; + mutex_lock(&dev->lock); + ++dev->open; + mutex_unlock(&dev->lock); + + usb_dbg(interface, "open success"); + return 0; +} + +static int chaoskey_release(struct inode *inode, struct file *file) +{ + struct chaoskey *dev = file->private_data; + struct usb_interface *interface; + + if (dev == NULL) + return -ENODEV; + + interface = dev->interface; + + usb_dbg(interface, "release"); + + mutex_lock(&dev->lock); + + usb_dbg(interface, "open count at release is %d", dev->open); + + if (dev->open <= 0) { + usb_dbg(interface, "invalid open count (%d)", dev->open); + mutex_unlock(&dev->lock); + return -ENODEV; + } + + --dev->open; + + if (!dev->present) { + if (dev->open == 0) { + mutex_unlock(&dev->lock); + chaoskey_free(dev); + } else + mutex_unlock(&dev->lock); + } else + mutex_unlock(&dev->lock); + + usb_dbg(interface, "release success"); + return 0; +} + +static void chaos_read_callback(struct urb *urb) +{ + struct chaoskey *dev = urb->context; + int status = urb->status; + + usb_dbg(dev->interface, "callback status (%d)", status); + + if (status == 0) + dev->valid = urb->actual_length; + else + dev->valid = 0; + + dev->used = 0; + + /* must be seen first before validity is announced */ + smp_wmb(); + + dev->reading = false; + wake_up(&dev->wait_q); +} + +/* Fill the buffer. Called with dev->lock held + */ +static int _chaoskey_fill(struct chaoskey *dev) +{ + DEFINE_WAIT(wait); + int result; + bool started; + + usb_dbg(dev->interface, "fill"); + + /* Return immediately if someone called before the buffer was + * empty */ + if (dev->valid != dev->used) { + usb_dbg(dev->interface, "not empty yet (valid %d used %d)", + dev->valid, dev->used); + return 0; + } + + /* Bail if the device has been removed */ + if (!dev->present) { + usb_dbg(dev->interface, "device not present"); + return -ENODEV; + } + + /* Make sure the device is awake */ + result = usb_autopm_get_interface(dev->interface); + if (result) { + usb_dbg(dev->interface, "wakeup failed (result %d)", result); + return result; + } + + dev->reading = true; + result = usb_submit_urb(dev->urb, GFP_KERNEL); + if (result < 0) { + result = usb_translate_errors(result); + dev->reading = false; + goto out; + } + + /* The first read on the Alea takes a little under 2 seconds. + * Reads after the first read take only a few microseconds + * though. Presumably the entropy-generating circuit needs + * time to ramp up. So, we wait longer on the first read. + */ + started = dev->reads_started; + dev->reads_started = true; + result = wait_event_interruptible_timeout( + dev->wait_q, + !dev->reading, + (started ? NAK_TIMEOUT : ALEA_FIRST_TIMEOUT) ); + + if (result < 0) { + usb_kill_urb(dev->urb); + goto out; + } + + if (result == 0) { + result = -ETIMEDOUT; + usb_kill_urb(dev->urb); + } else { + result = dev->valid; + } +out: + /* Let the device go back to sleep eventually */ + usb_autopm_put_interface(dev->interface); + + usb_dbg(dev->interface, "read %d bytes", dev->valid); + + return result; +} + +static ssize_t chaoskey_read(struct file *file, + char __user *buffer, + size_t count, + loff_t *ppos) +{ + struct chaoskey *dev; + ssize_t read_count = 0; + int this_time; + int result = 0; + unsigned long remain; + + dev = file->private_data; + + if (dev == NULL || !dev->present) + return -ENODEV; + + usb_dbg(dev->interface, "read %zu", count); + + while (count > 0) { + + /* Grab the rng_lock briefly to ensure that the hwrng interface + * gets priority over other user access + */ + result = mutex_lock_interruptible(&dev->rng_lock); + if (result) + goto bail; + mutex_unlock(&dev->rng_lock); + + result = mutex_lock_interruptible(&dev->lock); + if (result) + goto bail; + if (dev->valid == dev->used) { + result = _chaoskey_fill(dev); + if (result < 0) { + mutex_unlock(&dev->lock); + goto bail; + } + } + + this_time = dev->valid - dev->used; + if (this_time > count) + this_time = count; + + remain = copy_to_user(buffer, dev->buf + dev->used, this_time); + if (remain) { + result = -EFAULT; + + /* Consume the bytes that were copied so we don't leak + * data to user space + */ + dev->used += this_time - remain; + mutex_unlock(&dev->lock); + goto bail; + } + + count -= this_time; + read_count += this_time; + buffer += this_time; + dev->used += this_time; + mutex_unlock(&dev->lock); + } +bail: + if (read_count) { + usb_dbg(dev->interface, "read %zu bytes", read_count); + return read_count; + } + usb_dbg(dev->interface, "empty read, result %d", result); + if (result == -ETIMEDOUT) + result = -EAGAIN; + return result; +} + +static int chaoskey_rng_read(struct hwrng *rng, void *data, + size_t max, bool wait) +{ + struct chaoskey *dev = container_of(rng, struct chaoskey, hwrng); + int this_time; + + usb_dbg(dev->interface, "rng_read max %zu wait %d", max, wait); + + if (!dev->present) { + usb_dbg(dev->interface, "device not present"); + return 0; + } + + /* Hold the rng_lock until we acquire the device lock so that + * this operation gets priority over other user access to the + * device + */ + mutex_lock(&dev->rng_lock); + + mutex_lock(&dev->lock); + + mutex_unlock(&dev->rng_lock); + + /* Try to fill the buffer if empty. It doesn't actually matter + * if _chaoskey_fill works; we'll just return zero bytes as + * the buffer will still be empty + */ + if (dev->valid == dev->used) + (void) _chaoskey_fill(dev); + + this_time = dev->valid - dev->used; + if (this_time > max) + this_time = max; + + memcpy(data, dev->buf + dev->used, this_time); + + dev->used += this_time; + + mutex_unlock(&dev->lock); + + usb_dbg(dev->interface, "rng_read this_time %d\n", this_time); + return this_time; +} + +#ifdef CONFIG_PM +static int chaoskey_suspend(struct usb_interface *interface, + pm_message_t message) +{ + usb_dbg(interface, "suspend"); + return 0; +} + +static int chaoskey_resume(struct usb_interface *interface) +{ + struct chaoskey *dev; + struct usb_device *udev = interface_to_usbdev(interface); + + usb_dbg(interface, "resume"); + dev = usb_get_intfdata(interface); + + /* + * We may have lost power. + * In that case the device that needs a long time + * for the first requests needs an extended timeout + * again + */ + if (le16_to_cpu(udev->descriptor.idVendor) == ALEA_VENDOR_ID) + dev->reads_started = false; + + return 0; +} +#else +#define chaoskey_suspend NULL +#define chaoskey_resume NULL +#endif + +/* file operation pointers */ +static const struct file_operations chaoskey_fops = { + .owner = THIS_MODULE, + .read = chaoskey_read, + .open = chaoskey_open, + .release = chaoskey_release, + .llseek = default_llseek, +}; + +/* class driver information */ +static struct usb_class_driver chaoskey_class = { + .name = "chaoskey%d", + .fops = &chaoskey_fops, + .minor_base = USB_CHAOSKEY_MINOR_BASE, +}; + +/* usb specific object needed to register this driver with the usb subsystem */ +static struct usb_driver chaoskey_driver = { + .name = DRIVER_SHORT, + .probe = chaoskey_probe, + .disconnect = chaoskey_disconnect, + .suspend = chaoskey_suspend, + .resume = chaoskey_resume, + .reset_resume = chaoskey_resume, + .id_table = chaoskey_table, + .supports_autosuspend = 1, +}; + +module_usb_driver(chaoskey_driver); + diff --git a/drivers/usb/misc/cypress_cy7c63.c b/drivers/usb/misc/cypress_cy7c63.c new file mode 100644 index 0000000000..cecd7693b7 --- /dev/null +++ b/drivers/usb/misc/cypress_cy7c63.c @@ -0,0 +1,259 @@ +// SPDX-License-Identifier: GPL-2.0 +/* +* cypress_cy7c63.c +* +* Copyright (c) 2006-2007 Oliver Bock (bock@tfh-berlin.de) +* +* This driver is based on the Cypress USB Driver by Marcus Maul +* (cyport) and the 2.0 version of Greg Kroah-Hartman's +* USB Skeleton driver. +* +* This is a generic driver for the Cypress CY7C63xxx family. +* For the time being it enables you to read from and write to +* the single I/O ports of the device. +* +* Supported vendors: AK Modul-Bus Computer GmbH +* (Firmware "Port-Chip") +* +* Supported devices: CY7C63001A-PC +* CY7C63001C-PXC +* CY7C63001C-SXC +* +* Supported functions: Read/Write Ports +* +* +* For up-to-date information please visit: +* http://www.obock.de/kernel/cypress +*/ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/usb.h> + +#define DRIVER_AUTHOR "Oliver Bock (bock@tfh-berlin.de)" +#define DRIVER_DESC "Cypress CY7C63xxx USB driver" + +#define CYPRESS_VENDOR_ID 0xa2c +#define CYPRESS_PRODUCT_ID 0x8 + +#define CYPRESS_READ_PORT 0x4 +#define CYPRESS_WRITE_PORT 0x5 + +#define CYPRESS_READ_RAM 0x2 +#define CYPRESS_WRITE_RAM 0x3 +#define CYPRESS_READ_ROM 0x1 + +#define CYPRESS_READ_PORT_ID0 0 +#define CYPRESS_WRITE_PORT_ID0 0 +#define CYPRESS_READ_PORT_ID1 0x2 +#define CYPRESS_WRITE_PORT_ID1 1 + +#define CYPRESS_MAX_REQSIZE 8 + + +/* table of devices that work with this driver */ +static const struct usb_device_id cypress_table[] = { + { USB_DEVICE(CYPRESS_VENDOR_ID, CYPRESS_PRODUCT_ID) }, + { } +}; +MODULE_DEVICE_TABLE(usb, cypress_table); + +/* structure to hold all of our device specific stuff */ +struct cypress { + struct usb_device * udev; + unsigned char port[2]; +}; + +/* used to send usb control messages to device */ +static int vendor_command(struct cypress *dev, unsigned char request, + unsigned char address, unsigned char data) +{ + int retval = 0; + unsigned int pipe; + unsigned char *iobuf; + + /* allocate some memory for the i/o buffer*/ + iobuf = kzalloc(CYPRESS_MAX_REQSIZE, GFP_KERNEL); + if (!iobuf) { + retval = -ENOMEM; + goto error; + } + + dev_dbg(&dev->udev->dev, "Sending usb_control_msg (data: %d)\n", data); + + /* prepare usb control message and send it upstream */ + pipe = usb_rcvctrlpipe(dev->udev, 0); + retval = usb_control_msg(dev->udev, pipe, request, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_OTHER, + address, data, iobuf, CYPRESS_MAX_REQSIZE, + USB_CTRL_GET_TIMEOUT); + + /* store returned data (more READs to be added) */ + switch (request) { + case CYPRESS_READ_PORT: + if (address == CYPRESS_READ_PORT_ID0) { + dev->port[0] = iobuf[1]; + dev_dbg(&dev->udev->dev, + "READ_PORT0 returned: %d\n", + dev->port[0]); + } + else if (address == CYPRESS_READ_PORT_ID1) { + dev->port[1] = iobuf[1]; + dev_dbg(&dev->udev->dev, + "READ_PORT1 returned: %d\n", + dev->port[1]); + } + break; + } + + kfree(iobuf); +error: + return retval; +} + +/* write port value */ +static ssize_t write_port(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count, + int port_num, int write_id) +{ + int value = -1; + int result = 0; + + struct usb_interface *intf = to_usb_interface(dev); + struct cypress *cyp = usb_get_intfdata(intf); + + dev_dbg(&cyp->udev->dev, "WRITE_PORT%d called\n", port_num); + + /* validate input data */ + if (sscanf(buf, "%d", &value) < 1) { + result = -EINVAL; + goto error; + } + if (value < 0 || value > 255) { + result = -EINVAL; + goto error; + } + + result = vendor_command(cyp, CYPRESS_WRITE_PORT, write_id, + (unsigned char)value); + + dev_dbg(&cyp->udev->dev, "Result of vendor_command: %d\n\n", result); +error: + return result < 0 ? result : count; +} + +/* attribute callback handler (write) */ +static ssize_t port0_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + return write_port(dev, attr, buf, count, 0, CYPRESS_WRITE_PORT_ID0); +} + +/* attribute callback handler (write) */ +static ssize_t port1_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + return write_port(dev, attr, buf, count, 1, CYPRESS_WRITE_PORT_ID1); +} + +/* read port value */ +static ssize_t read_port(struct device *dev, struct device_attribute *attr, + char *buf, int port_num, int read_id) +{ + int result = 0; + + struct usb_interface *intf = to_usb_interface(dev); + struct cypress *cyp = usb_get_intfdata(intf); + + dev_dbg(&cyp->udev->dev, "READ_PORT%d called\n", port_num); + + result = vendor_command(cyp, CYPRESS_READ_PORT, read_id, 0); + + dev_dbg(&cyp->udev->dev, "Result of vendor_command: %d\n\n", result); + + return sprintf(buf, "%d", cyp->port[port_num]); +} + +/* attribute callback handler (read) */ +static ssize_t port0_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return read_port(dev, attr, buf, 0, CYPRESS_READ_PORT_ID0); +} +static DEVICE_ATTR_RW(port0); + +/* attribute callback handler (read) */ +static ssize_t port1_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return read_port(dev, attr, buf, 1, CYPRESS_READ_PORT_ID1); +} +static DEVICE_ATTR_RW(port1); + +static struct attribute *cypress_attrs[] = { + &dev_attr_port0.attr, + &dev_attr_port1.attr, + NULL, +}; +ATTRIBUTE_GROUPS(cypress); + +static int cypress_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + struct cypress *dev; + int retval = -ENOMEM; + + /* allocate memory for our device state and initialize it */ + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + goto error_mem; + + dev->udev = usb_get_dev(interface_to_usbdev(interface)); + + /* save our data pointer in this interface device */ + usb_set_intfdata(interface, dev); + + /* let the user know that the device is now attached */ + dev_info(&interface->dev, + "Cypress CY7C63xxx device now attached\n"); + return 0; + +error_mem: + return retval; +} + +static void cypress_disconnect(struct usb_interface *interface) +{ + struct cypress *dev; + + dev = usb_get_intfdata(interface); + + /* the intfdata can be set to NULL only after the + * device files have been removed */ + usb_set_intfdata(interface, NULL); + + usb_put_dev(dev->udev); + + dev_info(&interface->dev, + "Cypress CY7C63xxx device now disconnected\n"); + + kfree(dev); +} + +static struct usb_driver cypress_driver = { + .name = "cypress_cy7c63", + .probe = cypress_probe, + .disconnect = cypress_disconnect, + .id_table = cypress_table, + .dev_groups = cypress_groups, +}; + +module_usb_driver(cypress_driver); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); + +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/misc/cytherm.c b/drivers/usb/misc/cytherm.c new file mode 100644 index 0000000000..875016dd07 --- /dev/null +++ b/drivers/usb/misc/cytherm.c @@ -0,0 +1,357 @@ +// SPDX-License-Identifier: GPL-2.0 +/* -*- linux-c -*- + * Cypress USB Thermometer driver + * + * Copyright (c) 2004 Erik Rigtorp <erkki@linux.nu> <erik@rigtorp.com> + * + * This driver works with Elektor magazine USB Interface as published in + * issue #291. It should also work with the original starter kit/demo board + * from Cypress. + */ + + +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/usb.h> + +#define DRIVER_AUTHOR "Erik Rigtorp" +#define DRIVER_DESC "Cypress USB Thermometer driver" + +#define USB_SKEL_VENDOR_ID 0x04b4 +#define USB_SKEL_PRODUCT_ID 0x0002 + +static const struct usb_device_id id_table[] = { + { USB_DEVICE(USB_SKEL_VENDOR_ID, USB_SKEL_PRODUCT_ID) }, + { } +}; +MODULE_DEVICE_TABLE (usb, id_table); + +/* Structure to hold all of our device specific stuff */ +struct usb_cytherm { + struct usb_device *udev; /* save off the usb device pointer */ + struct usb_interface *interface; /* the interface for this device */ + int brightness; +}; + + +/* Vendor requests */ +/* They all operate on one byte at a time */ +#define PING 0x00 +#define READ_ROM 0x01 /* Reads form ROM, value = address */ +#define READ_RAM 0x02 /* Reads form RAM, value = address */ +#define WRITE_RAM 0x03 /* Write to RAM, value = address, index = data */ +#define READ_PORT 0x04 /* Reads from port, value = address */ +#define WRITE_PORT 0x05 /* Write to port, value = address, index = data */ + + +/* Send a vendor command to device */ +static int vendor_command(struct usb_device *dev, unsigned char request, + unsigned char value, unsigned char index, + void *buf, int size) +{ + return usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), + request, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_OTHER, + value, + index, buf, size, + USB_CTRL_GET_TIMEOUT); +} + + + +#define BRIGHTNESS 0x2c /* RAM location for brightness value */ +#define BRIGHTNESS_SEM 0x2b /* RAM location for brightness semaphore */ + +static ssize_t brightness_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct usb_cytherm *cytherm = usb_get_intfdata(intf); + + return sprintf(buf, "%i", cytherm->brightness); +} + +static ssize_t brightness_store(struct device *dev, struct device_attribute *attr, const char *buf, + size_t count) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct usb_cytherm *cytherm = usb_get_intfdata(intf); + + unsigned char *buffer; + int retval; + + buffer = kmalloc(8, GFP_KERNEL); + if (!buffer) + return 0; + + cytherm->brightness = simple_strtoul(buf, NULL, 10); + + if (cytherm->brightness > 0xFF) + cytherm->brightness = 0xFF; + else if (cytherm->brightness < 0) + cytherm->brightness = 0; + + /* Set brightness */ + retval = vendor_command(cytherm->udev, WRITE_RAM, BRIGHTNESS, + cytherm->brightness, buffer, 8); + if (retval) + dev_dbg(&cytherm->udev->dev, "retval = %d\n", retval); + /* Inform µC that we have changed the brightness setting */ + retval = vendor_command(cytherm->udev, WRITE_RAM, BRIGHTNESS_SEM, + 0x01, buffer, 8); + if (retval) + dev_dbg(&cytherm->udev->dev, "retval = %d\n", retval); + + kfree(buffer); + + return count; +} +static DEVICE_ATTR_RW(brightness); + + +#define TEMP 0x33 /* RAM location for temperature */ +#define SIGN 0x34 /* RAM location for temperature sign */ + +static ssize_t temp_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + + struct usb_interface *intf = to_usb_interface(dev); + struct usb_cytherm *cytherm = usb_get_intfdata(intf); + + int retval; + unsigned char *buffer; + + int temp, sign; + + buffer = kmalloc(8, GFP_KERNEL); + if (!buffer) + return 0; + + /* read temperature */ + retval = vendor_command(cytherm->udev, READ_RAM, TEMP, 0, buffer, 8); + if (retval) + dev_dbg(&cytherm->udev->dev, "retval = %d\n", retval); + temp = buffer[1]; + + /* read sign */ + retval = vendor_command(cytherm->udev, READ_RAM, SIGN, 0, buffer, 8); + if (retval) + dev_dbg(&cytherm->udev->dev, "retval = %d\n", retval); + sign = buffer[1]; + + kfree(buffer); + + return sprintf(buf, "%c%i.%i", sign ? '-' : '+', temp >> 1, + 5*(temp - ((temp >> 1) << 1))); +} +static DEVICE_ATTR_RO(temp); + + +#define BUTTON 0x7a + +static ssize_t button_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + + struct usb_interface *intf = to_usb_interface(dev); + struct usb_cytherm *cytherm = usb_get_intfdata(intf); + + int retval; + unsigned char *buffer; + + buffer = kmalloc(8, GFP_KERNEL); + if (!buffer) + return 0; + + /* check button */ + retval = vendor_command(cytherm->udev, READ_RAM, BUTTON, 0, buffer, 8); + if (retval) + dev_dbg(&cytherm->udev->dev, "retval = %d\n", retval); + + retval = buffer[1]; + + kfree(buffer); + + if (retval) + return sprintf(buf, "1"); + else + return sprintf(buf, "0"); +} +static DEVICE_ATTR_RO(button); + + +static ssize_t port0_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct usb_cytherm *cytherm = usb_get_intfdata(intf); + + int retval; + unsigned char *buffer; + + buffer = kmalloc(8, GFP_KERNEL); + if (!buffer) + return 0; + + retval = vendor_command(cytherm->udev, READ_PORT, 0, 0, buffer, 8); + if (retval) + dev_dbg(&cytherm->udev->dev, "retval = %d\n", retval); + + retval = buffer[1]; + + kfree(buffer); + + return sprintf(buf, "%d", retval); +} + + +static ssize_t port0_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct usb_cytherm *cytherm = usb_get_intfdata(intf); + + unsigned char *buffer; + int retval; + int tmp; + + buffer = kmalloc(8, GFP_KERNEL); + if (!buffer) + return 0; + + tmp = simple_strtoul(buf, NULL, 10); + + if (tmp > 0xFF) + tmp = 0xFF; + else if (tmp < 0) + tmp = 0; + + retval = vendor_command(cytherm->udev, WRITE_PORT, 0, + tmp, buffer, 8); + if (retval) + dev_dbg(&cytherm->udev->dev, "retval = %d\n", retval); + + kfree(buffer); + + return count; +} +static DEVICE_ATTR_RW(port0); + +static ssize_t port1_show(struct device *dev, struct device_attribute *attr, char *buf) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct usb_cytherm *cytherm = usb_get_intfdata(intf); + + int retval; + unsigned char *buffer; + + buffer = kmalloc(8, GFP_KERNEL); + if (!buffer) + return 0; + + retval = vendor_command(cytherm->udev, READ_PORT, 1, 0, buffer, 8); + if (retval) + dev_dbg(&cytherm->udev->dev, "retval = %d\n", retval); + + retval = buffer[1]; + + kfree(buffer); + + return sprintf(buf, "%d", retval); +} + + +static ssize_t port1_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct usb_cytherm *cytherm = usb_get_intfdata(intf); + + unsigned char *buffer; + int retval; + int tmp; + + buffer = kmalloc(8, GFP_KERNEL); + if (!buffer) + return 0; + + tmp = simple_strtoul(buf, NULL, 10); + + if (tmp > 0xFF) + tmp = 0xFF; + else if (tmp < 0) + tmp = 0; + + retval = vendor_command(cytherm->udev, WRITE_PORT, 1, + tmp, buffer, 8); + if (retval) + dev_dbg(&cytherm->udev->dev, "retval = %d\n", retval); + + kfree(buffer); + + return count; +} +static DEVICE_ATTR_RW(port1); + +static struct attribute *cytherm_attrs[] = { + &dev_attr_brightness.attr, + &dev_attr_temp.attr, + &dev_attr_button.attr, + &dev_attr_port0.attr, + &dev_attr_port1.attr, + NULL, +}; +ATTRIBUTE_GROUPS(cytherm); + +static int cytherm_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(interface); + struct usb_cytherm *dev; + int retval = -ENOMEM; + + dev = kzalloc(sizeof(struct usb_cytherm), GFP_KERNEL); + if (!dev) + goto error_mem; + + dev->udev = usb_get_dev(udev); + + usb_set_intfdata(interface, dev); + + dev->brightness = 0xFF; + + dev_info(&interface->dev, + "Cypress thermometer device now attached\n"); + return 0; + +error_mem: + return retval; +} + +static void cytherm_disconnect(struct usb_interface *interface) +{ + struct usb_cytherm *dev; + + dev = usb_get_intfdata(interface); + + /* first remove the files, then NULL the pointer */ + usb_set_intfdata(interface, NULL); + + usb_put_dev(dev->udev); + + kfree(dev); + + dev_info(&interface->dev, "Cypress thermometer now disconnected\n"); +} + +/* usb specific object needed to register this driver with the usb subsystem */ +static struct usb_driver cytherm_driver = { + .name = "cytherm", + .probe = cytherm_probe, + .disconnect = cytherm_disconnect, + .id_table = id_table, + .dev_groups = cytherm_groups, +}; + +module_usb_driver(cytherm_driver); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/misc/ehset.c b/drivers/usb/misc/ehset.c new file mode 100644 index 0000000000..36b6e9fa7f --- /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 0000000000..24d841850e --- /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 0000000000..3eea60437f --- /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 0000000000..78aaee56c2 --- /dev/null +++ b/drivers/usb/misc/ezusb.c @@ -0,0 +1,151 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * EZ-USB specific functions used by some of the USB to Serial drivers. + * + * Copyright (C) 1999 - 2002 Greg Kroah-Hartman (greg@kroah.com) + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/usb.h> +#include <linux/firmware.h> +#include <linux/ihex.h> +#include <linux/usb/ezusb.h> + +struct ezusb_fx_type { + /* EZ-USB Control and Status Register. Bit 0 controls 8051 reset */ + unsigned short cpucs_reg; + unsigned short max_internal_adress; +}; + +static const struct ezusb_fx_type ezusb_fx1 = { + .cpucs_reg = 0x7F92, + .max_internal_adress = 0x1B3F, +}; + +/* Commands for writing to memory */ +#define WRITE_INT_RAM 0xA0 +#define WRITE_EXT_RAM 0xA3 + +static int ezusb_writememory(struct usb_device *dev, int address, + unsigned char *data, int length, __u8 request) +{ + if (!dev) + return -ENODEV; + + return usb_control_msg_send(dev, 0, request, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + address, 0, data, length, 3000, GFP_KERNEL); +} + +static int ezusb_set_reset(struct usb_device *dev, unsigned short cpucs_reg, + unsigned char reset_bit) +{ + int response = ezusb_writememory(dev, cpucs_reg, &reset_bit, 1, WRITE_INT_RAM); + if (response < 0) + dev_err(&dev->dev, "%s-%d failed: %d\n", + __func__, reset_bit, response); + return response; +} + +int ezusb_fx1_set_reset(struct usb_device *dev, unsigned char reset_bit) +{ + return ezusb_set_reset(dev, ezusb_fx1.cpucs_reg, reset_bit); +} +EXPORT_SYMBOL_GPL(ezusb_fx1_set_reset); + +static int ezusb_ihex_firmware_download(struct usb_device *dev, + struct ezusb_fx_type fx, + const char *firmware_path) +{ + int ret = -ENOENT; + const struct firmware *firmware = NULL; + const struct ihex_binrec *record; + + if (request_ihex_firmware(&firmware, firmware_path, + &dev->dev)) { + dev_err(&dev->dev, + "%s - request \"%s\" failed\n", + __func__, firmware_path); + goto out; + } + + ret = ezusb_set_reset(dev, fx.cpucs_reg, 0); + if (ret < 0) + goto out; + + record = (const struct ihex_binrec *)firmware->data; + for (; record; record = ihex_next_binrec(record)) { + if (be32_to_cpu(record->addr) > fx.max_internal_adress) { + ret = ezusb_writememory(dev, be32_to_cpu(record->addr), + (unsigned char *)record->data, + be16_to_cpu(record->len), WRITE_EXT_RAM); + if (ret < 0) { + dev_err(&dev->dev, "%s - ezusb_writememory " + "failed writing internal memory " + "(%d %04X %p %d)\n", __func__, ret, + be32_to_cpu(record->addr), record->data, + be16_to_cpu(record->len)); + goto out; + } + } + } + + ret = ezusb_set_reset(dev, fx.cpucs_reg, 1); + if (ret < 0) + goto out; + record = (const struct ihex_binrec *)firmware->data; + for (; record; record = ihex_next_binrec(record)) { + if (be32_to_cpu(record->addr) <= fx.max_internal_adress) { + ret = ezusb_writememory(dev, be32_to_cpu(record->addr), + (unsigned char *)record->data, + be16_to_cpu(record->len), WRITE_INT_RAM); + if (ret < 0) { + dev_err(&dev->dev, "%s - ezusb_writememory " + "failed writing external memory " + "(%d %04X %p %d)\n", __func__, ret, + be32_to_cpu(record->addr), record->data, + be16_to_cpu(record->len)); + goto out; + } + } + } + ret = ezusb_set_reset(dev, fx.cpucs_reg, 0); +out: + release_firmware(firmware); + return ret; +} + +int ezusb_fx1_ihex_firmware_download(struct usb_device *dev, + const char *firmware_path) +{ + return ezusb_ihex_firmware_download(dev, ezusb_fx1, firmware_path); +} +EXPORT_SYMBOL_GPL(ezusb_fx1_ihex_firmware_download); + +#if 0 +/* + * Once someone one needs these fx2 functions, uncomment them + * and add them to ezusb.h and all should be good. + */ +static struct ezusb_fx_type ezusb_fx2 = { + .cpucs_reg = 0xE600, + .max_internal_adress = 0x3FFF, +}; + +int ezusb_fx2_set_reset(struct usb_device *dev, unsigned char reset_bit) +{ + return ezusb_set_reset(dev, ezusb_fx2.cpucs_reg, reset_bit); +} +EXPORT_SYMBOL_GPL(ezusb_fx2_set_reset); + +int ezusb_fx2_ihex_firmware_download(struct usb_device *dev, + const char *firmware_path) +{ + return ezusb_ihex_firmware_download(dev, ezusb_fx2, firmware_path); +} +EXPORT_SYMBOL_GPL(ezusb_fx2_ihex_firmware_download); +#endif + +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/misc/idmouse.c b/drivers/usb/misc/idmouse.c new file mode 100644 index 0000000000..ea39243efe --- /dev/null +++ b/drivers/usb/misc/idmouse.c @@ -0,0 +1,407 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* Siemens ID Mouse driver v0.6 + + Copyright (C) 2004-5 by Florian 'Floe' Echtler <echtler@fs.tum.de> + and Andreas 'ad' Deresch <aderesch@fs.tum.de> + + Derived from the USB Skeleton driver 1.1, + Copyright (C) 2003 Greg Kroah-Hartman (greg@kroah.com) + + Additional information provided by Martin Reising + <Martin.Reising@natural-computing.de> + +*/ + +#include <linux/kernel.h> +#include <linux/sched/signal.h> +#include <linux/errno.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/completion.h> +#include <linux/mutex.h> +#include <linux/uaccess.h> +#include <linux/usb.h> + +/* image constants */ +#define WIDTH 225 +#define HEIGHT 289 +#define HEADER "P5 225 289 255 " +#define IMGSIZE ((WIDTH * HEIGHT) + sizeof(HEADER)-1) + +#define DRIVER_SHORT "idmouse" +#define DRIVER_AUTHOR "Florian 'Floe' Echtler <echtler@fs.tum.de>" +#define DRIVER_DESC "Siemens ID Mouse FingerTIP Sensor Driver" + +/* minor number for misc USB devices */ +#define USB_IDMOUSE_MINOR_BASE 132 + +/* vendor and device IDs */ +#define ID_SIEMENS 0x0681 +#define ID_IDMOUSE 0x0005 +#define ID_CHERRY 0x0010 + +/* device ID table */ +static const struct usb_device_id idmouse_table[] = { + {USB_DEVICE(ID_SIEMENS, ID_IDMOUSE)}, /* Siemens ID Mouse (Professional) */ + {USB_DEVICE(ID_SIEMENS, ID_CHERRY )}, /* Cherry FingerTIP ID Board */ + {} /* terminating null entry */ +}; + +/* sensor commands */ +#define FTIP_RESET 0x20 +#define FTIP_ACQUIRE 0x21 +#define FTIP_RELEASE 0x22 +#define FTIP_BLINK 0x23 /* LSB of value = blink pulse width */ +#define FTIP_SCROLL 0x24 + +#define ftip_command(dev, command, value, index) \ + usb_control_msg(dev->udev, usb_sndctrlpipe(dev->udev, 0), command, \ + USB_TYPE_VENDOR | USB_RECIP_ENDPOINT | USB_DIR_OUT, value, index, NULL, 0, 1000) + +MODULE_DEVICE_TABLE(usb, idmouse_table); + +/* structure to hold all of our device specific stuff */ +struct usb_idmouse { + + struct usb_device *udev; /* save off the usb device pointer */ + struct usb_interface *interface; /* the interface for this device */ + + unsigned char *bulk_in_buffer; /* the buffer to receive data */ + size_t bulk_in_size; /* the maximum bulk packet size */ + size_t orig_bi_size; /* same as above, but reported by the device */ + __u8 bulk_in_endpointAddr; /* the address of the bulk in endpoint */ + + int open; /* if the port is open or not */ + int present; /* if the device is not disconnected */ + struct mutex lock; /* locks this structure */ + +}; + +/* local function prototypes */ +static ssize_t idmouse_read(struct file *file, char __user *buffer, + size_t count, loff_t * ppos); + +static int idmouse_open(struct inode *inode, struct file *file); +static int idmouse_release(struct inode *inode, struct file *file); + +static int idmouse_probe(struct usb_interface *interface, + const struct usb_device_id *id); + +static void idmouse_disconnect(struct usb_interface *interface); +static int idmouse_suspend(struct usb_interface *intf, pm_message_t message); +static int idmouse_resume(struct usb_interface *intf); + +/* file operation pointers */ +static const struct file_operations idmouse_fops = { + .owner = THIS_MODULE, + .read = idmouse_read, + .open = idmouse_open, + .release = idmouse_release, + .llseek = default_llseek, +}; + +/* class driver information */ +static struct usb_class_driver idmouse_class = { + .name = "idmouse%d", + .fops = &idmouse_fops, + .minor_base = USB_IDMOUSE_MINOR_BASE, +}; + +/* usb specific object needed to register this driver with the usb subsystem */ +static struct usb_driver idmouse_driver = { + .name = DRIVER_SHORT, + .probe = idmouse_probe, + .disconnect = idmouse_disconnect, + .suspend = idmouse_suspend, + .resume = idmouse_resume, + .reset_resume = idmouse_resume, + .id_table = idmouse_table, + .supports_autosuspend = 1, +}; + +static int idmouse_create_image(struct usb_idmouse *dev) +{ + int bytes_read; + int bulk_read; + int result; + + memcpy(dev->bulk_in_buffer, HEADER, sizeof(HEADER)-1); + bytes_read = sizeof(HEADER)-1; + + /* reset the device and set a fast blink rate */ + result = ftip_command(dev, FTIP_RELEASE, 0, 0); + if (result < 0) + goto reset; + result = ftip_command(dev, FTIP_BLINK, 1, 0); + if (result < 0) + goto reset; + + /* initialize the sensor - sending this command twice */ + /* significantly reduces the rate of failed reads */ + result = ftip_command(dev, FTIP_ACQUIRE, 0, 0); + if (result < 0) + goto reset; + result = ftip_command(dev, FTIP_ACQUIRE, 0, 0); + if (result < 0) + goto reset; + + /* start the readout - sending this command twice */ + /* presumably enables the high dynamic range mode */ + result = ftip_command(dev, FTIP_RESET, 0, 0); + if (result < 0) + goto reset; + result = ftip_command(dev, FTIP_RESET, 0, 0); + if (result < 0) + goto reset; + + /* loop over a blocking bulk read to get data from the device */ + while (bytes_read < IMGSIZE) { + result = usb_bulk_msg(dev->udev, + usb_rcvbulkpipe(dev->udev, dev->bulk_in_endpointAddr), + dev->bulk_in_buffer + bytes_read, + dev->bulk_in_size, &bulk_read, 5000); + if (result < 0) { + /* Maybe this error was caused by the increased packet size? */ + /* Reset to the original value and tell userspace to retry. */ + if (dev->bulk_in_size != dev->orig_bi_size) { + dev->bulk_in_size = dev->orig_bi_size; + result = -EAGAIN; + } + break; + } + if (signal_pending(current)) { + result = -EINTR; + break; + } + bytes_read += bulk_read; + } + + /* check for valid image */ + /* right border should be black (0x00) */ + for (bytes_read = sizeof(HEADER)-1 + WIDTH-1; bytes_read < IMGSIZE; bytes_read += WIDTH) + if (dev->bulk_in_buffer[bytes_read] != 0x00) + return -EAGAIN; + + /* lower border should be white (0xFF) */ + for (bytes_read = IMGSIZE-WIDTH; bytes_read < IMGSIZE-1; bytes_read++) + if (dev->bulk_in_buffer[bytes_read] != 0xFF) + return -EAGAIN; + + /* reset the device */ +reset: + ftip_command(dev, FTIP_RELEASE, 0, 0); + + /* should be IMGSIZE == 65040 */ + dev_dbg(&dev->interface->dev, "read %d bytes fingerprint data\n", + bytes_read); + return result; +} + +/* PM operations are nops as this driver does IO only during open() */ +static int idmouse_suspend(struct usb_interface *intf, pm_message_t message) +{ + return 0; +} + +static int idmouse_resume(struct usb_interface *intf) +{ + return 0; +} + +static inline void idmouse_delete(struct usb_idmouse *dev) +{ + kfree(dev->bulk_in_buffer); + kfree(dev); +} + +static int idmouse_open(struct inode *inode, struct file *file) +{ + struct usb_idmouse *dev; + struct usb_interface *interface; + int result; + + /* get the interface from minor number and driver information */ + interface = usb_find_interface(&idmouse_driver, iminor(inode)); + if (!interface) + return -ENODEV; + + /* get the device information block from the interface */ + dev = usb_get_intfdata(interface); + if (!dev) + return -ENODEV; + + /* lock this device */ + mutex_lock(&dev->lock); + + /* check if already open */ + if (dev->open) { + + /* already open, so fail */ + result = -EBUSY; + + } else { + + /* create a new image and check for success */ + result = usb_autopm_get_interface(interface); + if (result) + goto error; + result = idmouse_create_image(dev); + usb_autopm_put_interface(interface); + if (result) + goto error; + + /* increment our usage count for the driver */ + ++dev->open; + + /* save our object in the file's private structure */ + file->private_data = dev; + + } + +error: + + /* unlock this device */ + mutex_unlock(&dev->lock); + return result; +} + +static int idmouse_release(struct inode *inode, struct file *file) +{ + struct usb_idmouse *dev; + + dev = file->private_data; + + if (dev == NULL) + return -ENODEV; + + /* lock our device */ + mutex_lock(&dev->lock); + + --dev->open; + + if (!dev->present) { + /* the device was unplugged before the file was released */ + mutex_unlock(&dev->lock); + idmouse_delete(dev); + } else { + mutex_unlock(&dev->lock); + } + return 0; +} + +static ssize_t idmouse_read(struct file *file, char __user *buffer, size_t count, + loff_t * ppos) +{ + struct usb_idmouse *dev = file->private_data; + int result; + + /* lock this object */ + mutex_lock(&dev->lock); + + /* verify that the device wasn't unplugged */ + if (!dev->present) { + mutex_unlock(&dev->lock); + return -ENODEV; + } + + result = simple_read_from_buffer(buffer, count, ppos, + dev->bulk_in_buffer, IMGSIZE); + /* unlock the device */ + mutex_unlock(&dev->lock); + return result; +} + +static int idmouse_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(interface); + struct usb_idmouse *dev; + struct usb_host_interface *iface_desc; + struct usb_endpoint_descriptor *endpoint; + int result; + + /* check if we have gotten the data or the hid interface */ + iface_desc = interface->cur_altsetting; + if (iface_desc->desc.bInterfaceClass != 0x0A) + return -ENODEV; + + if (iface_desc->desc.bNumEndpoints < 1) + return -ENODEV; + + /* allocate memory for our device state and initialize it */ + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (dev == NULL) + return -ENOMEM; + + mutex_init(&dev->lock); + dev->udev = udev; + dev->interface = interface; + + /* set up the endpoint information - use only the first bulk-in endpoint */ + result = usb_find_bulk_in_endpoint(iface_desc, &endpoint); + if (result) { + dev_err(&interface->dev, "Unable to find bulk-in endpoint.\n"); + idmouse_delete(dev); + return result; + } + + dev->orig_bi_size = usb_endpoint_maxp(endpoint); + dev->bulk_in_size = 0x200; /* works _much_ faster */ + dev->bulk_in_endpointAddr = endpoint->bEndpointAddress; + dev->bulk_in_buffer = kmalloc(IMGSIZE + dev->bulk_in_size, GFP_KERNEL); + if (!dev->bulk_in_buffer) { + idmouse_delete(dev); + return -ENOMEM; + } + + /* allow device read, write and ioctl */ + dev->present = 1; + + /* we can register the device now, as it is ready */ + usb_set_intfdata(interface, dev); + result = usb_register_dev(interface, &idmouse_class); + if (result) { + /* something prevented us from registering this device */ + dev_err(&interface->dev, "Unable to allocate minor number.\n"); + idmouse_delete(dev); + return result; + } + + /* be noisy */ + dev_info(&interface->dev,"%s now attached\n",DRIVER_DESC); + + return 0; +} + +static void idmouse_disconnect(struct usb_interface *interface) +{ + struct usb_idmouse *dev = usb_get_intfdata(interface); + + /* give back our minor */ + usb_deregister_dev(interface, &idmouse_class); + + /* lock the device */ + mutex_lock(&dev->lock); + + /* prevent device read, write and ioctl */ + dev->present = 0; + + /* if the device is opened, idmouse_release will clean this up */ + if (!dev->open) { + mutex_unlock(&dev->lock); + idmouse_delete(dev); + } else { + /* unlock */ + mutex_unlock(&dev->lock); + } + + dev_info(&interface->dev, "disconnected\n"); +} + +module_usb_driver(idmouse_driver); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + diff --git a/drivers/usb/misc/iowarrior.c b/drivers/usb/misc/iowarrior.c new file mode 100644 index 0000000000..1e3df27bab --- /dev/null +++ b/drivers/usb/misc/iowarrior.c @@ -0,0 +1,926 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Native support for the I/O-Warrior USB devices + * + * Copyright (c) 2003-2005, 2020 Code Mercenaries GmbH + * written by Christian Lucht <lucht@codemercs.com> and + * Christoph Jung <jung@codemercs.com> + * + * based on + + * usb-skeleton.c by Greg Kroah-Hartman <greg@kroah.com> + * brlvger.c by Stephane Dalton <sdalton@videotron.ca> + * and Stephane Doyon <s.doyon@videotron.ca> + * + * Released under the GPLv2. + */ + +#include <linux/module.h> +#include <linux/usb.h> +#include <linux/slab.h> +#include <linux/sched.h> +#include <linux/mutex.h> +#include <linux/poll.h> +#include <linux/usb/iowarrior.h> + +#define DRIVER_AUTHOR "Christian Lucht <lucht@codemercs.com>" +#define DRIVER_DESC "USB IO-Warrior driver" + +#define USB_VENDOR_ID_CODEMERCS 1984 +/* low speed iowarrior */ +#define USB_DEVICE_ID_CODEMERCS_IOW40 0x1500 +#define USB_DEVICE_ID_CODEMERCS_IOW24 0x1501 +#define USB_DEVICE_ID_CODEMERCS_IOWPV1 0x1511 +#define USB_DEVICE_ID_CODEMERCS_IOWPV2 0x1512 +/* full speed iowarrior */ +#define USB_DEVICE_ID_CODEMERCS_IOW56 0x1503 +/* fuller speed iowarrior */ +#define USB_DEVICE_ID_CODEMERCS_IOW28 0x1504 +#define USB_DEVICE_ID_CODEMERCS_IOW28L 0x1505 +#define USB_DEVICE_ID_CODEMERCS_IOW100 0x1506 + +/* OEMed devices */ +#define USB_DEVICE_ID_CODEMERCS_IOW24SAG 0x158a +#define USB_DEVICE_ID_CODEMERCS_IOW56AM 0x158b + +/* Get a minor range for your devices from the usb maintainer */ +#ifdef CONFIG_USB_DYNAMIC_MINORS +#define IOWARRIOR_MINOR_BASE 0 +#else +#define IOWARRIOR_MINOR_BASE 208 // SKELETON_MINOR_BASE 192 + 16, not official yet +#endif + +/* interrupt input queue size */ +#define MAX_INTERRUPT_BUFFER 16 +/* + maximum number of urbs that are submitted for writes at the same time, + this applies to the IOWarrior56 only! + IOWarrior24 and IOWarrior40 use synchronous usb_control_msg calls. +*/ +#define MAX_WRITES_IN_FLIGHT 4 + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +static struct usb_driver iowarrior_driver; + +/*--------------*/ +/* data */ +/*--------------*/ + +/* Structure to hold all of our device specific stuff */ +struct iowarrior { + struct mutex mutex; /* locks this structure */ + struct usb_device *udev; /* save off the usb device pointer */ + struct usb_interface *interface; /* the interface for this device */ + unsigned char minor; /* the starting minor number for this device */ + struct usb_endpoint_descriptor *int_out_endpoint; /* endpoint for reading (needed for IOW56 only) */ + struct usb_endpoint_descriptor *int_in_endpoint; /* endpoint for reading */ + struct urb *int_in_urb; /* the urb for reading data */ + unsigned char *int_in_buffer; /* buffer for data to be read */ + unsigned char serial_number; /* to detect lost packages */ + unsigned char *read_queue; /* size is MAX_INTERRUPT_BUFFER * packet size */ + wait_queue_head_t read_wait; + wait_queue_head_t write_wait; /* wait-queue for writing to the device */ + atomic_t write_busy; /* number of write-urbs submitted */ + atomic_t read_idx; + atomic_t intr_idx; + atomic_t overflow_flag; /* signals an index 'rollover' */ + int present; /* this is 1 as long as the device is connected */ + int opened; /* this is 1 if the device is currently open */ + char chip_serial[9]; /* the serial number string of the chip connected */ + int report_size; /* number of bytes in a report */ + u16 product_id; + struct usb_anchor submitted; +}; + +/*--------------*/ +/* globals */ +/*--------------*/ + +#define USB_REQ_GET_REPORT 0x01 +//#if 0 +static int usb_get_report(struct usb_device *dev, + struct usb_host_interface *inter, unsigned char type, + unsigned char id, void *buf, int size) +{ + return usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), + USB_REQ_GET_REPORT, + USB_DIR_IN | USB_TYPE_CLASS | + USB_RECIP_INTERFACE, (type << 8) + id, + inter->desc.bInterfaceNumber, buf, size, + USB_CTRL_GET_TIMEOUT); +} +//#endif + +#define USB_REQ_SET_REPORT 0x09 + +static int usb_set_report(struct usb_interface *intf, unsigned char type, + unsigned char id, void *buf, int size) +{ + return usb_control_msg(interface_to_usbdev(intf), + usb_sndctrlpipe(interface_to_usbdev(intf), 0), + USB_REQ_SET_REPORT, + USB_TYPE_CLASS | USB_RECIP_INTERFACE, + (type << 8) + id, + intf->cur_altsetting->desc.bInterfaceNumber, buf, + size, 1000); +} + +/*---------------------*/ +/* driver registration */ +/*---------------------*/ +/* table of devices that work with this driver */ +static const struct usb_device_id iowarrior_ids[] = { + {USB_DEVICE(USB_VENDOR_ID_CODEMERCS, USB_DEVICE_ID_CODEMERCS_IOW40)}, + {USB_DEVICE(USB_VENDOR_ID_CODEMERCS, USB_DEVICE_ID_CODEMERCS_IOW24)}, + {USB_DEVICE(USB_VENDOR_ID_CODEMERCS, USB_DEVICE_ID_CODEMERCS_IOWPV1)}, + {USB_DEVICE(USB_VENDOR_ID_CODEMERCS, USB_DEVICE_ID_CODEMERCS_IOWPV2)}, + {USB_DEVICE(USB_VENDOR_ID_CODEMERCS, USB_DEVICE_ID_CODEMERCS_IOW56)}, + {USB_DEVICE(USB_VENDOR_ID_CODEMERCS, USB_DEVICE_ID_CODEMERCS_IOW24SAG)}, + {USB_DEVICE(USB_VENDOR_ID_CODEMERCS, USB_DEVICE_ID_CODEMERCS_IOW56AM)}, + {USB_DEVICE(USB_VENDOR_ID_CODEMERCS, USB_DEVICE_ID_CODEMERCS_IOW28)}, + {USB_DEVICE(USB_VENDOR_ID_CODEMERCS, USB_DEVICE_ID_CODEMERCS_IOW28L)}, + {USB_DEVICE(USB_VENDOR_ID_CODEMERCS, USB_DEVICE_ID_CODEMERCS_IOW100)}, + {} /* Terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, iowarrior_ids); + +/* + * USB callback handler for reading data + */ +static void iowarrior_callback(struct urb *urb) +{ + struct iowarrior *dev = urb->context; + int intr_idx; + int read_idx; + int aux_idx; + int offset; + int status = urb->status; + int retval; + + switch (status) { + case 0: + /* success */ + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + return; + default: + goto exit; + } + + intr_idx = atomic_read(&dev->intr_idx); + /* aux_idx become previous intr_idx */ + aux_idx = (intr_idx == 0) ? (MAX_INTERRUPT_BUFFER - 1) : (intr_idx - 1); + read_idx = atomic_read(&dev->read_idx); + + /* queue is not empty and it's interface 0 */ + if ((intr_idx != read_idx) + && (dev->interface->cur_altsetting->desc.bInterfaceNumber == 0)) { + /* + 1 for serial number */ + offset = aux_idx * (dev->report_size + 1); + if (!memcmp + (dev->read_queue + offset, urb->transfer_buffer, + dev->report_size)) { + /* equal values on interface 0 will be ignored */ + goto exit; + } + } + + /* aux_idx become next intr_idx */ + aux_idx = (intr_idx == (MAX_INTERRUPT_BUFFER - 1)) ? 0 : (intr_idx + 1); + if (read_idx == aux_idx) { + /* queue full, dropping oldest input */ + read_idx = (++read_idx == MAX_INTERRUPT_BUFFER) ? 0 : read_idx; + atomic_set(&dev->read_idx, read_idx); + atomic_set(&dev->overflow_flag, 1); + } + + /* +1 for serial number */ + offset = intr_idx * (dev->report_size + 1); + memcpy(dev->read_queue + offset, urb->transfer_buffer, + dev->report_size); + *(dev->read_queue + offset + (dev->report_size)) = dev->serial_number++; + + atomic_set(&dev->intr_idx, aux_idx); + /* tell the blocking read about the new data */ + wake_up_interruptible(&dev->read_wait); + +exit: + retval = usb_submit_urb(urb, GFP_ATOMIC); + if (retval) + dev_err(&dev->interface->dev, "%s - usb_submit_urb failed with result %d\n", + __func__, retval); + +} + +/* + * USB Callback handler for write-ops + */ +static void iowarrior_write_callback(struct urb *urb) +{ + struct iowarrior *dev; + int status = urb->status; + + dev = urb->context; + /* sync/async unlink faults aren't errors */ + if (status && + !(status == -ENOENT || + status == -ECONNRESET || status == -ESHUTDOWN)) { + dev_dbg(&dev->interface->dev, + "nonzero write bulk status received: %d\n", status); + } + /* free up our allocated buffer */ + usb_free_coherent(urb->dev, urb->transfer_buffer_length, + urb->transfer_buffer, urb->transfer_dma); + /* tell a waiting writer the interrupt-out-pipe is available again */ + atomic_dec(&dev->write_busy); + wake_up_interruptible(&dev->write_wait); +} + +/* + * iowarrior_delete + */ +static inline void iowarrior_delete(struct iowarrior *dev) +{ + dev_dbg(&dev->interface->dev, "minor %d\n", dev->minor); + kfree(dev->int_in_buffer); + usb_free_urb(dev->int_in_urb); + kfree(dev->read_queue); + usb_put_intf(dev->interface); + kfree(dev); +} + +/*---------------------*/ +/* fops implementation */ +/*---------------------*/ + +static int read_index(struct iowarrior *dev) +{ + int intr_idx, read_idx; + + read_idx = atomic_read(&dev->read_idx); + intr_idx = atomic_read(&dev->intr_idx); + + return (read_idx == intr_idx ? -1 : read_idx); +} + +/* + * iowarrior_read + */ +static ssize_t iowarrior_read(struct file *file, char __user *buffer, + size_t count, loff_t *ppos) +{ + struct iowarrior *dev; + int read_idx; + int offset; + + dev = file->private_data; + + /* verify that the device wasn't unplugged */ + if (!dev || !dev->present) + return -ENODEV; + + dev_dbg(&dev->interface->dev, "minor %d, count = %zd\n", + dev->minor, count); + + /* read count must be packet size (+ time stamp) */ + if ((count != dev->report_size) + && (count != (dev->report_size + 1))) + return -EINVAL; + + /* repeat until no buffer overrun in callback handler occur */ + do { + atomic_set(&dev->overflow_flag, 0); + if ((read_idx = read_index(dev)) == -1) { + /* queue empty */ + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + else { + //next line will return when there is either new data, or the device is unplugged + int r = wait_event_interruptible(dev->read_wait, + (!dev->present + || (read_idx = + read_index + (dev)) != + -1)); + if (r) { + //we were interrupted by a signal + return -ERESTART; + } + if (!dev->present) { + //The device was unplugged + return -ENODEV; + } + if (read_idx == -1) { + // Can this happen ??? + return 0; + } + } + } + + offset = read_idx * (dev->report_size + 1); + if (copy_to_user(buffer, dev->read_queue + offset, count)) { + return -EFAULT; + } + } while (atomic_read(&dev->overflow_flag)); + + read_idx = ++read_idx == MAX_INTERRUPT_BUFFER ? 0 : read_idx; + atomic_set(&dev->read_idx, read_idx); + return count; +} + +/* + * iowarrior_write + */ +static ssize_t iowarrior_write(struct file *file, + const char __user *user_buffer, + size_t count, loff_t *ppos) +{ + struct iowarrior *dev; + int retval = 0; + char *buf = NULL; /* for IOW24 and IOW56 we need a buffer */ + struct urb *int_out_urb = NULL; + + dev = file->private_data; + + mutex_lock(&dev->mutex); + /* verify that the device wasn't unplugged */ + if (!dev->present) { + retval = -ENODEV; + goto exit; + } + dev_dbg(&dev->interface->dev, "minor %d, count = %zd\n", + dev->minor, count); + /* if count is 0 we're already done */ + if (count == 0) { + retval = 0; + goto exit; + } + /* We only accept full reports */ + if (count != dev->report_size) { + retval = -EINVAL; + goto exit; + } + switch (dev->product_id) { + case USB_DEVICE_ID_CODEMERCS_IOW24: + case USB_DEVICE_ID_CODEMERCS_IOW24SAG: + case USB_DEVICE_ID_CODEMERCS_IOWPV1: + case USB_DEVICE_ID_CODEMERCS_IOWPV2: + case USB_DEVICE_ID_CODEMERCS_IOW40: + /* IOW24 and IOW40 use a synchronous call */ + buf = memdup_user(user_buffer, count); + if (IS_ERR(buf)) { + retval = PTR_ERR(buf); + goto exit; + } + retval = usb_set_report(dev->interface, 2, 0, buf, count); + kfree(buf); + goto exit; + case USB_DEVICE_ID_CODEMERCS_IOW56: + case USB_DEVICE_ID_CODEMERCS_IOW56AM: + case USB_DEVICE_ID_CODEMERCS_IOW28: + case USB_DEVICE_ID_CODEMERCS_IOW28L: + case USB_DEVICE_ID_CODEMERCS_IOW100: + /* The IOW56 uses asynchronous IO and more urbs */ + if (atomic_read(&dev->write_busy) == MAX_WRITES_IN_FLIGHT) { + /* Wait until we are below the limit for submitted urbs */ + if (file->f_flags & O_NONBLOCK) { + retval = -EAGAIN; + goto exit; + } else { + retval = wait_event_interruptible(dev->write_wait, + (!dev->present || (atomic_read (&dev-> write_busy) < MAX_WRITES_IN_FLIGHT))); + if (retval) { + /* we were interrupted by a signal */ + retval = -ERESTART; + goto exit; + } + if (!dev->present) { + /* The device was unplugged */ + retval = -ENODEV; + goto exit; + } + if (!dev->opened) { + /* We were closed while waiting for an URB */ + retval = -ENODEV; + goto exit; + } + } + } + atomic_inc(&dev->write_busy); + int_out_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!int_out_urb) { + retval = -ENOMEM; + goto error_no_urb; + } + buf = usb_alloc_coherent(dev->udev, dev->report_size, + GFP_KERNEL, &int_out_urb->transfer_dma); + if (!buf) { + retval = -ENOMEM; + dev_dbg(&dev->interface->dev, + "Unable to allocate buffer\n"); + goto error_no_buffer; + } + usb_fill_int_urb(int_out_urb, dev->udev, + usb_sndintpipe(dev->udev, + dev->int_out_endpoint->bEndpointAddress), + buf, dev->report_size, + iowarrior_write_callback, dev, + dev->int_out_endpoint->bInterval); + int_out_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + if (copy_from_user(buf, user_buffer, count)) { + retval = -EFAULT; + goto error; + } + usb_anchor_urb(int_out_urb, &dev->submitted); + retval = usb_submit_urb(int_out_urb, GFP_KERNEL); + if (retval) { + dev_dbg(&dev->interface->dev, + "submit error %d for urb nr.%d\n", + retval, atomic_read(&dev->write_busy)); + usb_unanchor_urb(int_out_urb); + goto error; + } + /* submit was ok */ + retval = count; + usb_free_urb(int_out_urb); + goto exit; + default: + /* what do we have here ? An unsupported Product-ID ? */ + dev_err(&dev->interface->dev, "%s - not supported for product=0x%x\n", + __func__, dev->product_id); + retval = -EFAULT; + goto exit; + } +error: + usb_free_coherent(dev->udev, dev->report_size, buf, + int_out_urb->transfer_dma); +error_no_buffer: + usb_free_urb(int_out_urb); +error_no_urb: + atomic_dec(&dev->write_busy); + wake_up_interruptible(&dev->write_wait); +exit: + mutex_unlock(&dev->mutex); + return retval; +} + +/* + * iowarrior_ioctl + */ +static long iowarrior_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct iowarrior *dev = NULL; + __u8 *buffer; + __u8 __user *user_buffer; + int retval; + int io_res; /* checks for bytes read/written and copy_to/from_user results */ + + dev = file->private_data; + if (!dev) + return -ENODEV; + + buffer = kzalloc(dev->report_size, GFP_KERNEL); + if (!buffer) + return -ENOMEM; + + mutex_lock(&dev->mutex); + + /* verify that the device wasn't unplugged */ + if (!dev->present) { + retval = -ENODEV; + goto error_out; + } + + dev_dbg(&dev->interface->dev, "minor %d, cmd 0x%.4x, arg %ld\n", + dev->minor, cmd, arg); + + retval = 0; + io_res = 0; + switch (cmd) { + case IOW_WRITE: + if (dev->product_id == USB_DEVICE_ID_CODEMERCS_IOW24 || + dev->product_id == USB_DEVICE_ID_CODEMERCS_IOW24SAG || + dev->product_id == USB_DEVICE_ID_CODEMERCS_IOWPV1 || + dev->product_id == USB_DEVICE_ID_CODEMERCS_IOWPV2 || + dev->product_id == USB_DEVICE_ID_CODEMERCS_IOW40) { + user_buffer = (__u8 __user *)arg; + io_res = copy_from_user(buffer, user_buffer, + dev->report_size); + if (io_res) { + retval = -EFAULT; + } else { + io_res = usb_set_report(dev->interface, 2, 0, + buffer, + dev->report_size); + if (io_res < 0) + retval = io_res; + } + } else { + retval = -EINVAL; + dev_err(&dev->interface->dev, + "ioctl 'IOW_WRITE' is not supported for product=0x%x.\n", + dev->product_id); + } + break; + case IOW_READ: + user_buffer = (__u8 __user *)arg; + io_res = usb_get_report(dev->udev, + dev->interface->cur_altsetting, 1, 0, + buffer, dev->report_size); + if (io_res < 0) + retval = io_res; + else { + io_res = copy_to_user(user_buffer, buffer, dev->report_size); + if (io_res) + retval = -EFAULT; + } + break; + case IOW_GETINFO: + { + /* Report available information for the device */ + struct iowarrior_info info; + /* needed for power consumption */ + struct usb_config_descriptor *cfg_descriptor = &dev->udev->actconfig->desc; + + memset(&info, 0, sizeof(info)); + /* directly from the descriptor */ + info.vendor = le16_to_cpu(dev->udev->descriptor.idVendor); + info.product = dev->product_id; + info.revision = le16_to_cpu(dev->udev->descriptor.bcdDevice); + + /* 0==UNKNOWN, 1==LOW(usb1.1) ,2=FULL(usb1.1), 3=HIGH(usb2.0) */ + info.speed = dev->udev->speed; + info.if_num = dev->interface->cur_altsetting->desc.bInterfaceNumber; + info.report_size = dev->report_size; + + /* serial number string has been read earlier 8 chars or empty string */ + memcpy(info.serial, dev->chip_serial, + sizeof(dev->chip_serial)); + if (cfg_descriptor == NULL) { + info.power = -1; /* no information available */ + } else { + /* the MaxPower is stored in units of 2mA to make it fit into a byte-value */ + info.power = cfg_descriptor->bMaxPower * 2; + } + io_res = copy_to_user((struct iowarrior_info __user *)arg, &info, + sizeof(struct iowarrior_info)); + if (io_res) + retval = -EFAULT; + break; + } + default: + /* return that we did not understand this ioctl call */ + retval = -ENOTTY; + break; + } +error_out: + /* unlock the device */ + mutex_unlock(&dev->mutex); + kfree(buffer); + return retval; +} + +/* + * iowarrior_open + */ +static int iowarrior_open(struct inode *inode, struct file *file) +{ + struct iowarrior *dev = NULL; + struct usb_interface *interface; + int subminor; + int retval = 0; + + subminor = iminor(inode); + + interface = usb_find_interface(&iowarrior_driver, subminor); + if (!interface) { + pr_err("%s - error, can't find device for minor %d\n", + __func__, subminor); + return -ENODEV; + } + + dev = usb_get_intfdata(interface); + if (!dev) + return -ENODEV; + + mutex_lock(&dev->mutex); + + /* Only one process can open each device, no sharing. */ + if (dev->opened) { + retval = -EBUSY; + goto out; + } + + /* setup interrupt handler for receiving values */ + if ((retval = usb_submit_urb(dev->int_in_urb, GFP_KERNEL)) < 0) { + dev_err(&interface->dev, "Error %d while submitting URB\n", retval); + retval = -EFAULT; + goto out; + } + /* increment our usage count for the driver */ + ++dev->opened; + /* save our object in the file's private structure */ + file->private_data = dev; + retval = 0; + +out: + mutex_unlock(&dev->mutex); + return retval; +} + +/* + * iowarrior_release + */ +static int iowarrior_release(struct inode *inode, struct file *file) +{ + struct iowarrior *dev; + int retval = 0; + + dev = file->private_data; + if (!dev) + return -ENODEV; + + dev_dbg(&dev->interface->dev, "minor %d\n", dev->minor); + + /* lock our device */ + mutex_lock(&dev->mutex); + + if (dev->opened <= 0) { + retval = -ENODEV; /* close called more than once */ + mutex_unlock(&dev->mutex); + } else { + dev->opened = 0; /* we're closing now */ + retval = 0; + if (dev->present) { + /* + The device is still connected so we only shutdown + pending read-/write-ops. + */ + usb_kill_urb(dev->int_in_urb); + wake_up_interruptible(&dev->read_wait); + wake_up_interruptible(&dev->write_wait); + mutex_unlock(&dev->mutex); + } else { + /* The device was unplugged, cleanup resources */ + mutex_unlock(&dev->mutex); + iowarrior_delete(dev); + } + } + return retval; +} + +static __poll_t iowarrior_poll(struct file *file, poll_table * wait) +{ + struct iowarrior *dev = file->private_data; + __poll_t mask = 0; + + if (!dev->present) + return EPOLLERR | EPOLLHUP; + + poll_wait(file, &dev->read_wait, wait); + poll_wait(file, &dev->write_wait, wait); + + if (!dev->present) + return EPOLLERR | EPOLLHUP; + + if (read_index(dev) != -1) + mask |= EPOLLIN | EPOLLRDNORM; + + if (atomic_read(&dev->write_busy) < MAX_WRITES_IN_FLIGHT) + mask |= EPOLLOUT | EPOLLWRNORM; + return mask; +} + +/* + * File operations needed when we register this driver. + * This assumes that this driver NEEDS file operations, + * of course, which means that the driver is expected + * to have a node in the /dev directory. If the USB + * device were for a network interface then the driver + * would use "struct net_driver" instead, and a serial + * device would use "struct tty_driver". + */ +static const struct file_operations iowarrior_fops = { + .owner = THIS_MODULE, + .write = iowarrior_write, + .read = iowarrior_read, + .unlocked_ioctl = iowarrior_ioctl, + .open = iowarrior_open, + .release = iowarrior_release, + .poll = iowarrior_poll, + .llseek = noop_llseek, +}; + +static char *iowarrior_devnode(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; + + /* allocate memory for our device state and initialize it */ + dev = kzalloc(sizeof(struct iowarrior), GFP_KERNEL); + if (!dev) + return retval; + + mutex_init(&dev->mutex); + + atomic_set(&dev->intr_idx, 0); + atomic_set(&dev->read_idx, 0); + atomic_set(&dev->overflow_flag, 0); + init_waitqueue_head(&dev->read_wait); + atomic_set(&dev->write_busy, 0); + init_waitqueue_head(&dev->write_wait); + + dev->udev = udev; + dev->interface = usb_get_intf(interface); + + iface_desc = interface->cur_altsetting; + dev->product_id = le16_to_cpu(udev->descriptor.idProduct); + + init_usb_anchor(&dev->submitted); + + res = usb_find_last_int_in_endpoint(iface_desc, &dev->int_in_endpoint); + if (res) { + dev_err(&interface->dev, "no interrupt-in endpoint found\n"); + retval = res; + goto error; + } + + if ((dev->product_id == USB_DEVICE_ID_CODEMERCS_IOW56) || + (dev->product_id == USB_DEVICE_ID_CODEMERCS_IOW56AM) || + (dev->product_id == USB_DEVICE_ID_CODEMERCS_IOW28) || + (dev->product_id == USB_DEVICE_ID_CODEMERCS_IOW28L) || + (dev->product_id == USB_DEVICE_ID_CODEMERCS_IOW100)) { + res = usb_find_last_int_out_endpoint(iface_desc, + &dev->int_out_endpoint); + if (res) { + dev_err(&interface->dev, "no interrupt-out endpoint found\n"); + retval = res; + goto error; + } + } + + /* we have to check the report_size often, so remember it in the endianness suitable for our machine */ + dev->report_size = usb_endpoint_maxp(dev->int_in_endpoint); + + /* + * Some devices need the report size to be different than the + * endpoint size. + */ + if (dev->interface->cur_altsetting->desc.bInterfaceNumber == 0) { + switch (dev->product_id) { + case USB_DEVICE_ID_CODEMERCS_IOW56: + case USB_DEVICE_ID_CODEMERCS_IOW56AM: + dev->report_size = 7; + break; + + case USB_DEVICE_ID_CODEMERCS_IOW28: + case USB_DEVICE_ID_CODEMERCS_IOW28L: + dev->report_size = 4; + break; + + case USB_DEVICE_ID_CODEMERCS_IOW100: + dev->report_size = 12; + break; + } + } + + /* create the urb and buffer for reading */ + dev->int_in_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!dev->int_in_urb) + goto error; + dev->int_in_buffer = kmalloc(dev->report_size, GFP_KERNEL); + if (!dev->int_in_buffer) + goto error; + usb_fill_int_urb(dev->int_in_urb, dev->udev, + usb_rcvintpipe(dev->udev, + dev->int_in_endpoint->bEndpointAddress), + dev->int_in_buffer, dev->report_size, + iowarrior_callback, dev, + dev->int_in_endpoint->bInterval); + /* create an internal buffer for interrupt data from the device */ + dev->read_queue = + kmalloc_array(dev->report_size + 1, MAX_INTERRUPT_BUFFER, + GFP_KERNEL); + if (!dev->read_queue) + goto error; + /* Get the serial-number of the chip */ + memset(dev->chip_serial, 0x00, sizeof(dev->chip_serial)); + usb_string(udev, udev->descriptor.iSerialNumber, dev->chip_serial, + sizeof(dev->chip_serial)); + if (strlen(dev->chip_serial) != 8) + memset(dev->chip_serial, 0x00, sizeof(dev->chip_serial)); + + /* Set the idle timeout to 0, if this is interface 0 */ + if (dev->interface->cur_altsetting->desc.bInterfaceNumber == 0) { + usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + 0x0A, + USB_TYPE_CLASS | USB_RECIP_INTERFACE, 0, + 0, NULL, 0, USB_CTRL_SET_TIMEOUT); + } + /* allow device read and ioctl */ + dev->present = 1; + + /* we can register the device now, as it is ready */ + usb_set_intfdata(interface, dev); + + retval = usb_register_dev(interface, &iowarrior_class); + if (retval) { + /* something prevented us from registering this driver */ + dev_err(&interface->dev, "Not able to get a minor for this device.\n"); + goto error; + } + + dev->minor = interface->minor; + + /* let the user know what node this device is now attached to */ + dev_info(&interface->dev, "IOWarrior product=0x%x, serial=%s interface=%d " + "now attached to iowarrior%d\n", dev->product_id, dev->chip_serial, + iface_desc->desc.bInterfaceNumber, dev->minor - IOWARRIOR_MINOR_BASE); + return retval; + +error: + iowarrior_delete(dev); + return retval; +} + +/* + * iowarrior_disconnect + * + * Called by the usb core when the device is removed from the system. + */ +static void iowarrior_disconnect(struct usb_interface *interface) +{ + struct iowarrior *dev = usb_get_intfdata(interface); + int minor = dev->minor; + + usb_deregister_dev(interface, &iowarrior_class); + + mutex_lock(&dev->mutex); + + /* prevent device read, write and ioctl */ + dev->present = 0; + + if (dev->opened) { + /* There is a process that holds a filedescriptor to the device , + so we only shutdown read-/write-ops going on. + Deleting the device is postponed until close() was called. + */ + usb_kill_urb(dev->int_in_urb); + usb_kill_anchored_urbs(&dev->submitted); + wake_up_interruptible(&dev->read_wait); + wake_up_interruptible(&dev->write_wait); + mutex_unlock(&dev->mutex); + } else { + /* no process is using the device, cleanup now */ + mutex_unlock(&dev->mutex); + iowarrior_delete(dev); + } + + dev_info(&interface->dev, "I/O-Warror #%d now disconnected\n", + minor - IOWARRIOR_MINOR_BASE); +} + +/* usb specific object needed to register this driver with the usb subsystem */ +static struct usb_driver iowarrior_driver = { + .name = "iowarrior", + .probe = iowarrior_probe, + .disconnect = iowarrior_disconnect, + .id_table = iowarrior_ids, +}; + +module_usb_driver(iowarrior_driver); diff --git a/drivers/usb/misc/isight_firmware.c b/drivers/usb/misc/isight_firmware.c new file mode 100644 index 0000000000..4d30095d6a --- /dev/null +++ b/drivers/usb/misc/isight_firmware.c @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for loading USB isight firmware + * + * Copyright (C) 2008 Matthew Garrett <mjg@redhat.com> + * + * The USB isight cameras in recent Apples are roughly compatible with the USB + * video class specification, and can be driven by uvcvideo. However, they + * need firmware to be loaded beforehand. After firmware loading, the device + * detaches from the USB bus and reattaches with a new device ID. It can then + * be claimed by the uvc driver. + * + * The firmware is non-free and must be extracted by the user. Tools to do this + * are available at http://bersace03.free.fr/ift/ + * + * The isight firmware loading was reverse engineered by Johannes Berg + * <johannes@sipsolutions.de>, and this driver is based on code by Ronald + * Bultje <rbultje@ronald.bitfreak.net> + */ + +#include <linux/usb.h> +#include <linux/firmware.h> +#include <linux/errno.h> +#include <linux/module.h> +#include <linux/slab.h> + +static const struct usb_device_id id_table[] = { + {USB_DEVICE(0x05ac, 0x8300)}, + {}, +}; + +MODULE_DEVICE_TABLE(usb, id_table); + +static int isight_firmware_load(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *dev = interface_to_usbdev(intf); + int llen, len, req, ret = 0; + const struct firmware *firmware; + unsigned char *buf = kmalloc(50, GFP_KERNEL); + unsigned char data[4]; + const u8 *ptr; + + if (!buf) + return -ENOMEM; + + if (request_firmware(&firmware, "isight.fw", &dev->dev) != 0) { + printk(KERN_ERR "Unable to load isight firmware\n"); + ret = -ENODEV; + goto out; + } + + ptr = firmware->data; + + buf[0] = 0x01; + if (usb_control_msg + (dev, usb_sndctrlpipe(dev, 0), 0xa0, 0x40, 0xe600, 0, buf, 1, + 300) != 1) { + printk(KERN_ERR + "Failed to initialise isight firmware loader\n"); + ret = -ENODEV; + goto out; + } + + while (ptr+4 <= firmware->data+firmware->size) { + memcpy(data, ptr, 4); + len = (data[0] << 8 | data[1]); + req = (data[2] << 8 | data[3]); + ptr += 4; + + if (len == 0x8001) + break; /* success */ + else if (len == 0) + continue; + + for (; len > 0; req += 50) { + llen = min(len, 50); + len -= llen; + if (ptr+llen > firmware->data+firmware->size) { + printk(KERN_ERR + "Malformed isight firmware"); + ret = -ENODEV; + goto out; + } + memcpy(buf, ptr, llen); + + ptr += llen; + + if (usb_control_msg + (dev, usb_sndctrlpipe(dev, 0), 0xa0, 0x40, req, 0, + buf, llen, 300) != llen) { + printk(KERN_ERR + "Failed to load isight firmware\n"); + ret = -ENODEV; + goto out; + } + + } + } + + buf[0] = 0x00; + if (usb_control_msg + (dev, usb_sndctrlpipe(dev, 0), 0xa0, 0x40, 0xe600, 0, buf, 1, + 300) != 1) { + printk(KERN_ERR "isight firmware loading completion failed\n"); + ret = -ENODEV; + } + +out: + kfree(buf); + release_firmware(firmware); + return ret; +} + +MODULE_FIRMWARE("isight.fw"); + +static void isight_firmware_disconnect(struct usb_interface *intf) +{ +} + +static struct usb_driver isight_firmware_driver = { + .name = "isight_firmware", + .probe = isight_firmware_load, + .disconnect = isight_firmware_disconnect, + .id_table = id_table, +}; + +module_usb_driver(isight_firmware_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Matthew Garrett <mjg@redhat.com>"); diff --git a/drivers/usb/misc/ldusb.c b/drivers/usb/misc/ldusb.c new file mode 100644 index 0000000000..7cbef74dfc --- /dev/null +++ b/drivers/usb/misc/ldusb.c @@ -0,0 +1,797 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Generic USB driver for report based interrupt in/out devices + * like LD Didactic's USB devices. LD Didactic's USB devices are + * HID devices which do not use HID report definitons (they use + * raw interrupt in and our reports only for communication). + * + * This driver uses a ring buffer for time critical reading of + * interrupt in reports and provides read and write methods for + * raw interrupt reports (similar to the Windows HID driver). + * Devices based on the book USB COMPLETE by Jan Axelson may need + * such a compatibility to the Windows HID driver. + * + * Copyright (C) 2005 Michael Hund <mhund@ld-didactic.de> + * + * Derived from Lego USB Tower driver + * Copyright (C) 2003 David Glance <advidgsf@sourceforge.net> + * 2001-2004 Juergen Stuber <starblue@users.sourceforge.net> + */ + +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/mutex.h> + +#include <linux/uaccess.h> +#include <linux/input.h> +#include <linux/usb.h> +#include <linux/poll.h> + +/* Define these values to match your devices */ +#define USB_VENDOR_ID_LD 0x0f11 /* USB Vendor ID of LD Didactic GmbH */ +#define USB_DEVICE_ID_LD_CASSY 0x1000 /* USB Product ID of CASSY-S modules with 8 bytes endpoint size */ +#define USB_DEVICE_ID_LD_CASSY2 0x1001 /* USB Product ID of CASSY-S modules with 64 bytes endpoint size */ +#define USB_DEVICE_ID_LD_POCKETCASSY 0x1010 /* USB Product ID of Pocket-CASSY */ +#define USB_DEVICE_ID_LD_POCKETCASSY2 0x1011 /* USB Product ID of Pocket-CASSY 2 (reserved) */ +#define USB_DEVICE_ID_LD_MOBILECASSY 0x1020 /* USB Product ID of Mobile-CASSY */ +#define USB_DEVICE_ID_LD_MOBILECASSY2 0x1021 /* USB Product ID of Mobile-CASSY 2 (reserved) */ +#define USB_DEVICE_ID_LD_MICROCASSYVOLTAGE 0x1031 /* USB Product ID of Micro-CASSY Voltage */ +#define USB_DEVICE_ID_LD_MICROCASSYCURRENT 0x1032 /* USB Product ID of Micro-CASSY Current */ +#define USB_DEVICE_ID_LD_MICROCASSYTIME 0x1033 /* USB Product ID of Micro-CASSY Time (reserved) */ +#define USB_DEVICE_ID_LD_MICROCASSYTEMPERATURE 0x1035 /* USB Product ID of Micro-CASSY Temperature */ +#define USB_DEVICE_ID_LD_MICROCASSYPH 0x1038 /* USB Product ID of Micro-CASSY pH */ +#define USB_DEVICE_ID_LD_POWERANALYSERCASSY 0x1040 /* USB Product ID of Power Analyser CASSY */ +#define USB_DEVICE_ID_LD_CONVERTERCONTROLLERCASSY 0x1042 /* USB Product ID of Converter Controller CASSY */ +#define USB_DEVICE_ID_LD_MACHINETESTCASSY 0x1043 /* USB Product ID of Machine Test CASSY */ +#define USB_DEVICE_ID_LD_JWM 0x1080 /* USB Product ID of Joule and Wattmeter */ +#define USB_DEVICE_ID_LD_DMMP 0x1081 /* USB Product ID of Digital Multimeter P (reserved) */ +#define USB_DEVICE_ID_LD_UMIP 0x1090 /* USB Product ID of UMI P */ +#define USB_DEVICE_ID_LD_UMIC 0x10A0 /* USB Product ID of UMI C */ +#define USB_DEVICE_ID_LD_UMIB 0x10B0 /* USB Product ID of UMI B */ +#define USB_DEVICE_ID_LD_XRAY 0x1100 /* USB Product ID of X-Ray Apparatus 55481 */ +#define USB_DEVICE_ID_LD_XRAY2 0x1101 /* USB Product ID of X-Ray Apparatus 554800 */ +#define USB_DEVICE_ID_LD_XRAYCT 0x1110 /* USB Product ID of X-Ray Apparatus CT 554821*/ +#define USB_DEVICE_ID_LD_VIDEOCOM 0x1200 /* USB Product ID of VideoCom */ +#define USB_DEVICE_ID_LD_MOTOR 0x1210 /* USB Product ID of Motor (reserved) */ +#define USB_DEVICE_ID_LD_COM3LAB 0x2000 /* USB Product ID of COM3LAB */ +#define USB_DEVICE_ID_LD_TELEPORT 0x2010 /* USB Product ID of Terminal Adapter */ +#define USB_DEVICE_ID_LD_NETWORKANALYSER 0x2020 /* USB Product ID of Network Analyser */ +#define USB_DEVICE_ID_LD_POWERCONTROL 0x2030 /* USB Product ID of Converter Control Unit */ +#define USB_DEVICE_ID_LD_MACHINETEST 0x2040 /* USB Product ID of Machine Test System */ +#define USB_DEVICE_ID_LD_MOSTANALYSER 0x2050 /* USB Product ID of MOST Protocol Analyser */ +#define USB_DEVICE_ID_LD_MOSTANALYSER2 0x2051 /* USB Product ID of MOST Protocol Analyser 2 */ +#define USB_DEVICE_ID_LD_ABSESP 0x2060 /* USB Product ID of ABS ESP */ +#define USB_DEVICE_ID_LD_AUTODATABUS 0x2070 /* USB Product ID of Automotive Data Buses */ +#define USB_DEVICE_ID_LD_MCT 0x2080 /* USB Product ID of Microcontroller technique */ +#define USB_DEVICE_ID_LD_HYBRID 0x2090 /* USB Product ID of Automotive Hybrid */ +#define USB_DEVICE_ID_LD_HEATCONTROL 0x20A0 /* USB Product ID of Heat control */ + +#ifdef CONFIG_USB_DYNAMIC_MINORS +#define USB_LD_MINOR_BASE 0 +#else +#define USB_LD_MINOR_BASE 176 +#endif + +/* table of devices that work with this driver */ +static const struct usb_device_id ld_usb_table[] = { + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_CASSY) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_CASSY2) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_POCKETCASSY) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_POCKETCASSY2) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_MOBILECASSY) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_MOBILECASSY2) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_MICROCASSYVOLTAGE) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_MICROCASSYCURRENT) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_MICROCASSYTIME) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_MICROCASSYTEMPERATURE) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_MICROCASSYPH) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_POWERANALYSERCASSY) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_CONVERTERCONTROLLERCASSY) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_MACHINETESTCASSY) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_JWM) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_DMMP) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_UMIP) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_UMIC) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_UMIB) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_XRAY) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_XRAY2) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_VIDEOCOM) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_MOTOR) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_COM3LAB) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_TELEPORT) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_NETWORKANALYSER) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_POWERCONTROL) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_MACHINETEST) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_MOSTANALYSER) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_MOSTANALYSER2) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_ABSESP) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_AUTODATABUS) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_MCT) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_HYBRID) }, + { USB_DEVICE(USB_VENDOR_ID_LD, USB_DEVICE_ID_LD_HEATCONTROL) }, + { } /* Terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, ld_usb_table); +MODULE_AUTHOR("Michael Hund <mhund@ld-didactic.de>"); +MODULE_DESCRIPTION("LD USB Driver"); +MODULE_LICENSE("GPL"); + +/* All interrupt in transfers are collected in a ring buffer to + * avoid racing conditions and get better performance of the driver. + */ +static int ring_buffer_size = 128; +module_param(ring_buffer_size, int, 0000); +MODULE_PARM_DESC(ring_buffer_size, "Read ring buffer size in reports"); + +/* The write_buffer can contain more than one interrupt out transfer. + */ +static int write_buffer_size = 10; +module_param(write_buffer_size, int, 0000); +MODULE_PARM_DESC(write_buffer_size, "Write buffer size in reports"); + +/* As of kernel version 2.6.4 ehci-hcd uses an + * "only one interrupt transfer per frame" shortcut + * to simplify the scheduling of periodic transfers. + * This conflicts with our standard 1ms intervals for in and out URBs. + * We use default intervals of 2ms for in and 2ms for out transfers, + * which should be fast enough. + * Increase the interval to allow more devices that do interrupt transfers, + * or set to 1 to use the standard interval from the endpoint descriptors. + */ +static int min_interrupt_in_interval = 2; +module_param(min_interrupt_in_interval, int, 0000); +MODULE_PARM_DESC(min_interrupt_in_interval, "Minimum interrupt in interval in ms"); + +static int min_interrupt_out_interval = 2; +module_param(min_interrupt_out_interval, int, 0000); +MODULE_PARM_DESC(min_interrupt_out_interval, "Minimum interrupt out interval in ms"); + +/* Structure to hold all of our device specific stuff */ +struct ld_usb { + struct mutex mutex; /* locks this structure */ + struct usb_interface *intf; /* save off the usb interface pointer */ + unsigned long disconnected:1; + + int open_count; /* number of times this port has been opened */ + + char *ring_buffer; + unsigned int ring_head; + unsigned int ring_tail; + + wait_queue_head_t read_wait; + wait_queue_head_t write_wait; + + char *interrupt_in_buffer; + struct usb_endpoint_descriptor *interrupt_in_endpoint; + struct urb *interrupt_in_urb; + int interrupt_in_interval; + size_t interrupt_in_endpoint_size; + int interrupt_in_running; + int interrupt_in_done; + int buffer_overflow; + spinlock_t rbsl; + + char *interrupt_out_buffer; + struct usb_endpoint_descriptor *interrupt_out_endpoint; + struct urb *interrupt_out_urb; + int interrupt_out_interval; + size_t interrupt_out_endpoint_size; + int interrupt_out_busy; +}; + +static struct usb_driver ld_usb_driver; + +/* + * ld_usb_abort_transfers + * aborts transfers and frees associated data structures + */ +static void ld_usb_abort_transfers(struct ld_usb *dev) +{ + /* shutdown transfer */ + if (dev->interrupt_in_running) { + dev->interrupt_in_running = 0; + usb_kill_urb(dev->interrupt_in_urb); + } + if (dev->interrupt_out_busy) + usb_kill_urb(dev->interrupt_out_urb); +} + +/* + * ld_usb_delete + */ +static void ld_usb_delete(struct ld_usb *dev) +{ + /* free data structures */ + usb_free_urb(dev->interrupt_in_urb); + usb_free_urb(dev->interrupt_out_urb); + kfree(dev->ring_buffer); + kfree(dev->interrupt_in_buffer); + kfree(dev->interrupt_out_buffer); + kfree(dev); +} + +/* + * ld_usb_interrupt_in_callback + */ +static void ld_usb_interrupt_in_callback(struct urb *urb) +{ + struct ld_usb *dev = urb->context; + size_t *actual_buffer; + unsigned int next_ring_head; + int status = urb->status; + unsigned long flags; + int retval; + + if (status) { + if (status == -ENOENT || + status == -ECONNRESET || + status == -ESHUTDOWN) { + goto exit; + } else { + dev_dbg(&dev->intf->dev, + "%s: nonzero status received: %d\n", __func__, + status); + spin_lock_irqsave(&dev->rbsl, flags); + goto resubmit; /* maybe we can recover */ + } + } + + spin_lock_irqsave(&dev->rbsl, flags); + if (urb->actual_length > 0) { + next_ring_head = (dev->ring_head+1) % ring_buffer_size; + if (next_ring_head != dev->ring_tail) { + actual_buffer = (size_t *)(dev->ring_buffer + dev->ring_head * (sizeof(size_t)+dev->interrupt_in_endpoint_size)); + /* actual_buffer gets urb->actual_length + interrupt_in_buffer */ + *actual_buffer = urb->actual_length; + memcpy(actual_buffer+1, dev->interrupt_in_buffer, urb->actual_length); + dev->ring_head = next_ring_head; + dev_dbg(&dev->intf->dev, "%s: received %d bytes\n", + __func__, urb->actual_length); + } else { + dev_warn(&dev->intf->dev, + "Ring buffer overflow, %d bytes dropped\n", + urb->actual_length); + dev->buffer_overflow = 1; + } + } + +resubmit: + /* resubmit if we're still running */ + if (dev->interrupt_in_running && !dev->buffer_overflow) { + retval = usb_submit_urb(dev->interrupt_in_urb, GFP_ATOMIC); + if (retval) { + dev_err(&dev->intf->dev, + "usb_submit_urb failed (%d)\n", retval); + dev->buffer_overflow = 1; + } + } + spin_unlock_irqrestore(&dev->rbsl, flags); +exit: + dev->interrupt_in_done = 1; + wake_up_interruptible(&dev->read_wait); +} + +/* + * ld_usb_interrupt_out_callback + */ +static void ld_usb_interrupt_out_callback(struct urb *urb) +{ + struct ld_usb *dev = urb->context; + int status = urb->status; + + /* sync/async unlink faults aren't errors */ + if (status && !(status == -ENOENT || + status == -ECONNRESET || + status == -ESHUTDOWN)) + dev_dbg(&dev->intf->dev, + "%s - nonzero write interrupt status received: %d\n", + __func__, status); + + dev->interrupt_out_busy = 0; + wake_up_interruptible(&dev->write_wait); +} + +/* + * ld_usb_open + */ +static int ld_usb_open(struct inode *inode, struct file *file) +{ + struct ld_usb *dev; + int subminor; + int retval; + struct usb_interface *interface; + + stream_open(inode, file); + subminor = iminor(inode); + + interface = usb_find_interface(&ld_usb_driver, subminor); + + if (!interface) { + printk(KERN_ERR "%s - error, can't find device for minor %d\n", + __func__, subminor); + return -ENODEV; + } + + dev = usb_get_intfdata(interface); + + if (!dev) + return -ENODEV; + + /* lock this device */ + if (mutex_lock_interruptible(&dev->mutex)) + return -ERESTARTSYS; + + /* allow opening only once */ + if (dev->open_count) { + retval = -EBUSY; + goto unlock_exit; + } + dev->open_count = 1; + + /* initialize in direction */ + dev->ring_head = 0; + dev->ring_tail = 0; + dev->buffer_overflow = 0; + usb_fill_int_urb(dev->interrupt_in_urb, + interface_to_usbdev(interface), + usb_rcvintpipe(interface_to_usbdev(interface), + dev->interrupt_in_endpoint->bEndpointAddress), + dev->interrupt_in_buffer, + dev->interrupt_in_endpoint_size, + ld_usb_interrupt_in_callback, + dev, + dev->interrupt_in_interval); + + dev->interrupt_in_running = 1; + dev->interrupt_in_done = 0; + + retval = usb_submit_urb(dev->interrupt_in_urb, GFP_KERNEL); + if (retval) { + dev_err(&interface->dev, "Couldn't submit interrupt_in_urb %d\n", retval); + dev->interrupt_in_running = 0; + dev->open_count = 0; + goto unlock_exit; + } + + /* save device in the file's private structure */ + file->private_data = dev; + +unlock_exit: + mutex_unlock(&dev->mutex); + + return retval; +} + +/* + * ld_usb_release + */ +static int ld_usb_release(struct inode *inode, struct file *file) +{ + struct ld_usb *dev; + int retval = 0; + + dev = file->private_data; + + if (dev == NULL) { + retval = -ENODEV; + goto exit; + } + + mutex_lock(&dev->mutex); + + if (dev->open_count != 1) { + retval = -ENODEV; + goto unlock_exit; + } + if (dev->disconnected) { + /* the device was unplugged before the file was released */ + mutex_unlock(&dev->mutex); + /* unlock here as ld_usb_delete frees dev */ + ld_usb_delete(dev); + goto exit; + } + + /* wait until write transfer is finished */ + if (dev->interrupt_out_busy) + wait_event_interruptible_timeout(dev->write_wait, !dev->interrupt_out_busy, 2 * HZ); + ld_usb_abort_transfers(dev); + dev->open_count = 0; + +unlock_exit: + mutex_unlock(&dev->mutex); + +exit: + return retval; +} + +/* + * ld_usb_poll + */ +static __poll_t ld_usb_poll(struct file *file, poll_table *wait) +{ + struct ld_usb *dev; + __poll_t mask = 0; + + dev = file->private_data; + + if (dev->disconnected) + return EPOLLERR | EPOLLHUP; + + poll_wait(file, &dev->read_wait, wait); + poll_wait(file, &dev->write_wait, wait); + + if (dev->ring_head != dev->ring_tail) + mask |= EPOLLIN | EPOLLRDNORM; + if (!dev->interrupt_out_busy) + mask |= EPOLLOUT | EPOLLWRNORM; + + return mask; +} + +/* + * ld_usb_read + */ +static ssize_t ld_usb_read(struct file *file, char __user *buffer, size_t count, + loff_t *ppos) +{ + struct ld_usb *dev; + size_t *actual_buffer; + size_t bytes_to_read; + int retval = 0; + int rv; + + dev = file->private_data; + + /* verify that we actually have some data to read */ + if (count == 0) + goto exit; + + /* lock this object */ + if (mutex_lock_interruptible(&dev->mutex)) { + retval = -ERESTARTSYS; + goto exit; + } + + /* verify that the device wasn't unplugged */ + if (dev->disconnected) { + retval = -ENODEV; + printk(KERN_ERR "ldusb: No device or device unplugged %d\n", retval); + goto unlock_exit; + } + + /* wait for data */ + spin_lock_irq(&dev->rbsl); + while (dev->ring_head == dev->ring_tail) { + dev->interrupt_in_done = 0; + spin_unlock_irq(&dev->rbsl); + if (file->f_flags & O_NONBLOCK) { + retval = -EAGAIN; + goto unlock_exit; + } + retval = wait_event_interruptible(dev->read_wait, dev->interrupt_in_done); + if (retval < 0) + goto unlock_exit; + + spin_lock_irq(&dev->rbsl); + } + spin_unlock_irq(&dev->rbsl); + + /* actual_buffer contains actual_length + interrupt_in_buffer */ + actual_buffer = (size_t *)(dev->ring_buffer + dev->ring_tail * (sizeof(size_t)+dev->interrupt_in_endpoint_size)); + if (*actual_buffer > dev->interrupt_in_endpoint_size) { + retval = -EIO; + goto unlock_exit; + } + bytes_to_read = min(count, *actual_buffer); + if (bytes_to_read < *actual_buffer) + dev_warn(&dev->intf->dev, "Read buffer overflow, %zu bytes dropped\n", + *actual_buffer-bytes_to_read); + + /* copy one interrupt_in_buffer from ring_buffer into userspace */ + if (copy_to_user(buffer, actual_buffer+1, bytes_to_read)) { + retval = -EFAULT; + goto unlock_exit; + } + retval = bytes_to_read; + + spin_lock_irq(&dev->rbsl); + dev->ring_tail = (dev->ring_tail + 1) % ring_buffer_size; + + if (dev->buffer_overflow) { + dev->buffer_overflow = 0; + spin_unlock_irq(&dev->rbsl); + rv = usb_submit_urb(dev->interrupt_in_urb, GFP_KERNEL); + if (rv < 0) + dev->buffer_overflow = 1; + } else { + spin_unlock_irq(&dev->rbsl); + } + +unlock_exit: + /* unlock the device */ + mutex_unlock(&dev->mutex); + +exit: + return retval; +} + +/* + * ld_usb_write + */ +static ssize_t ld_usb_write(struct file *file, const char __user *buffer, + size_t count, loff_t *ppos) +{ + struct ld_usb *dev; + size_t bytes_to_write; + int retval = 0; + + dev = file->private_data; + + /* verify that we actually have some data to write */ + if (count == 0) + goto exit; + + /* lock this object */ + if (mutex_lock_interruptible(&dev->mutex)) { + retval = -ERESTARTSYS; + goto exit; + } + + /* verify that the device wasn't unplugged */ + if (dev->disconnected) { + retval = -ENODEV; + printk(KERN_ERR "ldusb: No device or device unplugged %d\n", retval); + goto unlock_exit; + } + + /* wait until previous transfer is finished */ + if (dev->interrupt_out_busy) { + if (file->f_flags & O_NONBLOCK) { + retval = -EAGAIN; + goto unlock_exit; + } + retval = wait_event_interruptible(dev->write_wait, !dev->interrupt_out_busy); + if (retval < 0) { + goto unlock_exit; + } + } + + /* write the data into interrupt_out_buffer from userspace */ + bytes_to_write = min(count, write_buffer_size*dev->interrupt_out_endpoint_size); + if (bytes_to_write < count) + dev_warn(&dev->intf->dev, "Write buffer overflow, %zu bytes dropped\n", + count - bytes_to_write); + dev_dbg(&dev->intf->dev, "%s: count = %zu, bytes_to_write = %zu\n", + __func__, count, bytes_to_write); + + if (copy_from_user(dev->interrupt_out_buffer, buffer, bytes_to_write)) { + retval = -EFAULT; + goto unlock_exit; + } + + if (dev->interrupt_out_endpoint == NULL) { + /* try HID_REQ_SET_REPORT=9 on control_endpoint instead of interrupt_out_endpoint */ + retval = usb_control_msg(interface_to_usbdev(dev->intf), + usb_sndctrlpipe(interface_to_usbdev(dev->intf), 0), + 9, + USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT, + 1 << 8, 0, + dev->interrupt_out_buffer, + bytes_to_write, + USB_CTRL_SET_TIMEOUT); + if (retval < 0) + dev_err(&dev->intf->dev, + "Couldn't submit HID_REQ_SET_REPORT %d\n", + retval); + goto unlock_exit; + } + + /* send off the urb */ + usb_fill_int_urb(dev->interrupt_out_urb, + interface_to_usbdev(dev->intf), + usb_sndintpipe(interface_to_usbdev(dev->intf), + dev->interrupt_out_endpoint->bEndpointAddress), + dev->interrupt_out_buffer, + bytes_to_write, + ld_usb_interrupt_out_callback, + dev, + dev->interrupt_out_interval); + + dev->interrupt_out_busy = 1; + wmb(); + + retval = usb_submit_urb(dev->interrupt_out_urb, GFP_KERNEL); + if (retval) { + dev->interrupt_out_busy = 0; + dev_err(&dev->intf->dev, + "Couldn't submit interrupt_out_urb %d\n", retval); + goto unlock_exit; + } + retval = bytes_to_write; + +unlock_exit: + /* unlock the device */ + mutex_unlock(&dev->mutex); + +exit: + return retval; +} + +/* file operations needed when we register this driver */ +static const struct file_operations ld_usb_fops = { + .owner = THIS_MODULE, + .read = ld_usb_read, + .write = ld_usb_write, + .open = ld_usb_open, + .release = ld_usb_release, + .poll = ld_usb_poll, + .llseek = no_llseek, +}; + +/* + * usb class driver info in order to get a minor number from the usb core, + * and to have the device registered with the driver core + */ +static struct usb_class_driver ld_usb_class = { + .name = "ldusb%d", + .fops = &ld_usb_fops, + .minor_base = USB_LD_MINOR_BASE, +}; + +/* + * ld_usb_probe + * + * Called by the usb core when a new device is connected that it thinks + * this driver might be interested in. + */ +static int ld_usb_probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(intf); + struct ld_usb *dev = NULL; + struct usb_host_interface *iface_desc; + char *buffer; + int retval = -ENOMEM; + int res; + + /* allocate memory for our device state and initialize it */ + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + goto exit; + mutex_init(&dev->mutex); + spin_lock_init(&dev->rbsl); + dev->intf = intf; + init_waitqueue_head(&dev->read_wait); + init_waitqueue_head(&dev->write_wait); + + /* workaround for early firmware versions on fast computers */ + if ((le16_to_cpu(udev->descriptor.idVendor) == USB_VENDOR_ID_LD) && + ((le16_to_cpu(udev->descriptor.idProduct) == USB_DEVICE_ID_LD_CASSY) || + (le16_to_cpu(udev->descriptor.idProduct) == USB_DEVICE_ID_LD_COM3LAB)) && + (le16_to_cpu(udev->descriptor.bcdDevice) <= 0x103)) { + buffer = kmalloc(256, GFP_KERNEL); + if (!buffer) + goto error; + /* usb_string makes SETUP+STALL to leave always ControlReadLoop */ + usb_string(udev, 255, buffer, 256); + kfree(buffer); + } + + iface_desc = intf->cur_altsetting; + + res = usb_find_last_int_in_endpoint(iface_desc, + &dev->interrupt_in_endpoint); + if (res) { + dev_err(&intf->dev, "Interrupt in endpoint not found\n"); + retval = res; + goto error; + } + + res = usb_find_last_int_out_endpoint(iface_desc, + &dev->interrupt_out_endpoint); + if (res) + dev_warn(&intf->dev, "Interrupt out endpoint not found (using control endpoint instead)\n"); + + dev->interrupt_in_endpoint_size = usb_endpoint_maxp(dev->interrupt_in_endpoint); + dev->ring_buffer = kcalloc(ring_buffer_size, + sizeof(size_t) + dev->interrupt_in_endpoint_size, + GFP_KERNEL); + if (!dev->ring_buffer) + goto error; + dev->interrupt_in_buffer = kmalloc(dev->interrupt_in_endpoint_size, GFP_KERNEL); + if (!dev->interrupt_in_buffer) + goto error; + dev->interrupt_in_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!dev->interrupt_in_urb) + goto error; + dev->interrupt_out_endpoint_size = dev->interrupt_out_endpoint ? usb_endpoint_maxp(dev->interrupt_out_endpoint) : + udev->descriptor.bMaxPacketSize0; + dev->interrupt_out_buffer = + kmalloc_array(write_buffer_size, + dev->interrupt_out_endpoint_size, GFP_KERNEL); + if (!dev->interrupt_out_buffer) + goto error; + dev->interrupt_out_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!dev->interrupt_out_urb) + goto error; + dev->interrupt_in_interval = max_t(int, min_interrupt_in_interval, + dev->interrupt_in_endpoint->bInterval); + if (dev->interrupt_out_endpoint) + dev->interrupt_out_interval = max_t(int, min_interrupt_out_interval, + dev->interrupt_out_endpoint->bInterval); + + /* we can register the device now, as it is ready */ + usb_set_intfdata(intf, dev); + + retval = usb_register_dev(intf, &ld_usb_class); + if (retval) { + /* something prevented us from registering this driver */ + dev_err(&intf->dev, "Not able to get a minor for this device.\n"); + usb_set_intfdata(intf, NULL); + goto error; + } + + /* let the user know what node this device is now attached to */ + dev_info(&intf->dev, "LD USB Device #%d now attached to major %d minor %d\n", + (intf->minor - USB_LD_MINOR_BASE), USB_MAJOR, intf->minor); + +exit: + return retval; + +error: + ld_usb_delete(dev); + + return retval; +} + +/* + * ld_usb_disconnect + * + * Called by the usb core when the device is removed from the system. + */ +static void ld_usb_disconnect(struct usb_interface *intf) +{ + struct ld_usb *dev; + int minor; + + dev = usb_get_intfdata(intf); + usb_set_intfdata(intf, NULL); + + minor = intf->minor; + + /* give back our minor */ + usb_deregister_dev(intf, &ld_usb_class); + + usb_poison_urb(dev->interrupt_in_urb); + usb_poison_urb(dev->interrupt_out_urb); + + mutex_lock(&dev->mutex); + + /* if the device is not opened, then we clean up right now */ + if (!dev->open_count) { + mutex_unlock(&dev->mutex); + ld_usb_delete(dev); + } else { + dev->disconnected = 1; + /* wake up pollers */ + wake_up_interruptible_all(&dev->read_wait); + wake_up_interruptible_all(&dev->write_wait); + mutex_unlock(&dev->mutex); + } + + dev_info(&intf->dev, "LD USB Device #%d now disconnected\n", + (minor - USB_LD_MINOR_BASE)); +} + +/* usb specific object needed to register this driver with the usb subsystem */ +static struct usb_driver ld_usb_driver = { + .name = "ldusb", + .probe = ld_usb_probe, + .disconnect = ld_usb_disconnect, + .id_table = ld_usb_table, +}; + +module_usb_driver(ld_usb_driver); + diff --git a/drivers/usb/misc/legousbtower.c b/drivers/usb/misc/legousbtower.c new file mode 100644 index 0000000000..379cf01a6e --- /dev/null +++ b/drivers/usb/misc/legousbtower.c @@ -0,0 +1,879 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * LEGO USB Tower driver + * + * Copyright (C) 2003 David Glance <davidgsf@sourceforge.net> + * 2001-2004 Juergen Stuber <starblue@users.sourceforge.net> + * + * derived from USB Skeleton driver - 0.5 + * Copyright (C) 2001 Greg Kroah-Hartman (greg@kroah.com) + * + * History: + * + * 2001-10-13 - 0.1 js + * - first version + * 2001-11-03 - 0.2 js + * - simplified buffering, one-shot URBs for writing + * 2001-11-10 - 0.3 js + * - removed IOCTL (setting power/mode is more complicated, postponed) + * 2001-11-28 - 0.4 js + * - added vendor commands for mode of operation and power level in open + * 2001-12-04 - 0.5 js + * - set IR mode by default (by oversight 0.4 set VLL mode) + * 2002-01-11 - 0.5? pcchan + * - make read buffer reusable and work around bytes_to_write issue between + * uhci and legusbtower + * 2002-09-23 - 0.52 david (david@csse.uwa.edu.au) + * - imported into lejos project + * - changed wake_up to wake_up_interruptible + * - changed to use lego0 rather than tower0 + * - changed dbg() to use __func__ rather than deprecated __func__ + * 2003-01-12 - 0.53 david (david@csse.uwa.edu.au) + * - changed read and write to write everything or + * timeout (from a patch by Chris Riesen and Brett Thaeler driver) + * - added ioctl functionality to set timeouts + * 2003-07-18 - 0.54 davidgsf (david@csse.uwa.edu.au) + * - initial import into LegoUSB project + * - merge of existing LegoUSB.c driver + * 2003-07-18 - 0.56 davidgsf (david@csse.uwa.edu.au) + * - port to 2.6 style driver + * 2004-02-29 - 0.6 Juergen Stuber <starblue@users.sourceforge.net> + * - fix locking + * - unlink read URBs which are no longer needed + * - allow increased buffer size, eliminates need for timeout on write + * - have read URB running continuously + * - added poll + * - forbid seeking + * - added nonblocking I/O + * - changed back __func__ to __func__ + * - read and log tower firmware version + * - reset tower on probe, avoids failure of first write + * 2004-03-09 - 0.7 Juergen Stuber <starblue@users.sourceforge.net> + * - timeout read now only after inactivity, shorten default accordingly + * 2004-03-11 - 0.8 Juergen Stuber <starblue@users.sourceforge.net> + * - log major, minor instead of possibly confusing device filename + * - whitespace cleanup + * 2004-03-12 - 0.9 Juergen Stuber <starblue@users.sourceforge.net> + * - normalize whitespace in debug messages + * - take care about endianness in control message responses + * 2004-03-13 - 0.91 Juergen Stuber <starblue@users.sourceforge.net> + * - make default intervals longer to accommodate current EHCI driver + * 2004-03-19 - 0.92 Juergen Stuber <starblue@users.sourceforge.net> + * - replaced atomic_t by memory barriers + * 2004-04-21 - 0.93 Juergen Stuber <starblue@users.sourceforge.net> + * - wait for completion of write urb in release (needed for remotecontrol) + * - corrected poll for write direction (missing negation) + * 2004-04-22 - 0.94 Juergen Stuber <starblue@users.sourceforge.net> + * - make device locking interruptible + * 2004-04-30 - 0.95 Juergen Stuber <starblue@users.sourceforge.net> + * - check for valid udev on resubmitting and unlinking urbs + * 2004-08-03 - 0.96 Juergen Stuber <starblue@users.sourceforge.net> + * - move reset into open to clean out spurious data + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/completion.h> +#include <linux/mutex.h> +#include <linux/uaccess.h> +#include <linux/usb.h> +#include <linux/poll.h> + + +#define DRIVER_AUTHOR "Juergen Stuber <starblue@sourceforge.net>" +#define DRIVER_DESC "LEGO USB Tower Driver" + + +/* The defaults are chosen to work with the latest versions of leJOS and NQC. + */ + +/* Some legacy software likes to receive packets in one piece. + * In this case read_buffer_size should exceed the maximal packet length + * (417 for datalog uploads), and packet_timeout should be set. + */ +static int read_buffer_size = 480; +module_param(read_buffer_size, int, 0); +MODULE_PARM_DESC(read_buffer_size, "Read buffer size"); + +/* Some legacy software likes to send packets in one piece. + * In this case write_buffer_size should exceed the maximal packet length + * (417 for firmware and program downloads). + * A problem with long writes is that the following read may time out + * if the software is not prepared to wait long enough. + */ +static int write_buffer_size = 480; +module_param(write_buffer_size, int, 0); +MODULE_PARM_DESC(write_buffer_size, "Write buffer size"); + +/* Some legacy software expects reads to contain whole LASM packets. + * To achieve this, characters which arrive before a packet timeout + * occurs will be returned in a single read operation. + * A problem with long reads is that the software may time out + * if it is not prepared to wait long enough. + * The packet timeout should be greater than the time between the + * reception of subsequent characters, which should arrive about + * every 5ms for the standard 2400 baud. + * Set it to 0 to disable. + */ +static int packet_timeout = 50; +module_param(packet_timeout, int, 0); +MODULE_PARM_DESC(packet_timeout, "Packet timeout in ms"); + +/* Some legacy software expects blocking reads to time out. + * Timeout occurs after the specified time of read and write inactivity. + * Set it to 0 to disable. + */ +static int read_timeout = 200; +module_param(read_timeout, int, 0); +MODULE_PARM_DESC(read_timeout, "Read timeout in ms"); + +/* As of kernel version 2.6.4 ehci-hcd uses an + * "only one interrupt transfer per frame" shortcut + * to simplify the scheduling of periodic transfers. + * This conflicts with our standard 1ms intervals for in and out URBs. + * We use default intervals of 2ms for in and 8ms for out transfers, + * which is fast enough for 2400 baud and allows a small additional load. + * Increase the interval to allow more devices that do interrupt transfers, + * or set to 0 to use the standard interval from the endpoint descriptors. + */ +static int interrupt_in_interval = 2; +module_param(interrupt_in_interval, int, 0); +MODULE_PARM_DESC(interrupt_in_interval, "Interrupt in interval in ms"); + +static int interrupt_out_interval = 8; +module_param(interrupt_out_interval, int, 0); +MODULE_PARM_DESC(interrupt_out_interval, "Interrupt out interval in ms"); + +/* Define these values to match your device */ +#define LEGO_USB_TOWER_VENDOR_ID 0x0694 +#define LEGO_USB_TOWER_PRODUCT_ID 0x0001 + +/* Vendor requests */ +#define LEGO_USB_TOWER_REQUEST_RESET 0x04 +#define LEGO_USB_TOWER_REQUEST_GET_VERSION 0xFD + +struct tower_reset_reply { + __le16 size; + __u8 err_code; + __u8 spare; +}; + +struct tower_get_version_reply { + __le16 size; + __u8 err_code; + __u8 spare; + __u8 major; + __u8 minor; + __le16 build_no; +}; + + +/* table of devices that work with this driver */ +static const struct usb_device_id tower_table[] = { + { USB_DEVICE(LEGO_USB_TOWER_VENDOR_ID, LEGO_USB_TOWER_PRODUCT_ID) }, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE(usb, tower_table); + +#define LEGO_USB_TOWER_MINOR_BASE 160 + + +/* Structure to hold all of our device specific stuff */ +struct lego_usb_tower { + struct mutex lock; /* locks this structure */ + struct usb_device *udev; /* save off the usb device pointer */ + unsigned char minor; /* the starting minor number for this device */ + + int open_count; /* number of times this port has been opened */ + unsigned long disconnected:1; + + char *read_buffer; + size_t read_buffer_length; /* this much came in */ + size_t read_packet_length; /* this much will be returned on read */ + spinlock_t read_buffer_lock; + int packet_timeout_jiffies; + unsigned long read_last_arrival; + + wait_queue_head_t read_wait; + wait_queue_head_t write_wait; + + char *interrupt_in_buffer; + struct usb_endpoint_descriptor *interrupt_in_endpoint; + struct urb *interrupt_in_urb; + int interrupt_in_interval; + int interrupt_in_done; + + char *interrupt_out_buffer; + struct usb_endpoint_descriptor *interrupt_out_endpoint; + struct urb *interrupt_out_urb; + int interrupt_out_interval; + int interrupt_out_busy; + +}; + + +/* local function prototypes */ +static ssize_t tower_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos); +static ssize_t tower_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos); +static inline void tower_delete(struct lego_usb_tower *dev); +static int tower_open(struct inode *inode, struct file *file); +static int tower_release(struct inode *inode, struct file *file); +static __poll_t tower_poll(struct file *file, poll_table *wait); +static loff_t tower_llseek(struct file *file, loff_t off, int whence); + +static void tower_check_for_read_packet(struct lego_usb_tower *dev); +static void tower_interrupt_in_callback(struct urb *urb); +static void tower_interrupt_out_callback(struct urb *urb); + +static int tower_probe(struct usb_interface *interface, const struct usb_device_id *id); +static void tower_disconnect(struct usb_interface *interface); + + +/* file operations needed when we register this driver */ +static const struct file_operations tower_fops = { + .owner = THIS_MODULE, + .read = tower_read, + .write = tower_write, + .open = tower_open, + .release = tower_release, + .poll = tower_poll, + .llseek = tower_llseek, +}; + +static char *legousbtower_devnode(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 lego_usb_tower *dev) +{ + /* free data structures */ + usb_free_urb(dev->interrupt_in_urb); + usb_free_urb(dev->interrupt_out_urb); + kfree(dev->read_buffer); + kfree(dev->interrupt_in_buffer); + kfree(dev->interrupt_out_buffer); + usb_put_dev(dev->udev); + kfree(dev); +} + + +/* + * tower_open + */ +static int tower_open(struct inode *inode, struct file *file) +{ + struct lego_usb_tower *dev = NULL; + int subminor; + int retval = 0; + struct usb_interface *interface; + struct tower_reset_reply reset_reply; + int result; + + nonseekable_open(inode, file); + subminor = iminor(inode); + + interface = usb_find_interface(&tower_driver, subminor); + if (!interface) { + pr_err("error, can't find device for minor %d\n", subminor); + retval = -ENODEV; + goto exit; + } + + dev = usb_get_intfdata(interface); + if (!dev) { + retval = -ENODEV; + goto exit; + } + + /* lock this device */ + if (mutex_lock_interruptible(&dev->lock)) { + retval = -ERESTARTSYS; + goto exit; + } + + + /* allow opening only once */ + if (dev->open_count) { + retval = -EBUSY; + goto unlock_exit; + } + + /* reset the tower */ + result = usb_control_msg_recv(dev->udev, 0, + LEGO_USB_TOWER_REQUEST_RESET, + USB_TYPE_VENDOR | USB_DIR_IN | USB_RECIP_DEVICE, + 0, 0, + &reset_reply, sizeof(reset_reply), 1000, + GFP_KERNEL); + if (result < 0) { + dev_err(&dev->udev->dev, + "LEGO USB Tower reset control request failed\n"); + retval = result; + goto unlock_exit; + } + + /* initialize in direction */ + dev->read_buffer_length = 0; + dev->read_packet_length = 0; + usb_fill_int_urb(dev->interrupt_in_urb, + dev->udev, + usb_rcvintpipe(dev->udev, dev->interrupt_in_endpoint->bEndpointAddress), + dev->interrupt_in_buffer, + usb_endpoint_maxp(dev->interrupt_in_endpoint), + tower_interrupt_in_callback, + dev, + dev->interrupt_in_interval); + + dev->interrupt_in_done = 0; + mb(); + + retval = usb_submit_urb(dev->interrupt_in_urb, GFP_KERNEL); + if (retval) { + dev_err(&dev->udev->dev, + "Couldn't submit interrupt_in_urb %d\n", retval); + goto unlock_exit; + } + + /* save device in the file's private structure */ + file->private_data = dev; + + dev->open_count = 1; + +unlock_exit: + mutex_unlock(&dev->lock); + +exit: + return retval; +} + +/* + * tower_release + */ +static int tower_release(struct inode *inode, struct file *file) +{ + struct lego_usb_tower *dev; + int retval = 0; + + dev = file->private_data; + if (dev == NULL) { + retval = -ENODEV; + goto exit; + } + + mutex_lock(&dev->lock); + + if (dev->disconnected) { + /* the device was unplugged before the file was released */ + + /* unlock here as tower_delete frees dev */ + mutex_unlock(&dev->lock); + tower_delete(dev); + goto exit; + } + + /* wait until write transfer is finished */ + if (dev->interrupt_out_busy) { + wait_event_interruptible_timeout(dev->write_wait, !dev->interrupt_out_busy, + 2 * HZ); + } + + /* shutdown transfers */ + usb_kill_urb(dev->interrupt_in_urb); + usb_kill_urb(dev->interrupt_out_urb); + + dev->open_count = 0; + + mutex_unlock(&dev->lock); +exit: + return retval; +} + +/* + * tower_check_for_read_packet + * + * To get correct semantics for signals and non-blocking I/O + * with packetizing we pretend not to see any data in the read buffer + * until it has been there unchanged for at least + * dev->packet_timeout_jiffies, or until the buffer is full. + */ +static void tower_check_for_read_packet(struct lego_usb_tower *dev) +{ + spin_lock_irq(&dev->read_buffer_lock); + if (!packet_timeout + || time_after(jiffies, dev->read_last_arrival + dev->packet_timeout_jiffies) + || dev->read_buffer_length == read_buffer_size) { + dev->read_packet_length = dev->read_buffer_length; + } + dev->interrupt_in_done = 0; + spin_unlock_irq(&dev->read_buffer_lock); +} + + +/* + * tower_poll + */ +static __poll_t tower_poll(struct file *file, poll_table *wait) +{ + struct lego_usb_tower *dev; + __poll_t mask = 0; + + dev = file->private_data; + + if (dev->disconnected) + return EPOLLERR | EPOLLHUP; + + poll_wait(file, &dev->read_wait, wait); + poll_wait(file, &dev->write_wait, wait); + + tower_check_for_read_packet(dev); + if (dev->read_packet_length > 0) + mask |= EPOLLIN | EPOLLRDNORM; + if (!dev->interrupt_out_busy) + mask |= EPOLLOUT | EPOLLWRNORM; + + return mask; +} + + +/* + * tower_llseek + */ +static loff_t tower_llseek(struct file *file, loff_t off, int whence) +{ + return -ESPIPE; /* unseekable */ +} + + +/* + * tower_read + */ +static ssize_t tower_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos) +{ + struct lego_usb_tower *dev; + size_t bytes_to_read; + int i; + int retval = 0; + unsigned long timeout = 0; + + dev = file->private_data; + + /* lock this object */ + if (mutex_lock_interruptible(&dev->lock)) { + retval = -ERESTARTSYS; + goto exit; + } + + /* verify that the device wasn't unplugged */ + if (dev->disconnected) { + retval = -ENODEV; + goto unlock_exit; + } + + /* verify that we actually have some data to read */ + if (count == 0) { + dev_dbg(&dev->udev->dev, "read request of 0 bytes\n"); + goto unlock_exit; + } + + if (read_timeout) + timeout = jiffies + msecs_to_jiffies(read_timeout); + + /* wait for data */ + tower_check_for_read_packet(dev); + while (dev->read_packet_length == 0) { + if (file->f_flags & O_NONBLOCK) { + retval = -EAGAIN; + goto unlock_exit; + } + retval = wait_event_interruptible_timeout(dev->read_wait, dev->interrupt_in_done, dev->packet_timeout_jiffies); + if (retval < 0) + goto unlock_exit; + + /* reset read timeout during read or write activity */ + if (read_timeout + && (dev->read_buffer_length || dev->interrupt_out_busy)) { + timeout = jiffies + msecs_to_jiffies(read_timeout); + } + /* check for read timeout */ + if (read_timeout && time_after(jiffies, timeout)) { + retval = -ETIMEDOUT; + goto unlock_exit; + } + tower_check_for_read_packet(dev); + } + + /* copy the data from read_buffer into userspace */ + bytes_to_read = min(count, dev->read_packet_length); + + if (copy_to_user(buffer, dev->read_buffer, bytes_to_read)) { + retval = -EFAULT; + goto unlock_exit; + } + + spin_lock_irq(&dev->read_buffer_lock); + dev->read_buffer_length -= bytes_to_read; + dev->read_packet_length -= bytes_to_read; + for (i = 0; i < dev->read_buffer_length; i++) + dev->read_buffer[i] = dev->read_buffer[i+bytes_to_read]; + spin_unlock_irq(&dev->read_buffer_lock); + + retval = bytes_to_read; + +unlock_exit: + /* unlock the device */ + mutex_unlock(&dev->lock); + +exit: + return retval; +} + + +/* + * tower_write + */ +static ssize_t tower_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) +{ + struct lego_usb_tower *dev; + size_t bytes_to_write; + int retval = 0; + + dev = file->private_data; + + /* lock this object */ + if (mutex_lock_interruptible(&dev->lock)) { + retval = -ERESTARTSYS; + goto exit; + } + + /* verify that the device wasn't unplugged */ + if (dev->disconnected) { + retval = -ENODEV; + goto unlock_exit; + } + + /* verify that we actually have some data to write */ + if (count == 0) { + dev_dbg(&dev->udev->dev, "write request of 0 bytes\n"); + goto unlock_exit; + } + + /* wait until previous transfer is finished */ + while (dev->interrupt_out_busy) { + if (file->f_flags & O_NONBLOCK) { + retval = -EAGAIN; + goto unlock_exit; + } + retval = wait_event_interruptible(dev->write_wait, + !dev->interrupt_out_busy); + if (retval) + goto unlock_exit; + } + + /* write the data into interrupt_out_buffer from userspace */ + bytes_to_write = min_t(int, count, write_buffer_size); + dev_dbg(&dev->udev->dev, "%s: count = %zd, bytes_to_write = %zd\n", + __func__, count, bytes_to_write); + + if (copy_from_user(dev->interrupt_out_buffer, buffer, bytes_to_write)) { + retval = -EFAULT; + goto unlock_exit; + } + + /* send off the urb */ + usb_fill_int_urb(dev->interrupt_out_urb, + dev->udev, + usb_sndintpipe(dev->udev, dev->interrupt_out_endpoint->bEndpointAddress), + dev->interrupt_out_buffer, + bytes_to_write, + tower_interrupt_out_callback, + dev, + dev->interrupt_out_interval); + + dev->interrupt_out_busy = 1; + wmb(); + + retval = usb_submit_urb(dev->interrupt_out_urb, GFP_KERNEL); + if (retval) { + dev->interrupt_out_busy = 0; + dev_err(&dev->udev->dev, + "Couldn't submit interrupt_out_urb %d\n", retval); + goto unlock_exit; + } + retval = bytes_to_write; + +unlock_exit: + /* unlock the device */ + mutex_unlock(&dev->lock); + +exit: + return retval; +} + + +/* + * tower_interrupt_in_callback + */ +static void tower_interrupt_in_callback(struct urb *urb) +{ + struct lego_usb_tower *dev = urb->context; + int status = urb->status; + int retval; + unsigned long flags; + + lego_usb_tower_debug_data(&dev->udev->dev, __func__, + urb->actual_length, urb->transfer_buffer); + + if (status) { + if (status == -ENOENT || + status == -ECONNRESET || + status == -ESHUTDOWN) { + goto exit; + } else { + dev_dbg(&dev->udev->dev, + "%s: nonzero status received: %d\n", __func__, + status); + goto resubmit; /* maybe we can recover */ + } + } + + if (urb->actual_length > 0) { + spin_lock_irqsave(&dev->read_buffer_lock, flags); + if (dev->read_buffer_length + urb->actual_length < read_buffer_size) { + memcpy(dev->read_buffer + dev->read_buffer_length, + dev->interrupt_in_buffer, + urb->actual_length); + dev->read_buffer_length += urb->actual_length; + dev->read_last_arrival = jiffies; + dev_dbg(&dev->udev->dev, "%s: received %d bytes\n", + __func__, urb->actual_length); + } else { + pr_warn("read_buffer overflow, %d bytes dropped\n", + urb->actual_length); + } + spin_unlock_irqrestore(&dev->read_buffer_lock, flags); + } + +resubmit: + retval = usb_submit_urb(dev->interrupt_in_urb, GFP_ATOMIC); + if (retval) { + dev_err(&dev->udev->dev, "%s: usb_submit_urb failed (%d)\n", + __func__, retval); + } +exit: + dev->interrupt_in_done = 1; + wake_up_interruptible(&dev->read_wait); +} + + +/* + * tower_interrupt_out_callback + */ +static void tower_interrupt_out_callback(struct urb *urb) +{ + struct lego_usb_tower *dev = urb->context; + int status = urb->status; + + lego_usb_tower_debug_data(&dev->udev->dev, __func__, + urb->actual_length, urb->transfer_buffer); + + /* sync/async unlink faults aren't errors */ + if (status && !(status == -ENOENT || + status == -ECONNRESET || + status == -ESHUTDOWN)) { + dev_dbg(&dev->udev->dev, + "%s: nonzero write bulk status received: %d\n", __func__, + status); + } + + dev->interrupt_out_busy = 0; + wake_up_interruptible(&dev->write_wait); +} + + +/* + * tower_probe + * + * Called by the usb core when a new device is connected that it thinks + * this driver might be interested in. + */ +static int tower_probe(struct usb_interface *interface, const struct usb_device_id *id) +{ + struct device *idev = &interface->dev; + struct usb_device *udev = interface_to_usbdev(interface); + struct lego_usb_tower *dev; + struct tower_get_version_reply get_version_reply; + int retval = -ENOMEM; + int result; + + /* allocate memory for our device state and initialize it */ + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + goto exit; + + mutex_init(&dev->lock); + dev->udev = usb_get_dev(udev); + spin_lock_init(&dev->read_buffer_lock); + dev->packet_timeout_jiffies = msecs_to_jiffies(packet_timeout); + dev->read_last_arrival = jiffies; + init_waitqueue_head(&dev->read_wait); + init_waitqueue_head(&dev->write_wait); + + result = usb_find_common_endpoints_reverse(interface->cur_altsetting, + NULL, NULL, + &dev->interrupt_in_endpoint, + &dev->interrupt_out_endpoint); + if (result) { + dev_err(idev, "interrupt endpoints not found\n"); + retval = result; + goto error; + } + + dev->read_buffer = kmalloc(read_buffer_size, GFP_KERNEL); + if (!dev->read_buffer) + goto error; + dev->interrupt_in_buffer = kmalloc(usb_endpoint_maxp(dev->interrupt_in_endpoint), GFP_KERNEL); + if (!dev->interrupt_in_buffer) + goto error; + dev->interrupt_in_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!dev->interrupt_in_urb) + goto error; + dev->interrupt_out_buffer = kmalloc(write_buffer_size, GFP_KERNEL); + if (!dev->interrupt_out_buffer) + goto error; + dev->interrupt_out_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!dev->interrupt_out_urb) + goto error; + dev->interrupt_in_interval = interrupt_in_interval ? interrupt_in_interval : dev->interrupt_in_endpoint->bInterval; + dev->interrupt_out_interval = interrupt_out_interval ? interrupt_out_interval : dev->interrupt_out_endpoint->bInterval; + + /* get the firmware version and log it */ + result = usb_control_msg_recv(udev, 0, + LEGO_USB_TOWER_REQUEST_GET_VERSION, + USB_TYPE_VENDOR | USB_DIR_IN | USB_RECIP_DEVICE, + 0, + 0, + &get_version_reply, + sizeof(get_version_reply), + 1000, GFP_KERNEL); + if (result) { + dev_err(idev, "get version request failed: %d\n", result); + retval = result; + goto error; + } + dev_info(&interface->dev, + "LEGO USB Tower firmware version is %d.%d build %d\n", + get_version_reply.major, + get_version_reply.minor, + le16_to_cpu(get_version_reply.build_no)); + + /* we can register the device now, as it is ready */ + usb_set_intfdata(interface, dev); + + retval = usb_register_dev(interface, &tower_class); + if (retval) { + /* something prevented us from registering this driver */ + dev_err(idev, "Not able to get a minor for this device.\n"); + goto error; + } + dev->minor = interface->minor; + + /* let the user know what node this device is now attached to */ + dev_info(&interface->dev, "LEGO USB Tower #%d now attached to major " + "%d minor %d\n", (dev->minor - LEGO_USB_TOWER_MINOR_BASE), + USB_MAJOR, dev->minor); + +exit: + return retval; + +error: + tower_delete(dev); + return retval; +} + + +/* + * tower_disconnect + * + * Called by the usb core when the device is removed from the system. + */ +static void tower_disconnect(struct usb_interface *interface) +{ + struct lego_usb_tower *dev; + int minor; + + dev = usb_get_intfdata(interface); + + minor = dev->minor; + + /* give back our minor and prevent further open() */ + usb_deregister_dev(interface, &tower_class); + + /* stop I/O */ + usb_poison_urb(dev->interrupt_in_urb); + usb_poison_urb(dev->interrupt_out_urb); + + mutex_lock(&dev->lock); + + /* if the device is not opened, then we clean up right now */ + if (!dev->open_count) { + mutex_unlock(&dev->lock); + tower_delete(dev); + } else { + dev->disconnected = 1; + /* wake up pollers */ + wake_up_interruptible_all(&dev->read_wait); + wake_up_interruptible_all(&dev->write_wait); + mutex_unlock(&dev->lock); + } + + dev_info(&interface->dev, "LEGO USB Tower #%d now disconnected\n", + (minor - LEGO_USB_TOWER_MINOR_BASE)); +} + +module_usb_driver(tower_driver); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/misc/lvstest.c b/drivers/usb/misc/lvstest.c new file mode 100644 index 0000000000..25ec5666a7 --- /dev/null +++ b/drivers/usb/misc/lvstest.c @@ -0,0 +1,476 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * drivers/usb/misc/lvstest.c + * + * Test pattern generation for Link Layer Validation System Tests + * + * Copyright (C) 2014 ST Microelectronics + * Pratyush Anand <pratyush.anand@gmail.com> + */ + +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/usb.h> +#include <linux/usb/ch11.h> +#include <linux/usb/hcd.h> +#include <linux/usb/phy.h> + +struct lvs_rh { + /* root hub interface */ + struct usb_interface *intf; + /* if lvs device connected */ + bool present; + /* port no at which lvs device is present */ + int portnum; + /* urb buffer */ + u8 buffer[8]; + /* class descriptor */ + struct usb_hub_descriptor descriptor; + /* urb for polling interrupt pipe */ + struct urb *urb; + /* LVH RH work */ + struct work_struct rh_work; + /* RH port status */ + struct usb_port_status port_status; +}; + +static struct usb_device *create_lvs_device(struct usb_interface *intf) +{ + struct usb_device *udev, *hdev; + struct usb_hcd *hcd; + struct lvs_rh *lvs = usb_get_intfdata(intf); + + if (!lvs->present) { + dev_err(&intf->dev, "No LVS device is present\n"); + return NULL; + } + + hdev = interface_to_usbdev(intf); + hcd = bus_to_hcd(hdev->bus); + + udev = usb_alloc_dev(hdev, hdev->bus, lvs->portnum); + if (!udev) { + dev_err(&intf->dev, "Could not allocate lvs udev\n"); + return NULL; + } + udev->speed = USB_SPEED_SUPER; + udev->ep0.desc.wMaxPacketSize = cpu_to_le16(512); + usb_set_device_state(udev, USB_STATE_DEFAULT); + + if (hcd->driver->enable_device) { + if (hcd->driver->enable_device(hcd, udev) < 0) { + dev_err(&intf->dev, "Failed to enable\n"); + usb_put_dev(udev); + return NULL; + } + } + + return udev; +} + +static void destroy_lvs_device(struct usb_device *udev) +{ + struct usb_device *hdev = udev->parent; + struct usb_hcd *hcd = bus_to_hcd(hdev->bus); + + if (hcd->driver->free_dev) + hcd->driver->free_dev(hcd, udev); + + usb_put_dev(udev); +} + +static int lvs_rh_clear_port_feature(struct usb_device *hdev, + int port1, int feature) +{ + return usb_control_msg(hdev, usb_sndctrlpipe(hdev, 0), + USB_REQ_CLEAR_FEATURE, USB_RT_PORT, feature, port1, + NULL, 0, 1000); +} + +static int lvs_rh_set_port_feature(struct usb_device *hdev, + int port1, int feature) +{ + return usb_control_msg(hdev, usb_sndctrlpipe(hdev, 0), + USB_REQ_SET_FEATURE, USB_RT_PORT, feature, port1, + NULL, 0, 1000); +} + +static ssize_t u3_entry_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct usb_device *hdev = interface_to_usbdev(intf); + struct lvs_rh *lvs = usb_get_intfdata(intf); + struct usb_device *udev; + int ret; + + udev = create_lvs_device(intf); + if (!udev) { + dev_err(dev, "failed to create lvs device\n"); + return -ENOMEM; + } + + ret = lvs_rh_set_port_feature(hdev, lvs->portnum, + USB_PORT_FEAT_SUSPEND); + if (ret < 0) + dev_err(dev, "can't issue U3 entry %d\n", ret); + + destroy_lvs_device(udev); + + if (ret < 0) + return ret; + + return count; +} +static DEVICE_ATTR_WO(u3_entry); + +static ssize_t u3_exit_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct usb_device *hdev = interface_to_usbdev(intf); + struct lvs_rh *lvs = usb_get_intfdata(intf); + struct usb_device *udev; + int ret; + + udev = create_lvs_device(intf); + if (!udev) { + dev_err(dev, "failed to create lvs device\n"); + return -ENOMEM; + } + + ret = lvs_rh_clear_port_feature(hdev, lvs->portnum, + USB_PORT_FEAT_SUSPEND); + if (ret < 0) + dev_err(dev, "can't issue U3 exit %d\n", ret); + + destroy_lvs_device(udev); + + if (ret < 0) + return ret; + + return count; +} +static DEVICE_ATTR_WO(u3_exit); + +static ssize_t hot_reset_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct usb_device *hdev = interface_to_usbdev(intf); + struct lvs_rh *lvs = usb_get_intfdata(intf); + int ret; + + ret = lvs_rh_set_port_feature(hdev, lvs->portnum, + USB_PORT_FEAT_RESET); + if (ret < 0) { + dev_err(dev, "can't issue hot reset %d\n", ret); + return ret; + } + + return count; +} +static DEVICE_ATTR_WO(hot_reset); + +static ssize_t warm_reset_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct usb_device *hdev = interface_to_usbdev(intf); + struct lvs_rh *lvs = usb_get_intfdata(intf); + int ret; + + ret = lvs_rh_set_port_feature(hdev, lvs->portnum, + USB_PORT_FEAT_BH_PORT_RESET); + if (ret < 0) { + dev_err(dev, "can't issue warm reset %d\n", ret); + return ret; + } + + return count; +} +static DEVICE_ATTR_WO(warm_reset); + +static ssize_t u2_timeout_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct usb_device *hdev = interface_to_usbdev(intf); + struct lvs_rh *lvs = usb_get_intfdata(intf); + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret < 0) { + dev_err(dev, "couldn't parse string %d\n", ret); + return ret; + } + + if (val > 127) + return -EINVAL; + + ret = lvs_rh_set_port_feature(hdev, lvs->portnum | (val << 8), + USB_PORT_FEAT_U2_TIMEOUT); + if (ret < 0) { + dev_err(dev, "Error %d while setting U2 timeout %ld\n", ret, val); + return ret; + } + + return count; +} +static DEVICE_ATTR_WO(u2_timeout); + +static ssize_t u1_timeout_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct usb_device *hdev = interface_to_usbdev(intf); + struct lvs_rh *lvs = usb_get_intfdata(intf); + unsigned long val; + int ret; + + ret = kstrtoul(buf, 10, &val); + if (ret < 0) { + dev_err(dev, "couldn't parse string %d\n", ret); + return ret; + } + + if (val > 127) + return -EINVAL; + + ret = lvs_rh_set_port_feature(hdev, lvs->portnum | (val << 8), + USB_PORT_FEAT_U1_TIMEOUT); + if (ret < 0) { + dev_err(dev, "Error %d while setting U1 timeout %ld\n", ret, val); + return ret; + } + + return count; +} +static DEVICE_ATTR_WO(u1_timeout); + +static ssize_t get_dev_desc_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct usb_device *udev; + struct usb_device_descriptor *descriptor; + int ret; + + descriptor = kmalloc(sizeof(*descriptor), GFP_KERNEL); + if (!descriptor) + return -ENOMEM; + + udev = create_lvs_device(intf); + if (!udev) { + dev_err(dev, "failed to create lvs device\n"); + ret = -ENOMEM; + goto free_desc; + } + + ret = usb_control_msg(udev, (PIPE_CONTROL << 30) | USB_DIR_IN, + USB_REQ_GET_DESCRIPTOR, USB_DIR_IN, USB_DT_DEVICE << 8, + 0, descriptor, sizeof(*descriptor), + USB_CTRL_GET_TIMEOUT); + if (ret < 0) + dev_err(dev, "can't read device descriptor %d\n", ret); + + destroy_lvs_device(udev); + +free_desc: + kfree(descriptor); + + if (ret < 0) + return ret; + + return count; +} +static DEVICE_ATTR_WO(get_dev_desc); + +static ssize_t enable_compliance_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct usb_device *hdev = interface_to_usbdev(intf); + struct lvs_rh *lvs = usb_get_intfdata(intf); + int ret; + + ret = lvs_rh_set_port_feature(hdev, + lvs->portnum | USB_SS_PORT_LS_COMP_MOD << 3, + USB_PORT_FEAT_LINK_STATE); + if (ret < 0) { + dev_err(dev, "can't enable compliance mode %d\n", ret); + return ret; + } + + return count; +} +static DEVICE_ATTR_WO(enable_compliance); + +static struct attribute *lvs_attrs[] = { + &dev_attr_get_dev_desc.attr, + &dev_attr_u1_timeout.attr, + &dev_attr_u2_timeout.attr, + &dev_attr_hot_reset.attr, + &dev_attr_warm_reset.attr, + &dev_attr_u3_entry.attr, + &dev_attr_u3_exit.attr, + &dev_attr_enable_compliance.attr, + NULL +}; +ATTRIBUTE_GROUPS(lvs); + +static void lvs_rh_work(struct work_struct *work) +{ + struct lvs_rh *lvs = container_of(work, struct lvs_rh, rh_work); + struct usb_interface *intf = lvs->intf; + struct usb_device *hdev = interface_to_usbdev(intf); + struct usb_hcd *hcd = bus_to_hcd(hdev->bus); + struct usb_hub_descriptor *descriptor = &lvs->descriptor; + struct usb_port_status *port_status = &lvs->port_status; + int i, ret = 0; + u16 portchange; + + /* Examine each root port */ + for (i = 1; i <= descriptor->bNbrPorts; i++) { + ret = usb_control_msg(hdev, usb_rcvctrlpipe(hdev, 0), + USB_REQ_GET_STATUS, USB_DIR_IN | USB_RT_PORT, 0, i, + port_status, sizeof(*port_status), 1000); + if (ret < 4) + continue; + + portchange = le16_to_cpu(port_status->wPortChange); + + if (portchange & USB_PORT_STAT_C_LINK_STATE) + lvs_rh_clear_port_feature(hdev, i, + USB_PORT_FEAT_C_PORT_LINK_STATE); + if (portchange & USB_PORT_STAT_C_ENABLE) + lvs_rh_clear_port_feature(hdev, i, + USB_PORT_FEAT_C_ENABLE); + if (portchange & USB_PORT_STAT_C_RESET) + lvs_rh_clear_port_feature(hdev, i, + USB_PORT_FEAT_C_RESET); + if (portchange & USB_PORT_STAT_C_BH_RESET) + lvs_rh_clear_port_feature(hdev, i, + USB_PORT_FEAT_C_BH_PORT_RESET); + if (portchange & USB_PORT_STAT_C_CONNECTION) { + lvs_rh_clear_port_feature(hdev, i, + USB_PORT_FEAT_C_CONNECTION); + + if (le16_to_cpu(port_status->wPortStatus) & + USB_PORT_STAT_CONNECTION) { + lvs->present = true; + lvs->portnum = i; + if (hcd->usb_phy) + usb_phy_notify_connect(hcd->usb_phy, + USB_SPEED_SUPER); + } else { + lvs->present = false; + if (hcd->usb_phy) + usb_phy_notify_disconnect(hcd->usb_phy, + USB_SPEED_SUPER); + } + break; + } + } + + ret = usb_submit_urb(lvs->urb, GFP_KERNEL); + if (ret != 0 && ret != -ENODEV && ret != -EPERM) + dev_err(&intf->dev, "urb resubmit error %d\n", ret); +} + +static void lvs_rh_irq(struct urb *urb) +{ + struct lvs_rh *lvs = urb->context; + + schedule_work(&lvs->rh_work); +} + +static int lvs_rh_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *hdev; + struct usb_host_interface *desc; + struct usb_endpoint_descriptor *endpoint; + struct lvs_rh *lvs; + unsigned int pipe; + int ret, maxp; + + hdev = interface_to_usbdev(intf); + desc = intf->cur_altsetting; + + ret = usb_find_int_in_endpoint(desc, &endpoint); + if (ret) + return ret; + + /* valid only for SS root hub */ + if (hdev->descriptor.bDeviceProtocol != USB_HUB_PR_SS || hdev->parent) { + dev_err(&intf->dev, "Bind LVS driver with SS root Hub only\n"); + return -EINVAL; + } + + lvs = devm_kzalloc(&intf->dev, sizeof(*lvs), GFP_KERNEL); + if (!lvs) + return -ENOMEM; + + lvs->intf = intf; + usb_set_intfdata(intf, lvs); + + /* how many number of ports this root hub has */ + ret = usb_control_msg(hdev, usb_rcvctrlpipe(hdev, 0), + USB_REQ_GET_DESCRIPTOR, USB_DIR_IN | USB_RT_HUB, + USB_DT_SS_HUB << 8, 0, &lvs->descriptor, + USB_DT_SS_HUB_SIZE, USB_CTRL_GET_TIMEOUT); + if (ret < (USB_DT_HUB_NONVAR_SIZE + 2)) { + dev_err(&hdev->dev, "wrong root hub descriptor read %d\n", ret); + return ret < 0 ? ret : -EINVAL; + } + + /* submit urb to poll interrupt endpoint */ + lvs->urb = usb_alloc_urb(0, GFP_KERNEL); + if (!lvs->urb) + return -ENOMEM; + + INIT_WORK(&lvs->rh_work, lvs_rh_work); + + pipe = usb_rcvintpipe(hdev, endpoint->bEndpointAddress); + maxp = usb_maxpacket(hdev, pipe); + usb_fill_int_urb(lvs->urb, hdev, pipe, &lvs->buffer[0], maxp, + lvs_rh_irq, lvs, endpoint->bInterval); + + ret = usb_submit_urb(lvs->urb, GFP_KERNEL); + if (ret < 0) { + dev_err(&intf->dev, "couldn't submit lvs urb %d\n", ret); + goto free_urb; + } + + return ret; + +free_urb: + usb_free_urb(lvs->urb); + return ret; +} + +static void lvs_rh_disconnect(struct usb_interface *intf) +{ + struct lvs_rh *lvs = usb_get_intfdata(intf); + + usb_poison_urb(lvs->urb); /* used in scheduled work */ + flush_work(&lvs->rh_work); + usb_free_urb(lvs->urb); +} + +static struct usb_driver lvs_driver = { + .name = "lvs", + .probe = lvs_rh_probe, + .disconnect = lvs_rh_disconnect, + .dev_groups = lvs_groups, +}; + +module_usb_driver(lvs_driver); + +MODULE_DESCRIPTION("Link Layer Validation System Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/misc/onboard_usb_hub.c b/drivers/usb/misc/onboard_usb_hub.c new file mode 100644 index 0000000000..d72130eda5 --- /dev/null +++ b/drivers/usb/misc/onboard_usb_hub.c @@ -0,0 +1,490 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for onboard USB hubs + * + * Copyright (c) 2022, Google LLC + */ + +#include <linux/device.h> +#include <linux/export.h> +#include <linux/gpio/consumer.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> +#include <linux/suspend.h> +#include <linux/sysfs.h> +#include <linux/usb.h> +#include <linux/usb/hcd.h> +#include <linux/usb/onboard_hub.h> +#include <linux/workqueue.h> + +#include "onboard_usb_hub.h" + +/* + * Use generic names, as the actual names might differ between hubs. If a new + * hub requires more than the currently supported supplies, add a new one here. + */ +static const char * const supply_names[] = { + "vdd", + "vdd2", +}; + +#define MAX_SUPPLIES ARRAY_SIZE(supply_names) + +static void onboard_hub_attach_usb_driver(struct work_struct *work); + +static struct usb_device_driver onboard_hub_usbdev_driver; +static DECLARE_WORK(attach_usb_driver_work, onboard_hub_attach_usb_driver); + +/************************** Platform driver **************************/ + +struct usbdev_node { + struct usb_device *udev; + struct list_head list; +}; + +struct onboard_hub { + struct regulator_bulk_data supplies[MAX_SUPPLIES]; + struct device *dev; + const struct onboard_hub_pdata *pdata; + struct gpio_desc *reset_gpio; + bool always_powered_in_suspend; + bool is_powered_on; + bool going_away; + struct list_head udev_list; + struct mutex lock; +}; + +static int onboard_hub_power_on(struct onboard_hub *hub) +{ + int err; + + err = regulator_bulk_enable(hub->pdata->num_supplies, hub->supplies); + if (err) { + dev_err(hub->dev, "failed to enable supplies: %d\n", err); + return err; + } + + fsleep(hub->pdata->reset_us); + gpiod_set_value_cansleep(hub->reset_gpio, 0); + + hub->is_powered_on = true; + + return 0; +} + +static int onboard_hub_power_off(struct onboard_hub *hub) +{ + int err; + + gpiod_set_value_cansleep(hub->reset_gpio, 1); + + err = regulator_bulk_disable(hub->pdata->num_supplies, hub->supplies); + if (err) { + dev_err(hub->dev, "failed to disable supplies: %d\n", err); + return err; + } + + hub->is_powered_on = false; + + return 0; +} + +static int __maybe_unused onboard_hub_suspend(struct device *dev) +{ + struct onboard_hub *hub = dev_get_drvdata(dev); + struct usbdev_node *node; + bool power_off = true; + + if (hub->always_powered_in_suspend) + return 0; + + mutex_lock(&hub->lock); + + list_for_each_entry(node, &hub->udev_list, list) { + if (!device_may_wakeup(node->udev->bus->controller)) + continue; + + if (usb_wakeup_enabled_descendants(node->udev)) { + power_off = false; + break; + } + } + + mutex_unlock(&hub->lock); + + if (!power_off) + return 0; + + return onboard_hub_power_off(hub); +} + +static int __maybe_unused onboard_hub_resume(struct device *dev) +{ + struct onboard_hub *hub = dev_get_drvdata(dev); + + if (hub->is_powered_on) + return 0; + + return onboard_hub_power_on(hub); +} + +static inline void get_udev_link_name(const struct usb_device *udev, char *buf, size_t size) +{ + snprintf(buf, size, "usb_dev.%s", dev_name(&udev->dev)); +} + +static int onboard_hub_add_usbdev(struct onboard_hub *hub, struct usb_device *udev) +{ + struct usbdev_node *node; + char link_name[64]; + int err; + + mutex_lock(&hub->lock); + + if (hub->going_away) { + err = -EINVAL; + goto error; + } + + node = kzalloc(sizeof(*node), GFP_KERNEL); + if (!node) { + err = -ENOMEM; + goto error; + } + + node->udev = udev; + + list_add(&node->list, &hub->udev_list); + + mutex_unlock(&hub->lock); + + get_udev_link_name(udev, link_name, sizeof(link_name)); + WARN_ON(sysfs_create_link(&hub->dev->kobj, &udev->dev.kobj, link_name)); + + return 0; + +error: + mutex_unlock(&hub->lock); + + return err; +} + +static void onboard_hub_remove_usbdev(struct onboard_hub *hub, const struct usb_device *udev) +{ + struct usbdev_node *node; + char link_name[64]; + + get_udev_link_name(udev, link_name, sizeof(link_name)); + sysfs_remove_link(&hub->dev->kobj, link_name); + + mutex_lock(&hub->lock); + + list_for_each_entry(node, &hub->udev_list, list) { + if (node->udev == udev) { + list_del(&node->list); + kfree(node); + break; + } + } + + mutex_unlock(&hub->lock); +} + +static ssize_t always_powered_in_suspend_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + const struct onboard_hub *hub = dev_get_drvdata(dev); + + return sysfs_emit(buf, "%d\n", hub->always_powered_in_suspend); +} + +static ssize_t always_powered_in_suspend_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct onboard_hub *hub = dev_get_drvdata(dev); + bool val; + int ret; + + ret = kstrtobool(buf, &val); + if (ret < 0) + return ret; + + hub->always_powered_in_suspend = val; + + return count; +} +static DEVICE_ATTR_RW(always_powered_in_suspend); + +static struct attribute *onboard_hub_attrs[] = { + &dev_attr_always_powered_in_suspend.attr, + NULL, +}; +ATTRIBUTE_GROUPS(onboard_hub); + +static void onboard_hub_attach_usb_driver(struct work_struct *work) +{ + int err; + + err = driver_attach(&onboard_hub_usbdev_driver.drvwrap.driver); + if (err) + pr_err("Failed to attach USB driver: %d\n", err); +} + +static int onboard_hub_probe(struct platform_device *pdev) +{ + const struct of_device_id *of_id; + struct device *dev = &pdev->dev; + struct onboard_hub *hub; + unsigned int i; + int err; + + hub = devm_kzalloc(dev, sizeof(*hub), GFP_KERNEL); + if (!hub) + return -ENOMEM; + + of_id = of_match_device(onboard_hub_match, &pdev->dev); + if (!of_id) + return -ENODEV; + + hub->pdata = of_id->data; + if (!hub->pdata) + return -EINVAL; + + if (hub->pdata->num_supplies > MAX_SUPPLIES) + return dev_err_probe(dev, -EINVAL, "max %zu supplies supported!\n", + MAX_SUPPLIES); + + for (i = 0; i < hub->pdata->num_supplies; i++) + hub->supplies[i].supply = supply_names[i]; + + err = devm_regulator_bulk_get(dev, hub->pdata->num_supplies, hub->supplies); + if (err) { + dev_err(dev, "Failed to get regulator supplies: %d\n", err); + return err; + } + + hub->reset_gpio = devm_gpiod_get_optional(dev, "reset", + GPIOD_OUT_HIGH); + if (IS_ERR(hub->reset_gpio)) + return dev_err_probe(dev, PTR_ERR(hub->reset_gpio), "failed to get reset GPIO\n"); + + hub->dev = dev; + mutex_init(&hub->lock); + INIT_LIST_HEAD(&hub->udev_list); + + dev_set_drvdata(dev, hub); + + err = onboard_hub_power_on(hub); + if (err) + return err; + + /* + * The USB driver might have been detached from the USB devices by + * onboard_hub_remove() (e.g. through an 'unbind' by userspace), + * make sure to re-attach it if needed. + * + * This needs to be done deferred to avoid self-deadlocks on systems + * with nested onboard hubs. + */ + schedule_work(&attach_usb_driver_work); + + return 0; +} + +static void onboard_hub_remove(struct platform_device *pdev) +{ + struct onboard_hub *hub = dev_get_drvdata(&pdev->dev); + struct usbdev_node *node; + struct usb_device *udev; + + hub->going_away = true; + + mutex_lock(&hub->lock); + + /* unbind the USB devices to avoid dangling references to this device */ + while (!list_empty(&hub->udev_list)) { + node = list_first_entry(&hub->udev_list, struct usbdev_node, list); + udev = node->udev; + + /* + * Unbinding the driver will call onboard_hub_remove_usbdev(), + * which acquires hub->lock. We must release the lock first. + */ + get_device(&udev->dev); + mutex_unlock(&hub->lock); + device_release_driver(&udev->dev); + put_device(&udev->dev); + mutex_lock(&hub->lock); + } + + mutex_unlock(&hub->lock); + + onboard_hub_power_off(hub); +} + +MODULE_DEVICE_TABLE(of, onboard_hub_match); + +static const struct dev_pm_ops __maybe_unused onboard_hub_pm_ops = { + SET_LATE_SYSTEM_SLEEP_PM_OPS(onboard_hub_suspend, onboard_hub_resume) +}; + +static struct platform_driver onboard_hub_driver = { + .probe = onboard_hub_probe, + .remove_new = onboard_hub_remove, + + .driver = { + .name = "onboard-usb-hub", + .of_match_table = onboard_hub_match, + .pm = pm_ptr(&onboard_hub_pm_ops), + .dev_groups = onboard_hub_groups, + }, +}; + +/************************** USB driver **************************/ + +#define VENDOR_ID_CYPRESS 0x04b4 +#define VENDOR_ID_GENESYS 0x05e3 +#define VENDOR_ID_MICROCHIP 0x0424 +#define VENDOR_ID_REALTEK 0x0bda +#define VENDOR_ID_TI 0x0451 +#define VENDOR_ID_VIA 0x2109 + +/* + * Returns the onboard_hub platform device that is associated with the USB + * device passed as parameter. + */ +static struct onboard_hub *_find_onboard_hub(struct device *dev) +{ + struct platform_device *pdev; + struct device_node *np; + struct onboard_hub *hub; + + pdev = of_find_device_by_node(dev->of_node); + if (!pdev) { + np = of_parse_phandle(dev->of_node, "peer-hub", 0); + if (!np) { + dev_err(dev, "failed to find device node for peer hub\n"); + return ERR_PTR(-EINVAL); + } + + pdev = of_find_device_by_node(np); + of_node_put(np); + + if (!pdev) + return ERR_PTR(-ENODEV); + } + + hub = dev_get_drvdata(&pdev->dev); + put_device(&pdev->dev); + + /* + * The presence of drvdata ('hub') indicates that the platform driver + * finished probing. This handles the case where (conceivably) we could + * be running at the exact same time as the platform driver's probe. If + * we detect the race we request probe deferral and we'll come back and + * try again. + */ + if (!hub) + return ERR_PTR(-EPROBE_DEFER); + + return hub; +} + +static int onboard_hub_usbdev_probe(struct usb_device *udev) +{ + struct device *dev = &udev->dev; + struct onboard_hub *hub; + int err; + + /* ignore supported hubs without device tree node */ + if (!dev->of_node) + return -ENODEV; + + hub = _find_onboard_hub(dev); + if (IS_ERR(hub)) + return PTR_ERR(hub); + + dev_set_drvdata(dev, hub); + + err = onboard_hub_add_usbdev(hub, udev); + if (err) + return err; + + return 0; +} + +static void onboard_hub_usbdev_disconnect(struct usb_device *udev) +{ + struct onboard_hub *hub = dev_get_drvdata(&udev->dev); + + onboard_hub_remove_usbdev(hub, udev); +} + +static const struct usb_device_id onboard_hub_id_table[] = { + { USB_DEVICE(VENDOR_ID_CYPRESS, 0x6504) }, /* CYUSB33{0,1,2}x/CYUSB230x 3.0 */ + { USB_DEVICE(VENDOR_ID_CYPRESS, 0x6506) }, /* CYUSB33{0,1,2}x/CYUSB230x 2.0 */ + { USB_DEVICE(VENDOR_ID_GENESYS, 0x0608) }, /* Genesys Logic GL850G USB 2.0 */ + { USB_DEVICE(VENDOR_ID_GENESYS, 0x0610) }, /* Genesys Logic GL852G USB 2.0 */ + { USB_DEVICE(VENDOR_ID_GENESYS, 0x0620) }, /* Genesys Logic GL3523 USB 3.1 */ + { USB_DEVICE(VENDOR_ID_MICROCHIP, 0x2412) }, /* USB2412 USB 2.0 */ + { USB_DEVICE(VENDOR_ID_MICROCHIP, 0x2514) }, /* USB2514B USB 2.0 */ + { USB_DEVICE(VENDOR_ID_MICROCHIP, 0x2517) }, /* USB2517 USB 2.0 */ + { USB_DEVICE(VENDOR_ID_MICROCHIP, 0x2744) }, /* USB5744 USB 2.0 */ + { USB_DEVICE(VENDOR_ID_MICROCHIP, 0x5744) }, /* USB5744 USB 3.0 */ + { USB_DEVICE(VENDOR_ID_REALTEK, 0x0411) }, /* RTS5411 USB 3.1 */ + { USB_DEVICE(VENDOR_ID_REALTEK, 0x5411) }, /* RTS5411 USB 2.1 */ + { USB_DEVICE(VENDOR_ID_REALTEK, 0x0414) }, /* RTS5414 USB 3.2 */ + { USB_DEVICE(VENDOR_ID_REALTEK, 0x5414) }, /* RTS5414 USB 2.1 */ + { USB_DEVICE(VENDOR_ID_TI, 0x8140) }, /* TI USB8041 3.0 */ + { USB_DEVICE(VENDOR_ID_TI, 0x8142) }, /* TI USB8041 2.0 */ + { USB_DEVICE(VENDOR_ID_VIA, 0x0817) }, /* VIA VL817 3.1 */ + { USB_DEVICE(VENDOR_ID_VIA, 0x2817) }, /* VIA VL817 2.0 */ + {} +}; +MODULE_DEVICE_TABLE(usb, onboard_hub_id_table); + +static struct usb_device_driver onboard_hub_usbdev_driver = { + .name = "onboard-usb-hub", + .probe = onboard_hub_usbdev_probe, + .disconnect = onboard_hub_usbdev_disconnect, + .generic_subclass = 1, + .supports_autosuspend = 1, + .id_table = onboard_hub_id_table, +}; + +static int __init onboard_hub_init(void) +{ + int ret; + + ret = usb_register_device_driver(&onboard_hub_usbdev_driver, THIS_MODULE); + if (ret) + return ret; + + ret = platform_driver_register(&onboard_hub_driver); + if (ret) + usb_deregister_device_driver(&onboard_hub_usbdev_driver); + + return ret; +} +module_init(onboard_hub_init); + +static void __exit onboard_hub_exit(void) +{ + usb_deregister_device_driver(&onboard_hub_usbdev_driver); + platform_driver_unregister(&onboard_hub_driver); + + cancel_work_sync(&attach_usb_driver_work); +} +module_exit(onboard_hub_exit); + +MODULE_AUTHOR("Matthias Kaehlcke <mka@chromium.org>"); +MODULE_DESCRIPTION("Driver for discrete onboard USB hubs"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/misc/onboard_usb_hub.h b/drivers/usb/misc/onboard_usb_hub.h new file mode 100644 index 0000000000..8af34e6d1a --- /dev/null +++ b/drivers/usb/misc/onboard_usb_hub.h @@ -0,0 +1,76 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2022, Google LLC + */ + +#ifndef _USB_MISC_ONBOARD_USB_HUB_H +#define _USB_MISC_ONBOARD_USB_HUB_H + +struct onboard_hub_pdata { + unsigned long reset_us; /* reset pulse width in us */ + unsigned int num_supplies; /* number of supplies */ +}; + +static const struct onboard_hub_pdata microchip_usb424_data = { + .reset_us = 1, + .num_supplies = 1, +}; + +static const struct onboard_hub_pdata microchip_usb5744_data = { + .reset_us = 0, + .num_supplies = 2, +}; + +static const struct onboard_hub_pdata realtek_rts5411_data = { + .reset_us = 0, + .num_supplies = 1, +}; + +static const struct onboard_hub_pdata ti_tusb8041_data = { + .reset_us = 3000, + .num_supplies = 1, +}; + +static const struct onboard_hub_pdata cypress_hx3_data = { + .reset_us = 10000, + .num_supplies = 2, +}; + +static const struct onboard_hub_pdata genesys_gl850g_data = { + .reset_us = 3, + .num_supplies = 1, +}; + +static const struct onboard_hub_pdata genesys_gl852g_data = { + .reset_us = 50, + .num_supplies = 1, +}; + +static const struct onboard_hub_pdata vialab_vl817_data = { + .reset_us = 10, + .num_supplies = 1, +}; + +static const struct of_device_id onboard_hub_match[] = { + { .compatible = "usb424,2412", .data = µchip_usb424_data, }, + { .compatible = "usb424,2514", .data = µchip_usb424_data, }, + { .compatible = "usb424,2517", .data = µchip_usb424_data, }, + { .compatible = "usb424,2744", .data = µchip_usb5744_data, }, + { .compatible = "usb424,5744", .data = µchip_usb5744_data, }, + { .compatible = "usb451,8140", .data = &ti_tusb8041_data, }, + { .compatible = "usb451,8142", .data = &ti_tusb8041_data, }, + { .compatible = "usb4b4,6504", .data = &cypress_hx3_data, }, + { .compatible = "usb4b4,6506", .data = &cypress_hx3_data, }, + { .compatible = "usb5e3,608", .data = &genesys_gl850g_data, }, + { .compatible = "usb5e3,610", .data = &genesys_gl852g_data, }, + { .compatible = "usb5e3,620", .data = &genesys_gl852g_data, }, + { .compatible = "usbbda,411", .data = &realtek_rts5411_data, }, + { .compatible = "usbbda,5411", .data = &realtek_rts5411_data, }, + { .compatible = "usbbda,414", .data = &realtek_rts5411_data, }, + { .compatible = "usbbda,5414", .data = &realtek_rts5411_data, }, + { .compatible = "usb2109,817", .data = &vialab_vl817_data, }, + { .compatible = "usb2109,2817", .data = &vialab_vl817_data, }, + {} +}; + +#endif /* _USB_MISC_ONBOARD_USB_HUB_H */ diff --git a/drivers/usb/misc/onboard_usb_hub_pdevs.c b/drivers/usb/misc/onboard_usb_hub_pdevs.c new file mode 100644 index 0000000000..ed22a18f4a --- /dev/null +++ b/drivers/usb/misc/onboard_usb_hub_pdevs.c @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * API for creating and destroying USB onboard hub platform devices + * + * Copyright (c) 2022, Google LLC + */ + +#include <linux/device.h> +#include <linux/export.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/of.h> +#include <linux/of_platform.h> +#include <linux/platform_device.h> +#include <linux/usb.h> +#include <linux/usb/hcd.h> +#include <linux/usb/of.h> +#include <linux/usb/onboard_hub.h> + +#include "onboard_usb_hub.h" + +struct pdev_list_entry { + struct platform_device *pdev; + struct list_head node; +}; + +static bool of_is_onboard_usb_hub(const struct device_node *np) +{ + return !!of_match_node(onboard_hub_match, np); +} + +/** + * onboard_hub_create_pdevs -- create platform devices for onboard USB hubs + * @parent_hub : parent hub to scan for connected onboard hubs + * @pdev_list : list of onboard hub platform devices owned by the parent hub + * + * Creates a platform device for each supported onboard hub that is connected to + * the given parent hub. The platform device is in charge of initializing the + * hub (enable regulators, take the hub out of reset, ...) and can optionally + * control whether the hub remains powered during system suspend or not. + * + * To keep track of the platform devices they are added to a list that is owned + * by the parent hub. + * + * Some background about the logic in this function, which can be a bit hard + * to follow: + * + * Root hubs don't have dedicated device tree nodes, but use the node of their + * HCD. The primary and secondary HCD are usually represented by a single DT + * node. That means the root hubs of the primary and secondary HCD share the + * same device tree node (the HCD node). As a result this function can be called + * twice with the same DT node for root hubs. We only want to create a single + * platform device for each physical onboard hub, hence for root hubs the loop + * is only executed for the root hub of the primary HCD. Since the function + * scans through all child nodes it still creates pdevs for onboard hubs + * connected to the root hub of the secondary HCD if needed. + * + * Further there must be only one platform device for onboard hubs with a peer + * hub (the hub is a single physical device). To achieve this two measures are + * taken: pdevs for onboard hubs with a peer are only created when the function + * is called on behalf of the parent hub that is connected to the primary HCD + * (directly or through other hubs). For onboard hubs connected to root hubs + * the function processes the nodes of both peers. A platform device is only + * created if the peer hub doesn't have one already. + */ +void onboard_hub_create_pdevs(struct usb_device *parent_hub, struct list_head *pdev_list) +{ + int i; + struct usb_hcd *hcd = bus_to_hcd(parent_hub->bus); + struct device_node *np, *npc; + struct platform_device *pdev; + struct pdev_list_entry *pdle; + + if (!parent_hub->dev.of_node) + return; + + if (!parent_hub->parent && !usb_hcd_is_primary_hcd(hcd)) + return; + + for (i = 1; i <= parent_hub->maxchild; i++) { + np = usb_of_get_device_node(parent_hub, i); + if (!np) + continue; + + if (!of_is_onboard_usb_hub(np)) + goto node_put; + + npc = of_parse_phandle(np, "peer-hub", 0); + if (npc) { + if (!usb_hcd_is_primary_hcd(hcd)) { + of_node_put(npc); + goto node_put; + } + + pdev = of_find_device_by_node(npc); + of_node_put(npc); + + if (pdev) { + put_device(&pdev->dev); + goto node_put; + } + } + + pdev = of_platform_device_create(np, NULL, &parent_hub->dev); + if (!pdev) { + dev_err(&parent_hub->dev, + "failed to create platform device for onboard hub '%pOF'\n", np); + goto node_put; + } + + pdle = kzalloc(sizeof(*pdle), GFP_KERNEL); + if (!pdle) { + of_platform_device_destroy(&pdev->dev, NULL); + goto node_put; + } + + pdle->pdev = pdev; + list_add(&pdle->node, pdev_list); + +node_put: + of_node_put(np); + } +} +EXPORT_SYMBOL_GPL(onboard_hub_create_pdevs); + +/** + * onboard_hub_destroy_pdevs -- free resources of onboard hub platform devices + * @pdev_list : list of onboard hub platform devices + * + * Destroys the platform devices in the given list and frees the memory associated + * with the list entry. + */ +void onboard_hub_destroy_pdevs(struct list_head *pdev_list) +{ + struct pdev_list_entry *pdle, *tmp; + + list_for_each_entry_safe(pdle, tmp, pdev_list, node) { + list_del(&pdle->node); + of_platform_device_destroy(&pdle->pdev->dev, NULL); + kfree(pdle); + } +} +EXPORT_SYMBOL_GPL(onboard_hub_destroy_pdevs); diff --git a/drivers/usb/misc/qcom_eud.c b/drivers/usb/misc/qcom_eud.c new file mode 100644 index 0000000000..7f371ea124 --- /dev/null +++ b/drivers/usb/misc/qcom_eud.c @@ -0,0 +1,249 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2015-2021, The Linux Foundation. All rights reserved. + */ + +#include <linux/bitops.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/iopoll.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/sysfs.h> +#include <linux/usb/role.h> + +#define EUD_REG_INT1_EN_MASK 0x0024 +#define EUD_REG_INT_STATUS_1 0x0044 +#define EUD_REG_CTL_OUT_1 0x0074 +#define EUD_REG_VBUS_INT_CLR 0x0080 +#define EUD_REG_CSR_EUD_EN 0x1014 +#define EUD_REG_SW_ATTACH_DET 0x1018 +#define EUD_REG_EUD_EN2 0x0000 + +#define EUD_ENABLE BIT(0) +#define EUD_INT_PET_EUD BIT(0) +#define EUD_INT_VBUS BIT(2) +#define EUD_INT_SAFE_MODE BIT(4) +#define EUD_INT_ALL (EUD_INT_VBUS | EUD_INT_SAFE_MODE) + +struct eud_chip { + struct device *dev; + struct usb_role_switch *role_sw; + void __iomem *base; + void __iomem *mode_mgr; + unsigned int int_status; + int irq; + bool enabled; + bool usb_attached; +}; + +static int enable_eud(struct eud_chip *priv) +{ + writel(EUD_ENABLE, priv->base + EUD_REG_CSR_EUD_EN); + writel(EUD_INT_VBUS | EUD_INT_SAFE_MODE, + priv->base + EUD_REG_INT1_EN_MASK); + writel(1, priv->mode_mgr + EUD_REG_EUD_EN2); + + return usb_role_switch_set_role(priv->role_sw, USB_ROLE_DEVICE); +} + +static void disable_eud(struct eud_chip *priv) +{ + writel(0, priv->base + EUD_REG_CSR_EUD_EN); + writel(0, priv->mode_mgr + EUD_REG_EUD_EN2); +} + +static ssize_t enable_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct eud_chip *chip = dev_get_drvdata(dev); + + return sysfs_emit(buf, "%d\n", chip->enabled); +} + +static ssize_t enable_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct eud_chip *chip = dev_get_drvdata(dev); + bool enable; + int ret; + + if (kstrtobool(buf, &enable)) + return -EINVAL; + + if (enable) { + ret = enable_eud(chip); + if (!ret) + chip->enabled = enable; + else + disable_eud(chip); + } else { + disable_eud(chip); + } + + return count; +} + +static DEVICE_ATTR_RW(enable); + +static struct attribute *eud_attrs[] = { + &dev_attr_enable.attr, + NULL, +}; +ATTRIBUTE_GROUPS(eud); + +static void usb_attach_detach(struct eud_chip *chip) +{ + u32 reg; + + /* read ctl_out_1[4] to find USB attach or detach event */ + reg = readl(chip->base + EUD_REG_CTL_OUT_1); + chip->usb_attached = reg & EUD_INT_SAFE_MODE; +} + +static void pet_eud(struct eud_chip *chip) +{ + u32 reg; + int ret; + + /* When the EUD_INT_PET_EUD in SW_ATTACH_DET is set, the cable has been + * disconnected and we need to detach the pet to check if EUD is in safe + * mode before attaching again. + */ + reg = readl(chip->base + EUD_REG_SW_ATTACH_DET); + if (reg & EUD_INT_PET_EUD) { + /* Detach & Attach pet for EUD */ + writel(0, chip->base + EUD_REG_SW_ATTACH_DET); + /* Delay to make sure detach pet is done before attach pet */ + ret = readl_poll_timeout(chip->base + EUD_REG_SW_ATTACH_DET, + reg, (reg == 0), 1, 100); + if (ret) { + dev_err(chip->dev, "Detach pet failed\n"); + return; + } + } + /* Attach pet for EUD */ + writel(EUD_INT_PET_EUD, chip->base + EUD_REG_SW_ATTACH_DET); +} + +static irqreturn_t handle_eud_irq(int irq, void *data) +{ + struct eud_chip *chip = data; + u32 reg; + + reg = readl(chip->base + EUD_REG_INT_STATUS_1); + switch (reg & EUD_INT_ALL) { + case EUD_INT_VBUS: + usb_attach_detach(chip); + return IRQ_WAKE_THREAD; + case EUD_INT_SAFE_MODE: + pet_eud(chip); + return IRQ_HANDLED; + default: + return IRQ_NONE; + } +} + +static irqreturn_t handle_eud_irq_thread(int irq, void *data) +{ + struct eud_chip *chip = data; + int ret; + + if (chip->usb_attached) + ret = usb_role_switch_set_role(chip->role_sw, USB_ROLE_DEVICE); + else + ret = usb_role_switch_set_role(chip->role_sw, USB_ROLE_HOST); + if (ret) + dev_err(chip->dev, "failed to set role switch\n"); + + /* set and clear vbus_int_clr[0] to clear interrupt */ + writel(BIT(0), chip->base + EUD_REG_VBUS_INT_CLR); + writel(0, chip->base + EUD_REG_VBUS_INT_CLR); + + return IRQ_HANDLED; +} + +static void eud_role_switch_release(void *data) +{ + struct eud_chip *chip = data; + + usb_role_switch_put(chip->role_sw); +} + +static int eud_probe(struct platform_device *pdev) +{ + struct eud_chip *chip; + int ret; + + chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->dev = &pdev->dev; + + chip->role_sw = usb_role_switch_get(&pdev->dev); + if (IS_ERR(chip->role_sw)) + return dev_err_probe(chip->dev, PTR_ERR(chip->role_sw), + "failed to get role switch\n"); + + ret = devm_add_action_or_reset(chip->dev, eud_role_switch_release, chip); + if (ret) + return dev_err_probe(chip->dev, ret, + "failed to add role switch release action\n"); + + chip->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(chip->base)) + return PTR_ERR(chip->base); + + chip->mode_mgr = devm_platform_ioremap_resource(pdev, 1); + if (IS_ERR(chip->mode_mgr)) + return PTR_ERR(chip->mode_mgr); + + chip->irq = platform_get_irq(pdev, 0); + ret = devm_request_threaded_irq(&pdev->dev, chip->irq, handle_eud_irq, + handle_eud_irq_thread, IRQF_ONESHOT, NULL, chip); + if (ret) + return dev_err_probe(chip->dev, ret, "failed to allocate irq\n"); + + enable_irq_wake(chip->irq); + + platform_set_drvdata(pdev, chip); + + return 0; +} + +static 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,sc7280-eud" }, + { } +}; +MODULE_DEVICE_TABLE(of, eud_dt_match); + +static struct platform_driver eud_driver = { + .probe = eud_probe, + .remove_new = 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 0000000000..42f81c8eaa --- /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 0000000000..28aa1e6ef8 --- /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 0000000000..e5b1228655 --- /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 0000000000..a86032a26d --- /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 0000000000..56972f1ec2 --- /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 0000000000..febf34f9f0 --- /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(sizeof(*sisusb), GFP_KERNEL); + if (!sisusb) + return -ENOMEM; + + kref_init(&sisusb->kref); + + mutex_init(&(sisusb->lock)); + + sisusb->sisusb_dev = dev; + sisusb->vrambase = SISUSB_PCI_MEMBASE; + sisusb->mmiobase = SISUSB_PCI_MMIOBASE; + sisusb->mmiosize = SISUSB_PCI_MMIOSIZE; + sisusb->ioportbase = SISUSB_PCI_IOPORTBASE; + /* Everything else is zero */ + + /* Register device */ + retval = usb_register_dev(intf, &usb_sisusb_class); + if (retval) { + dev_err(&sisusb->sisusb_dev->dev, + "Failed to get a minor for device %d\n", + dev->devnum); + retval = -ENODEV; + goto error_1; + } + + sisusb->minor = intf->minor; + + /* Allocate buffers */ + sisusb->ibufsize = SISUSB_IBUF_SIZE; + sisusb->ibuf = kmalloc(SISUSB_IBUF_SIZE, GFP_KERNEL); + if (!sisusb->ibuf) { + retval = -ENOMEM; + goto error_2; + } + + sisusb->numobufs = 0; + sisusb->obufsize = SISUSB_OBUF_SIZE; + for (i = 0; i < NUMOBUFS; i++) { + sisusb->obuf[i] = kmalloc(SISUSB_OBUF_SIZE, GFP_KERNEL); + if (!sisusb->obuf[i]) { + if (i == 0) { + retval = -ENOMEM; + goto error_3; + } + break; + } + sisusb->numobufs++; + } + + /* Allocate URBs */ + sisusb->sisurbin = usb_alloc_urb(0, GFP_KERNEL); + if (!sisusb->sisurbin) { + retval = -ENOMEM; + goto error_3; + } + sisusb->completein = 1; + + for (i = 0; i < sisusb->numobufs; i++) { + sisusb->sisurbout[i] = usb_alloc_urb(0, GFP_KERNEL); + if (!sisusb->sisurbout[i]) { + retval = -ENOMEM; + goto error_4; + } + sisusb->urbout_context[i].sisusb = (void *)sisusb; + sisusb->urbout_context[i].urbindex = i; + sisusb->urbstatus[i] = 0; + } + + dev_info(&sisusb->sisusb_dev->dev, "Allocated %d output buffers\n", + sisusb->numobufs); + + /* 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 0000000000..26baba3ab7 --- /dev/null +++ b/drivers/usb/misc/trancevibrator.c @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * PlayStation 2 Trance Vibrator driver + * + * Copyright (C) 2006 Sam Hocevar <sam@zoy.org> + */ + +/* Standard include files */ +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/usb.h> + +#define DRIVER_AUTHOR "Sam Hocevar, sam@zoy.org" +#define DRIVER_DESC "PlayStation 2 Trance Vibrator driver" + +#define TRANCEVIBRATOR_VENDOR_ID 0x0b49 /* ASCII Corporation */ +#define TRANCEVIBRATOR_PRODUCT_ID 0x064f /* Trance Vibrator */ + +static const struct usb_device_id id_table[] = { + { USB_DEVICE(TRANCEVIBRATOR_VENDOR_ID, TRANCEVIBRATOR_PRODUCT_ID) }, + { }, +}; +MODULE_DEVICE_TABLE (usb, id_table); + +/* Driver-local specific stuff */ +struct trancevibrator { + struct usb_device *udev; + unsigned int speed; +}; + +static ssize_t speed_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct trancevibrator *tv = usb_get_intfdata(intf); + + return sprintf(buf, "%d\n", tv->speed); +} + +static ssize_t speed_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct trancevibrator *tv = usb_get_intfdata(intf); + int temp, retval, old; + + retval = kstrtoint(buf, 10, &temp); + if (retval) + return retval; + if (temp > 255) + temp = 255; + else if (temp < 0) + temp = 0; + old = tv->speed; + tv->speed = temp; + + dev_dbg(&tv->udev->dev, "speed = %d\n", tv->speed); + + /* Set speed */ + retval = usb_control_msg(tv->udev, usb_sndctrlpipe(tv->udev, 0), + 0x01, /* vendor request: set speed */ + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_OTHER, + tv->speed, /* speed value */ + 0, NULL, 0, USB_CTRL_SET_TIMEOUT); + if (retval) { + tv->speed = old; + dev_dbg(&tv->udev->dev, "retval = %d\n", retval); + return retval; + } + return count; +} +static DEVICE_ATTR_RW(speed); + +static struct attribute *tv_attrs[] = { + &dev_attr_speed.attr, + NULL, +}; +ATTRIBUTE_GROUPS(tv); + +static int tv_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(interface); + struct trancevibrator *dev; + int retval; + + dev = kzalloc(sizeof(struct trancevibrator), GFP_KERNEL); + if (!dev) { + retval = -ENOMEM; + goto error; + } + + dev->udev = usb_get_dev(udev); + usb_set_intfdata(interface, dev); + + return 0; + +error: + kfree(dev); + return retval; +} + +static void tv_disconnect(struct usb_interface *interface) +{ + struct trancevibrator *dev; + + dev = usb_get_intfdata (interface); + usb_set_intfdata(interface, NULL); + usb_put_dev(dev->udev); + kfree(dev); +} + +/* USB subsystem object */ +static struct usb_driver tv_driver = { + .name = "trancevibrator", + .probe = tv_probe, + .disconnect = tv_disconnect, + .id_table = id_table, + .dev_groups = tv_groups, +}; + +module_usb_driver(tv_driver); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/misc/usb251xb.c b/drivers/usb/misc/usb251xb.c new file mode 100644 index 0000000000..7da404f55a --- /dev/null +++ b/drivers/usb/misc/usb251xb.c @@ -0,0 +1,756 @@ +// 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/regulator/consumer.h> +#include <linux/slab.h> + +/* Internal Register Set Addresses & Default Values acc. to DS00001692C */ +#define USB251XB_ADDR_VENDOR_ID_LSB 0x00 +#define USB251XB_ADDR_VENDOR_ID_MSB 0x01 +#define USB251XB_DEF_VENDOR_ID 0x0424 + +#define USB251XB_ADDR_PRODUCT_ID_LSB 0x02 +#define USB251XB_ADDR_PRODUCT_ID_MSB 0x03 + +#define USB251XB_ADDR_DEVICE_ID_LSB 0x04 +#define USB251XB_ADDR_DEVICE_ID_MSB 0x05 +#define USB251XB_DEF_DEVICE_ID 0x0BB3 + +#define USB251XB_ADDR_CONFIG_DATA_1 0x06 +#define USB251XB_DEF_CONFIG_DATA_1 0x9B +#define USB251XB_ADDR_CONFIG_DATA_2 0x07 +#define USB251XB_DEF_CONFIG_DATA_2 0x20 +#define USB251XB_ADDR_CONFIG_DATA_3 0x08 +#define USB251XB_DEF_CONFIG_DATA_3 0x02 + +#define USB251XB_ADDR_NON_REMOVABLE_DEVICES 0x09 +#define USB251XB_DEF_NON_REMOVABLE_DEVICES 0x00 + +#define USB251XB_ADDR_PORT_DISABLE_SELF 0x0A +#define USB251XB_DEF_PORT_DISABLE_SELF 0x00 +#define USB251XB_ADDR_PORT_DISABLE_BUS 0x0B +#define USB251XB_DEF_PORT_DISABLE_BUS 0x00 + +#define USB251XB_ADDR_MAX_POWER_SELF 0x0C +#define USB251XB_DEF_MAX_POWER_SELF 0x01 +#define USB251XB_ADDR_MAX_POWER_BUS 0x0D +#define USB251XB_DEF_MAX_POWER_BUS 0x32 + +#define USB251XB_ADDR_MAX_CURRENT_SELF 0x0E +#define USB251XB_DEF_MAX_CURRENT_SELF 0x01 +#define USB251XB_ADDR_MAX_CURRENT_BUS 0x0F +#define USB251XB_DEF_MAX_CURRENT_BUS 0x32 + +#define USB251XB_ADDR_POWER_ON_TIME 0x10 +#define USB251XB_DEF_POWER_ON_TIME 0x32 + +#define USB251XB_ADDR_LANGUAGE_ID_HIGH 0x11 +#define USB251XB_ADDR_LANGUAGE_ID_LOW 0x12 +#define USB251XB_DEF_LANGUAGE_ID 0x0000 + +#define USB251XB_STRING_BUFSIZE 62 +#define USB251XB_ADDR_MANUFACTURER_STRING_LEN 0x13 +#define USB251XB_ADDR_MANUFACTURER_STRING 0x16 +#define USB251XB_DEF_MANUFACTURER_STRING "Microchip" + +#define USB251XB_ADDR_PRODUCT_STRING_LEN 0x14 +#define USB251XB_ADDR_PRODUCT_STRING 0x54 + +#define USB251XB_ADDR_SERIAL_STRING_LEN 0x15 +#define USB251XB_ADDR_SERIAL_STRING 0x92 +#define USB251XB_DEF_SERIAL_STRING "" + +#define USB251XB_ADDR_BATTERY_CHARGING_ENABLE 0xD0 +#define USB251XB_DEF_BATTERY_CHARGING_ENABLE 0x00 + +#define USB251XB_ADDR_BOOST_UP 0xF6 +#define USB251XB_DEF_BOOST_UP 0x00 +#define USB251XB_ADDR_BOOST_57 0xF7 +#define USB251XB_DEF_BOOST_57 0x00 +#define USB251XB_ADDR_BOOST_14 0xF8 +#define USB251XB_DEF_BOOST_14 0x00 + +#define USB251XB_ADDR_PORT_SWAP 0xFA +#define USB251XB_DEF_PORT_SWAP 0x00 + +#define USB251XB_ADDR_PORT_MAP_12 0xFB +#define USB251XB_DEF_PORT_MAP_12 0x00 +#define USB251XB_ADDR_PORT_MAP_34 0xFC +#define USB251XB_DEF_PORT_MAP_34 0x00 /* USB251{3B/i,4B/i,7/i} only */ +#define USB251XB_ADDR_PORT_MAP_56 0xFD +#define USB251XB_DEF_PORT_MAP_56 0x00 /* USB2517/i only */ +#define USB251XB_ADDR_PORT_MAP_7 0xFE +#define USB251XB_DEF_PORT_MAP_7 0x00 /* USB2517/i only */ + +#define USB251XB_ADDR_STATUS_COMMAND 0xFF +#define USB251XB_STATUS_COMMAND_SMBUS_DOWN 0x04 +#define USB251XB_STATUS_COMMAND_RESET 0x02 +#define USB251XB_STATUS_COMMAND_ATTACH 0x01 + +#define USB251XB_I2C_REG_SZ 0x100 +#define USB251XB_I2C_WRITE_SZ 0x10 + +#define DRIVER_NAME "usb251xb" +#define DRIVER_DESC "Microchip USB 2.0 Hi-Speed Hub Controller" + +struct usb251xb { + struct device *dev; + struct i2c_client *i2c; + struct regulator *vdd; + u8 skip_config; + struct gpio_desc *gpio_reset; + u16 vendor_id; + u16 product_id; + u16 device_id; + u8 conf_data1; + u8 conf_data2; + u8 conf_data3; + u8 non_rem_dev; + u8 port_disable_sp; + u8 port_disable_bp; + u8 max_power_sp; + u8 max_power_bp; + u8 max_current_sp; + u8 max_current_bp; + u8 power_on_time; + u16 lang_id; + u8 manufacturer_len; + u8 product_len; + u8 serial_len; + char manufacturer[USB251XB_STRING_BUFSIZE]; + char product[USB251XB_STRING_BUFSIZE]; + char serial[USB251XB_STRING_BUFSIZE]; + u8 bat_charge_en; + u8 boost_up; + u8 boost_57; + u8 boost_14; + u8 port_swap; + u8 port_map12; + u8 port_map34; + u8 port_map56; + u8 port_map7; + u8 status; +}; + +struct usb251xb_data { + u16 product_id; + u8 port_cnt; + bool led_support; + bool bat_support; + char product_str[USB251XB_STRING_BUFSIZE / 2]; /* ASCII string */ +}; + +static const struct usb251xb_data usb2422_data = { + .product_id = 0x2422, + .port_cnt = 2, + .led_support = false, + .bat_support = true, + .product_str = "USB2422", +}; + +static const struct usb251xb_data usb2512b_data = { + .product_id = 0x2512, + .port_cnt = 2, + .led_support = false, + .bat_support = true, + .product_str = "USB2512B", +}; + +static const struct usb251xb_data usb2512bi_data = { + .product_id = 0x2512, + .port_cnt = 2, + .led_support = false, + .bat_support = true, + .product_str = "USB2512Bi", +}; + +static const struct usb251xb_data usb2513b_data = { + .product_id = 0x2513, + .port_cnt = 3, + .led_support = false, + .bat_support = true, + .product_str = "USB2513B", +}; + +static const struct usb251xb_data usb2513bi_data = { + .product_id = 0x2513, + .port_cnt = 3, + .led_support = false, + .bat_support = true, + .product_str = "USB2513Bi", +}; + +static const struct usb251xb_data usb2514b_data = { + .product_id = 0x2514, + .port_cnt = 4, + .led_support = false, + .bat_support = true, + .product_str = "USB2514B", +}; + +static const struct usb251xb_data usb2514bi_data = { + .product_id = 0x2514, + .port_cnt = 4, + .led_support = false, + .bat_support = true, + .product_str = "USB2514Bi", +}; + +static const struct usb251xb_data usb2517_data = { + .product_id = 0x2517, + .port_cnt = 7, + .led_support = true, + .bat_support = false, + .product_str = "USB2517", +}; + +static const struct usb251xb_data usb2517i_data = { + .product_id = 0x2517, + .port_cnt = 7, + .led_support = true, + .bat_support = false, + .product_str = "USB2517i", +}; + +#ifdef CONFIG_GPIOLIB +static int usb251xb_check_dev_children(struct device *dev, void *child) +{ + if (dev->type == &i2c_adapter_type) { + return device_for_each_child(dev, child, + usb251xb_check_dev_children); + } + + return (dev == child); +} + +static int usb251x_check_gpio_chip(struct usb251xb *hub) +{ + struct gpio_chip *gc = gpiod_to_chip(hub->gpio_reset); + struct i2c_adapter *adap = hub->i2c->adapter; + int ret; + + if (!hub->gpio_reset) + return 0; + + if (!gc) + return -EINVAL; + + ret = usb251xb_check_dev_children(&adap->dev, gc->parent); + if (ret) { + dev_err(hub->dev, "Reset GPIO chip is at the same i2c-bus\n"); + return -EINVAL; + } + + return 0; +} +#else +static int usb251x_check_gpio_chip(struct usb251xb *hub) +{ + return 0; +} +#endif + +static void usb251xb_reset(struct usb251xb *hub) +{ + if (!hub->gpio_reset) + return; + + i2c_lock_bus(hub->i2c->adapter, I2C_LOCK_SEGMENT); + + gpiod_set_value_cansleep(hub->gpio_reset, 1); + usleep_range(1, 10); /* >=1us RESET_N asserted */ + gpiod_set_value_cansleep(hub->gpio_reset, 0); + + /* wait for hub recovery/stabilization */ + usleep_range(500, 750); /* >=500us after RESET_N deasserted */ + + i2c_unlock_bus(hub->i2c->adapter, I2C_LOCK_SEGMENT); +} + +static int usb251xb_connect(struct usb251xb *hub) +{ + struct device *dev = hub->dev; + int err, i; + char i2c_wb[USB251XB_I2C_REG_SZ]; + + memset(i2c_wb, 0, USB251XB_I2C_REG_SZ); + + if (hub->skip_config) { + dev_info(dev, "Skip hub configuration, only attach.\n"); + i2c_wb[0] = 0x01; + i2c_wb[1] = USB251XB_STATUS_COMMAND_ATTACH; + + usb251xb_reset(hub); + + err = i2c_smbus_write_i2c_block_data(hub->i2c, + USB251XB_ADDR_STATUS_COMMAND, 2, i2c_wb); + if (err) { + dev_err(dev, "attaching hub failed: %d\n", err); + return err; + } + return 0; + } + + i2c_wb[USB251XB_ADDR_VENDOR_ID_MSB] = (hub->vendor_id >> 8) & 0xFF; + i2c_wb[USB251XB_ADDR_VENDOR_ID_LSB] = hub->vendor_id & 0xFF; + i2c_wb[USB251XB_ADDR_PRODUCT_ID_MSB] = (hub->product_id >> 8) & 0xFF; + i2c_wb[USB251XB_ADDR_PRODUCT_ID_LSB] = hub->product_id & 0xFF; + i2c_wb[USB251XB_ADDR_DEVICE_ID_MSB] = (hub->device_id >> 8) & 0xFF; + i2c_wb[USB251XB_ADDR_DEVICE_ID_LSB] = hub->device_id & 0xFF; + i2c_wb[USB251XB_ADDR_CONFIG_DATA_1] = hub->conf_data1; + i2c_wb[USB251XB_ADDR_CONFIG_DATA_2] = hub->conf_data2; + i2c_wb[USB251XB_ADDR_CONFIG_DATA_3] = hub->conf_data3; + i2c_wb[USB251XB_ADDR_NON_REMOVABLE_DEVICES] = hub->non_rem_dev; + i2c_wb[USB251XB_ADDR_PORT_DISABLE_SELF] = hub->port_disable_sp; + i2c_wb[USB251XB_ADDR_PORT_DISABLE_BUS] = hub->port_disable_bp; + i2c_wb[USB251XB_ADDR_MAX_POWER_SELF] = hub->max_power_sp; + i2c_wb[USB251XB_ADDR_MAX_POWER_BUS] = hub->max_power_bp; + i2c_wb[USB251XB_ADDR_MAX_CURRENT_SELF] = hub->max_current_sp; + i2c_wb[USB251XB_ADDR_MAX_CURRENT_BUS] = hub->max_current_bp; + i2c_wb[USB251XB_ADDR_POWER_ON_TIME] = hub->power_on_time; + i2c_wb[USB251XB_ADDR_LANGUAGE_ID_HIGH] = (hub->lang_id >> 8) & 0xFF; + i2c_wb[USB251XB_ADDR_LANGUAGE_ID_LOW] = hub->lang_id & 0xFF; + i2c_wb[USB251XB_ADDR_MANUFACTURER_STRING_LEN] = hub->manufacturer_len; + i2c_wb[USB251XB_ADDR_PRODUCT_STRING_LEN] = hub->product_len; + i2c_wb[USB251XB_ADDR_SERIAL_STRING_LEN] = hub->serial_len; + memcpy(&i2c_wb[USB251XB_ADDR_MANUFACTURER_STRING], hub->manufacturer, + USB251XB_STRING_BUFSIZE); + memcpy(&i2c_wb[USB251XB_ADDR_SERIAL_STRING], hub->serial, + USB251XB_STRING_BUFSIZE); + memcpy(&i2c_wb[USB251XB_ADDR_PRODUCT_STRING], hub->product, + USB251XB_STRING_BUFSIZE); + i2c_wb[USB251XB_ADDR_BATTERY_CHARGING_ENABLE] = hub->bat_charge_en; + i2c_wb[USB251XB_ADDR_BOOST_UP] = hub->boost_up; + i2c_wb[USB251XB_ADDR_BOOST_57] = hub->boost_57; + i2c_wb[USB251XB_ADDR_BOOST_14] = hub->boost_14; + i2c_wb[USB251XB_ADDR_PORT_SWAP] = hub->port_swap; + i2c_wb[USB251XB_ADDR_PORT_MAP_12] = hub->port_map12; + i2c_wb[USB251XB_ADDR_PORT_MAP_34] = hub->port_map34; + i2c_wb[USB251XB_ADDR_PORT_MAP_56] = hub->port_map56; + i2c_wb[USB251XB_ADDR_PORT_MAP_7] = hub->port_map7; + i2c_wb[USB251XB_ADDR_STATUS_COMMAND] = USB251XB_STATUS_COMMAND_ATTACH; + + usb251xb_reset(hub); + + /* write registers */ + for (i = 0; i < (USB251XB_I2C_REG_SZ / USB251XB_I2C_WRITE_SZ); i++) { + int offset = i * USB251XB_I2C_WRITE_SZ; + char wbuf[USB251XB_I2C_WRITE_SZ + 1]; + + /* The first data byte transferred tells the hub how many data + * bytes will follow (byte count). + */ + wbuf[0] = USB251XB_I2C_WRITE_SZ; + memcpy(&wbuf[1], &i2c_wb[offset], USB251XB_I2C_WRITE_SZ); + + dev_dbg(dev, "writing %d byte block %d to 0x%02X\n", + USB251XB_I2C_WRITE_SZ, i, offset); + + err = i2c_smbus_write_i2c_block_data(hub->i2c, offset, + USB251XB_I2C_WRITE_SZ + 1, + wbuf); + if (err) + goto out_err; + } + + dev_info(dev, "Hub configuration was successful.\n"); + return 0; + +out_err: + dev_err(dev, "configuring block %d failed: %d\n", i, err); + return err; +} + +static void usb251xb_get_ports_field(struct usb251xb *hub, + const char *prop_name, u8 port_cnt, + bool ds_only, u8 *fld) +{ + struct device *dev = hub->dev; + struct property *prop; + const __be32 *p; + u32 port; + + of_property_for_each_u32(dev->of_node, prop_name, prop, p, port) { + if ((port >= ds_only ? 1 : 0) && (port <= port_cnt)) + *fld |= BIT(port); + else + dev_warn(dev, "port %u doesn't exist\n", port); + } +} + +static int usb251xb_get_ofdata(struct usb251xb *hub, + const struct usb251xb_data *data) +{ + struct device *dev = hub->dev; + struct device_node *np = dev->of_node; + int len; + u32 property_u32 = 0; + const char *cproperty_char; + char str[USB251XB_STRING_BUFSIZE / 2]; + + if (!np) { + dev_err(dev, "failed to get ofdata\n"); + return -ENODEV; + } + + 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) { + dev_err(dev, "failed to get ofdata: %d\n", err); + return err; + } + } + + /* + * usb251x SMBus-slave SCL lane is muxed with CFG_SEL0 pin. So if anyone + * tries to work with the bus at the moment the hub reset is released, + * it may cause an invalid config being latched by usb251x. Particularly + * one of the config modes makes the hub loading a default registers + * value without SMBus-slave interface activation. If the hub + * accidentally gets this mode, this will cause the driver SMBus- + * functions failure. Normally we could just lock the SMBus-segment the + * hub i2c-interface resides for the device-specific reset timing. But + * the GPIO controller, which is used to handle the hub reset, might be + * placed at the same i2c-bus segment. In this case an error should be + * returned since we can't safely use the GPIO controller to clear the + * reset state (it may affect the hub configuration) and we can't lock + * the i2c-bus segment (it will cause a deadlock). + */ + err = usb251x_check_gpio_chip(hub); + if (err) + return err; + + hub->vdd = devm_regulator_get(dev, "vdd"); + if (IS_ERR(hub->vdd)) + return PTR_ERR(hub->vdd); + + err = regulator_enable(hub->vdd); + if (err) + return err; + + err = devm_add_action_or_reset(dev, + usb251xb_regulator_disable_action, hub); + if (err) + return err; + + err = usb251xb_connect(hub); + if (err) { + dev_err(dev, "Failed to connect hub (%d)\n", err); + return err; + } + + dev_info(dev, "Hub probed successfully\n"); + + return 0; +} + +static int usb251xb_i2c_probe(struct i2c_client *i2c) +{ + struct usb251xb *hub; + + hub = devm_kzalloc(&i2c->dev, sizeof(struct usb251xb), GFP_KERNEL); + if (!hub) + return -ENOMEM; + + i2c_set_clientdata(i2c, hub); + hub->dev = &i2c->dev; + hub->i2c = i2c; + + return usb251xb_probe(hub); +} + +static int __maybe_unused usb251xb_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct usb251xb *hub = i2c_get_clientdata(client); + + return regulator_disable(hub->vdd); +} + +static int __maybe_unused usb251xb_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct usb251xb *hub = i2c_get_clientdata(client); + int err; + + err = regulator_enable(hub->vdd); + if (err) + return err; + + return usb251xb_connect(hub); +} + +static SIMPLE_DEV_PM_OPS(usb251xb_pm_ops, usb251xb_suspend, usb251xb_resume); + +static const struct i2c_device_id usb251xb_id[] = { + { "usb2422", 0 }, + { "usb2512b", 0 }, + { "usb2512bi", 0 }, + { "usb2513b", 0 }, + { "usb2513bi", 0 }, + { "usb2514b", 0 }, + { "usb2514bi", 0 }, + { "usb2517", 0 }, + { "usb2517i", 0 }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(i2c, usb251xb_id); + +static struct i2c_driver usb251xb_i2c_driver = { + .driver = { + .name = DRIVER_NAME, + .of_match_table = usb251xb_of_match, + .pm = &usb251xb_pm_ops, + }, + .probe = usb251xb_i2c_probe, + .id_table = usb251xb_id, +}; + +module_i2c_driver(usb251xb_i2c_driver); + +MODULE_AUTHOR("Richard Leitner <richard.leitner@skidata.com>"); +MODULE_DESCRIPTION("USB251x/xBi USB 2.0 Hub Controller Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/misc/usb3503.c b/drivers/usb/misc/usb3503.c new file mode 100644 index 0000000000..7276507793 --- /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[] = { + { USB3503_I2C_NAME, 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, usb3503_id); + +#ifdef CONFIG_OF +static const struct of_device_id usb3503_of_match[] = { + { .compatible = "smsc,usb3503", }, + { .compatible = "smsc,usb3503a", }, + { .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_new = 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 0000000000..065e269ba4 --- /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[] = { + { "usb4604", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, usb4604_id); + +#ifdef CONFIG_OF +static const struct of_device_id usb4604_of_match[] = { + { .compatible = "smsc,usb4604" }, + {} +}; +MODULE_DEVICE_TABLE(of, usb4604_of_match); +#endif + +static struct i2c_driver usb4604_i2c_driver = { + .driver = { + .name = "usb4604", + .pm = pm_ptr(&usb4604_i2c_pm_ops), + .of_match_table = of_match_ptr(usb4604_of_match), + }, + .probe = usb4604_i2c_probe, + .id_table = usb4604_id, +}; +module_i2c_driver(usb4604_i2c_driver); + +MODULE_DESCRIPTION("USB4604 USB HUB driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/usb/misc/usblcd.c b/drivers/usb/misc/usblcd.c new file mode 100644 index 0000000000..bb546f624a --- /dev/null +++ b/drivers/usb/misc/usblcd.c @@ -0,0 +1,450 @@ +// SPDX-License-Identifier: GPL-2.0 +/***************************************************************************** + * USBLCD Kernel Driver * + * Version 1.05 * + * (C) 2005 Georges Toth <g.toth@e-biz.lu> * + * * + * This file is licensed under the GPL. See COPYING in the package. * + * Based on usb-skeleton.c 2.0 by Greg Kroah-Hartman (greg@kroah.com) * + * * + * * + * 28.02.05 Complete rewrite of the original usblcd.c driver, * + * based on usb_skeleton.c. * + * This new driver allows more than one USB-LCD to be connected * + * and controlled, at once * + *****************************************************************************/ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include <linux/mutex.h> +#include <linux/rwsem.h> +#include <linux/uaccess.h> +#include <linux/usb.h> + +#define DRIVER_VERSION "USBLCD Driver Version 1.05" + +#define USBLCD_MINOR 144 + +#define IOCTL_GET_HARD_VERSION 1 +#define IOCTL_GET_DRV_VERSION 2 + + +static const struct usb_device_id id_table[] = { + { .idVendor = 0x10D2, .match_flags = USB_DEVICE_ID_MATCH_VENDOR, }, + { }, +}; +MODULE_DEVICE_TABLE(usb, id_table); + +struct usb_lcd { + struct usb_device *udev; /* init: probe_lcd */ + struct usb_interface *interface; /* the interface for + this device */ + unsigned char *bulk_in_buffer; /* the buffer to receive + data */ + size_t bulk_in_size; /* the size of the + receive buffer */ + __u8 bulk_in_endpointAddr; /* the address of the + bulk in endpoint */ + __u8 bulk_out_endpointAddr; /* the address of the + bulk out endpoint */ + struct kref kref; + struct semaphore limit_sem; /* to stop writes at + full throttle from + using up all RAM */ + struct usb_anchor submitted; /* URBs to wait for + before suspend */ + struct rw_semaphore io_rwsem; + unsigned long disconnected:1; +}; +#define to_lcd_dev(d) container_of(d, struct usb_lcd, kref) + +#define USB_LCD_CONCURRENT_WRITES 5 + +static struct usb_driver lcd_driver; + + +static void lcd_delete(struct kref *kref) +{ + struct usb_lcd *dev = to_lcd_dev(kref); + + usb_put_dev(dev->udev); + kfree(dev->bulk_in_buffer); + kfree(dev); +} + + +static int lcd_open(struct inode *inode, struct file *file) +{ + struct usb_lcd *dev; + struct usb_interface *interface; + int subminor, r; + + subminor = iminor(inode); + + interface = usb_find_interface(&lcd_driver, subminor); + if (!interface) { + pr_err("USBLCD: %s - error, can't find device for minor %d\n", + __func__, subminor); + return -ENODEV; + } + + dev = usb_get_intfdata(interface); + + /* increment our usage count for the device */ + kref_get(&dev->kref); + + /* grab a power reference */ + r = usb_autopm_get_interface(interface); + if (r < 0) { + kref_put(&dev->kref, lcd_delete); + return r; + } + + /* save our object in the file's private structure */ + file->private_data = dev; + + return 0; +} + +static int lcd_release(struct inode *inode, struct file *file) +{ + struct usb_lcd *dev; + + dev = file->private_data; + if (dev == NULL) + return -ENODEV; + + /* decrement the count on our device */ + usb_autopm_put_interface(dev->interface); + kref_put(&dev->kref, lcd_delete); + return 0; +} + +static ssize_t lcd_read(struct file *file, char __user * buffer, + size_t count, loff_t *ppos) +{ + struct usb_lcd *dev; + int retval = 0; + int bytes_read; + + dev = file->private_data; + + down_read(&dev->io_rwsem); + + if (dev->disconnected) { + retval = -ENODEV; + goto out_up_io; + } + + /* do a blocking bulk read to get data from the device */ + retval = usb_bulk_msg(dev->udev, + usb_rcvbulkpipe(dev->udev, + dev->bulk_in_endpointAddr), + dev->bulk_in_buffer, + min(dev->bulk_in_size, count), + &bytes_read, 10000); + + /* if the read was successful, copy the data to userspace */ + if (!retval) { + if (copy_to_user(buffer, dev->bulk_in_buffer, bytes_read)) + retval = -EFAULT; + else + retval = bytes_read; + } + +out_up_io: + up_read(&dev->io_rwsem); + + return retval; +} + +static long lcd_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct usb_lcd *dev; + u16 bcdDevice; + char buf[30]; + + dev = file->private_data; + if (dev == NULL) + return -ENODEV; + + switch (cmd) { + case IOCTL_GET_HARD_VERSION: + bcdDevice = le16_to_cpu((dev->udev)->descriptor.bcdDevice); + sprintf(buf, "%1d%1d.%1d%1d", + (bcdDevice & 0xF000)>>12, + (bcdDevice & 0xF00)>>8, + (bcdDevice & 0xF0)>>4, + (bcdDevice & 0xF)); + if (copy_to_user((void __user *)arg, buf, strlen(buf)) != 0) + return -EFAULT; + break; + case IOCTL_GET_DRV_VERSION: + sprintf(buf, DRIVER_VERSION); + if (copy_to_user((void __user *)arg, buf, strlen(buf)) != 0) + return -EFAULT; + break; + default: + return -ENOTTY; + } + + return 0; +} + +static void lcd_write_bulk_callback(struct urb *urb) +{ + struct usb_lcd *dev; + int status = urb->status; + + dev = urb->context; + + /* sync/async unlink faults aren't errors */ + if (status && + !(status == -ENOENT || + status == -ECONNRESET || + status == -ESHUTDOWN)) { + dev_dbg(&dev->interface->dev, + "nonzero write bulk status received: %d\n", status); + } + + /* free up our allocated buffer */ + usb_free_coherent(urb->dev, urb->transfer_buffer_length, + urb->transfer_buffer, urb->transfer_dma); + up(&dev->limit_sem); +} + +static ssize_t lcd_write(struct file *file, const char __user * user_buffer, + size_t count, loff_t *ppos) +{ + struct usb_lcd *dev; + int retval = 0, r; + struct urb *urb = NULL; + char *buf = NULL; + + dev = file->private_data; + + /* verify that we actually have some data to write */ + if (count == 0) + goto exit; + + r = down_interruptible(&dev->limit_sem); + if (r < 0) + return -EINTR; + + down_read(&dev->io_rwsem); + + if (dev->disconnected) { + retval = -ENODEV; + goto err_up_io; + } + + /* create a urb, and a buffer for it, and copy the data to the urb */ + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) { + retval = -ENOMEM; + goto err_up_io; + } + + buf = usb_alloc_coherent(dev->udev, count, GFP_KERNEL, + &urb->transfer_dma); + if (!buf) { + retval = -ENOMEM; + goto error; + } + + if (copy_from_user(buf, user_buffer, count)) { + retval = -EFAULT; + goto error; + } + + /* initialize the urb properly */ + usb_fill_bulk_urb(urb, dev->udev, + usb_sndbulkpipe(dev->udev, + dev->bulk_out_endpointAddr), + buf, count, lcd_write_bulk_callback, dev); + urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + usb_anchor_urb(urb, &dev->submitted); + + /* send the data out the bulk port */ + retval = usb_submit_urb(urb, GFP_KERNEL); + if (retval) { + dev_err(&dev->udev->dev, + "%s - failed submitting write urb, error %d\n", + __func__, retval); + goto error_unanchor; + } + + /* release our reference to this urb, + the USB core will eventually free it entirely */ + usb_free_urb(urb); + + up_read(&dev->io_rwsem); +exit: + return count; +error_unanchor: + usb_unanchor_urb(urb); +error: + usb_free_coherent(dev->udev, count, buf, urb->transfer_dma); + usb_free_urb(urb); +err_up_io: + up_read(&dev->io_rwsem); + up(&dev->limit_sem); + return retval; +} + +static const struct file_operations lcd_fops = { + .owner = THIS_MODULE, + .read = lcd_read, + .write = lcd_write, + .open = lcd_open, + .unlocked_ioctl = lcd_ioctl, + .release = lcd_release, + .llseek = noop_llseek, +}; + +/* + * usb class driver info in order to get a minor number from the usb core, + * and to have the device registered with the driver core + */ +static struct usb_class_driver lcd_class = { + .name = "lcd%d", + .fops = &lcd_fops, + .minor_base = USBLCD_MINOR, +}; + +static int lcd_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + struct usb_lcd *dev = NULL; + struct usb_endpoint_descriptor *bulk_in, *bulk_out; + int i; + int retval; + + /* allocate memory for our device state and initialize it */ + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + + kref_init(&dev->kref); + sema_init(&dev->limit_sem, USB_LCD_CONCURRENT_WRITES); + init_rwsem(&dev->io_rwsem); + init_usb_anchor(&dev->submitted); + + dev->udev = usb_get_dev(interface_to_usbdev(interface)); + dev->interface = interface; + + if (le16_to_cpu(dev->udev->descriptor.idProduct) != 0x0001) { + dev_warn(&interface->dev, "USBLCD model not supported.\n"); + retval = -ENODEV; + goto error; + } + + /* set up the endpoint information */ + /* use only the first bulk-in and bulk-out endpoints */ + retval = usb_find_common_endpoints(interface->cur_altsetting, + &bulk_in, &bulk_out, NULL, NULL); + if (retval) { + dev_err(&interface->dev, + "Could not find both bulk-in and bulk-out endpoints\n"); + goto error; + } + + dev->bulk_in_size = usb_endpoint_maxp(bulk_in); + dev->bulk_in_endpointAddr = bulk_in->bEndpointAddress; + dev->bulk_in_buffer = kmalloc(dev->bulk_in_size, GFP_KERNEL); + if (!dev->bulk_in_buffer) { + retval = -ENOMEM; + goto error; + } + + dev->bulk_out_endpointAddr = bulk_out->bEndpointAddress; + + /* save our data pointer in this interface device */ + usb_set_intfdata(interface, dev); + + /* we can register the device now, as it is ready */ + retval = usb_register_dev(interface, &lcd_class); + if (retval) { + /* something prevented us from registering this driver */ + dev_err(&interface->dev, + "Not able to get a minor for this device.\n"); + goto error; + } + + i = le16_to_cpu(dev->udev->descriptor.bcdDevice); + + dev_info(&interface->dev, "USBLCD Version %1d%1d.%1d%1d found " + "at address %d\n", (i & 0xF000)>>12, (i & 0xF00)>>8, + (i & 0xF0)>>4, (i & 0xF), dev->udev->devnum); + + /* let the user know what node this device is now attached to */ + dev_info(&interface->dev, "USB LCD device now attached to USBLCD-%d\n", + interface->minor); + return 0; + +error: + kref_put(&dev->kref, lcd_delete); + return retval; +} + +static void lcd_draw_down(struct usb_lcd *dev) +{ + int time; + + time = usb_wait_anchor_empty_timeout(&dev->submitted, 1000); + if (!time) + usb_kill_anchored_urbs(&dev->submitted); +} + +static int lcd_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct usb_lcd *dev = usb_get_intfdata(intf); + + if (!dev) + return 0; + lcd_draw_down(dev); + return 0; +} + +static int lcd_resume(struct usb_interface *intf) +{ + return 0; +} + +static void lcd_disconnect(struct usb_interface *interface) +{ + struct usb_lcd *dev = usb_get_intfdata(interface); + int minor = interface->minor; + + /* give back our minor */ + usb_deregister_dev(interface, &lcd_class); + + down_write(&dev->io_rwsem); + dev->disconnected = 1; + up_write(&dev->io_rwsem); + + usb_kill_anchored_urbs(&dev->submitted); + + /* decrement our usage count */ + kref_put(&dev->kref, lcd_delete); + + dev_info(&interface->dev, "USB LCD #%d now disconnected\n", minor); +} + +static struct usb_driver lcd_driver = { + .name = "usblcd", + .probe = lcd_probe, + .disconnect = lcd_disconnect, + .suspend = lcd_suspend, + .resume = lcd_resume, + .id_table = id_table, + .supports_autosuspend = 1, +}; + +module_usb_driver(lcd_driver); + +MODULE_AUTHOR("Georges Toth <g.toth@e-biz.lu>"); +MODULE_DESCRIPTION(DRIVER_VERSION); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/misc/usbsevseg.c b/drivers/usb/misc/usbsevseg.c new file mode 100644 index 0000000000..546deff754 --- /dev/null +++ b/drivers/usb/misc/usbsevseg.c @@ -0,0 +1,396 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * USB 7 Segment Driver + * + * Copyright (C) 2008 Harrison Metzger <harrisonmetz@gmail.com> + * Based on usbled.c by Greg Kroah-Hartman (greg@kroah.com) + */ + +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/string.h> +#include <linux/usb.h> + + +#define DRIVER_AUTHOR "Harrison Metzger <harrisonmetz@gmail.com>" +#define DRIVER_DESC "USB 7 Segment Driver" + +#define VENDOR_ID 0x0fc5 +#define PRODUCT_ID 0x1227 +#define MAXLEN 8 + +/* table of devices that work with this driver */ +static const struct usb_device_id id_table[] = { + { USB_DEVICE(VENDOR_ID, PRODUCT_ID) }, + { }, +}; +MODULE_DEVICE_TABLE(usb, id_table); + +/* the different text display modes the device is capable of */ +static const char *display_textmodes[] = {"raw", "hex", "ascii"}; + +struct usb_sevsegdev { + struct usb_device *udev; + struct usb_interface *intf; + + u8 powered; + u8 mode_msb; + u8 mode_lsb; + u8 decimals[MAXLEN]; + u8 textmode; + u8 text[MAXLEN]; + u16 textlength; + + u8 shadow_power; /* for PM */ + u8 has_interface_pm; +}; + +/* sysfs_streq can't replace this completely + * If the device was in hex mode, and the user wanted a 0, + * if str commands are used, we would assume the end of string + * so mem commands are used. + */ +static inline size_t my_memlen(const char *buf, size_t count) +{ + if (count > 0 && buf[count-1] == '\n') + return count - 1; + else + return count; +} + +static void update_display_powered(struct usb_sevsegdev *mydev) +{ + int rc; + + if (mydev->powered && !mydev->has_interface_pm) { + rc = usb_autopm_get_interface(mydev->intf); + if (rc < 0) + return; + mydev->has_interface_pm = 1; + } + + if (mydev->shadow_power != 1) + return; + + rc = usb_control_msg_send(mydev->udev, 0, 0x12, 0x48, + (80 * 0x100) + 10, /* (power mode) */ + (0x00 * 0x100) + (mydev->powered ? 1 : 0), + NULL, 0, 2000, GFP_KERNEL); + if (rc < 0) + dev_dbg(&mydev->udev->dev, "power retval = %d\n", rc); + + if (!mydev->powered && mydev->has_interface_pm) { + usb_autopm_put_interface(mydev->intf); + mydev->has_interface_pm = 0; + } +} + +static void update_display_mode(struct usb_sevsegdev *mydev) +{ + int rc; + + if(mydev->shadow_power != 1) + return; + + rc = usb_control_msg_send(mydev->udev, 0, 0x12, 0x48, + (82 * 0x100) + 10, /* (set mode) */ + (mydev->mode_msb * 0x100) + mydev->mode_lsb, + NULL, 0, 2000, GFP_NOIO); + + if (rc < 0) + dev_dbg(&mydev->udev->dev, "mode retval = %d\n", rc); +} + +static void update_display_visual(struct usb_sevsegdev *mydev, gfp_t mf) +{ + int rc; + int i; + unsigned char buffer[MAXLEN] = {0}; + u8 decimals = 0; + + if(mydev->shadow_power != 1) + return; + + /* The device is right to left, where as you write left to right */ + for (i = 0; i < mydev->textlength; i++) + buffer[i] = mydev->text[mydev->textlength-1-i]; + + rc = usb_control_msg_send(mydev->udev, 0, 0x12, 0x48, + (85 * 0x100) + 10, /* (write text) */ + (0 * 0x100) + mydev->textmode, /* mode */ + &buffer, mydev->textlength, 2000, mf); + + if (rc < 0) + dev_dbg(&mydev->udev->dev, "write retval = %d\n", rc); + + /* The device is right to left, where as you write left to right */ + for (i = 0; i < sizeof(mydev->decimals); i++) + decimals |= mydev->decimals[i] << i; + + rc = usb_control_msg_send(mydev->udev, 0, 0x12, 0x48, + (86 * 0x100) + 10, /* (set decimal) */ + (0 * 0x100) + decimals, /* decimals */ + NULL, 0, 2000, mf); + + if (rc < 0) + dev_dbg(&mydev->udev->dev, "decimal retval = %d\n", rc); +} + +#define MYDEV_ATTR_SIMPLE_UNSIGNED(name, update_fcn) \ +static ssize_t name##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + struct usb_interface *intf = to_usb_interface(dev); \ + struct usb_sevsegdev *mydev = usb_get_intfdata(intf); \ + \ + return sprintf(buf, "%u\n", mydev->name); \ +} \ + \ +static ssize_t name##_store(struct device *dev, \ + struct device_attribute *attr, const char *buf, size_t count) \ +{ \ + struct usb_interface *intf = to_usb_interface(dev); \ + struct usb_sevsegdev *mydev = usb_get_intfdata(intf); \ + \ + mydev->name = simple_strtoul(buf, NULL, 10); \ + update_fcn(mydev); \ + \ + return count; \ +} \ +static DEVICE_ATTR_RW(name); + +static ssize_t text_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct usb_sevsegdev *mydev = usb_get_intfdata(intf); + + return sysfs_emit(buf, "%s\n", mydev->text); +} + +static ssize_t text_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct usb_sevsegdev *mydev = usb_get_intfdata(intf); + size_t end = my_memlen(buf, count); + + if (end > sizeof(mydev->text)) + return -EINVAL; + + memset(mydev->text, 0, sizeof(mydev->text)); + mydev->textlength = end; + + if (end > 0) + memcpy(mydev->text, buf, end); + + update_display_visual(mydev, GFP_KERNEL); + return count; +} + +static DEVICE_ATTR_RW(text); + +static ssize_t decimals_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct usb_sevsegdev *mydev = usb_get_intfdata(intf); + int i; + int pos; + + for (i = 0; i < sizeof(mydev->decimals); i++) { + pos = sizeof(mydev->decimals) - 1 - i; + if (mydev->decimals[i] == 0) + buf[pos] = '0'; + else if (mydev->decimals[i] == 1) + buf[pos] = '1'; + else + buf[pos] = 'x'; + } + + buf[sizeof(mydev->decimals)] = '\n'; + return sizeof(mydev->decimals) + 1; +} + +static ssize_t decimals_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct usb_sevsegdev *mydev = usb_get_intfdata(intf); + size_t end = my_memlen(buf, count); + int i; + + if (end > sizeof(mydev->decimals)) + return -EINVAL; + + for (i = 0; i < end; i++) + if (buf[i] != '0' && buf[i] != '1') + return -EINVAL; + + memset(mydev->decimals, 0, sizeof(mydev->decimals)); + for (i = 0; i < end; i++) + if (buf[i] == '1') + mydev->decimals[end-1-i] = 1; + + update_display_visual(mydev, GFP_KERNEL); + + return count; +} + +static DEVICE_ATTR_RW(decimals); + +static ssize_t textmode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct usb_sevsegdev *mydev = usb_get_intfdata(intf); + int i; + + buf[0] = 0; + + for (i = 0; i < ARRAY_SIZE(display_textmodes); i++) { + if (mydev->textmode == i) { + strcat(buf, " ["); + strcat(buf, display_textmodes[i]); + strcat(buf, "] "); + } else { + strcat(buf, " "); + strcat(buf, display_textmodes[i]); + strcat(buf, " "); + } + } + strcat(buf, "\n"); + + + return strlen(buf); +} + +static ssize_t textmode_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct usb_interface *intf = to_usb_interface(dev); + struct usb_sevsegdev *mydev = usb_get_intfdata(intf); + int i; + + i = sysfs_match_string(display_textmodes, buf); + if (i < 0) + return i; + + mydev->textmode = i; + update_display_visual(mydev, GFP_KERNEL); + return count; +} + +static DEVICE_ATTR_RW(textmode); + + +MYDEV_ATTR_SIMPLE_UNSIGNED(powered, update_display_powered); +MYDEV_ATTR_SIMPLE_UNSIGNED(mode_msb, update_display_mode); +MYDEV_ATTR_SIMPLE_UNSIGNED(mode_lsb, update_display_mode); + +static struct attribute *sevseg_attrs[] = { + &dev_attr_powered.attr, + &dev_attr_text.attr, + &dev_attr_textmode.attr, + &dev_attr_decimals.attr, + &dev_attr_mode_msb.attr, + &dev_attr_mode_lsb.attr, + NULL +}; +ATTRIBUTE_GROUPS(sevseg); + +static int sevseg_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(interface); + struct usb_sevsegdev *mydev; + int rc = -ENOMEM; + + mydev = kzalloc(sizeof(struct usb_sevsegdev), GFP_KERNEL); + if (!mydev) + goto error_mem; + + mydev->udev = usb_get_dev(udev); + mydev->intf = interface; + usb_set_intfdata(interface, mydev); + + /* PM */ + mydev->shadow_power = 1; /* currently active */ + mydev->has_interface_pm = 0; /* have not issued autopm_get */ + + /*set defaults */ + mydev->textmode = 0x02; /* ascii mode */ + mydev->mode_msb = 0x06; /* 6 characters */ + mydev->mode_lsb = 0x3f; /* scanmode for 6 chars */ + + dev_info(&interface->dev, "USB 7 Segment device now attached\n"); + return 0; + +error_mem: + return rc; +} + +static void sevseg_disconnect(struct usb_interface *interface) +{ + struct usb_sevsegdev *mydev; + + mydev = usb_get_intfdata(interface); + usb_set_intfdata(interface, NULL); + usb_put_dev(mydev->udev); + kfree(mydev); + dev_info(&interface->dev, "USB 7 Segment now disconnected\n"); +} + +static int sevseg_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct usb_sevsegdev *mydev; + + mydev = usb_get_intfdata(intf); + mydev->shadow_power = 0; + + return 0; +} + +static int sevseg_resume(struct usb_interface *intf) +{ + struct usb_sevsegdev *mydev; + + mydev = usb_get_intfdata(intf); + mydev->shadow_power = 1; + update_display_mode(mydev); + update_display_visual(mydev, GFP_NOIO); + + return 0; +} + +static int sevseg_reset_resume(struct usb_interface *intf) +{ + struct usb_sevsegdev *mydev; + + mydev = usb_get_intfdata(intf); + mydev->shadow_power = 1; + update_display_mode(mydev); + update_display_visual(mydev, GFP_NOIO); + + return 0; +} + +static struct usb_driver sevseg_driver = { + .name = "usbsevseg", + .probe = sevseg_probe, + .disconnect = sevseg_disconnect, + .suspend = sevseg_suspend, + .resume = sevseg_resume, + .reset_resume = sevseg_reset_resume, + .id_table = id_table, + .dev_groups = sevseg_groups, + .supports_autosuspend = 1, +}; + +module_usb_driver(sevseg_driver); + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/misc/usbtest.c b/drivers/usb/misc/usbtest.c new file mode 100644 index 0000000000..ac0d75ac2d --- /dev/null +++ b/drivers/usb/misc/usbtest.c @@ -0,0 +1,3078 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/scatterlist.h> +#include <linux/mutex.h> +#include <linux/timer.h> +#include <linux/usb.h> + +#define SIMPLE_IO_TIMEOUT 10000 /* in milliseconds */ + +/*-------------------------------------------------------------------------*/ + +static int override_alt = -1; +module_param_named(alt, override_alt, int, 0644); +MODULE_PARM_DESC(alt, ">= 0 to override altsetting selection"); +static void complicated_callback(struct urb *urb); + +/*-------------------------------------------------------------------------*/ + +/* FIXME make these public somewhere; usbdevfs.h? */ + +/* Parameter for usbtest driver. */ +struct usbtest_param_32 { + /* inputs */ + __u32 test_num; /* 0..(TEST_CASES-1) */ + __u32 iterations; + __u32 length; + __u32 vary; + __u32 sglen; + + /* outputs */ + __s32 duration_sec; + __s32 duration_usec; +}; + +/* + * Compat parameter to the usbtest driver. + * This supports older user space binaries compiled with 64 bit compiler. + */ +struct usbtest_param_64 { + /* inputs */ + __u32 test_num; /* 0..(TEST_CASES-1) */ + __u32 iterations; + __u32 length; + __u32 vary; + __u32 sglen; + + /* outputs */ + __s64 duration_sec; + __s64 duration_usec; +}; + +/* IOCTL interface to the driver. */ +#define USBTEST_REQUEST_32 _IOWR('U', 100, struct usbtest_param_32) +/* COMPAT IOCTL interface to the driver. */ +#define USBTEST_REQUEST_64 _IOWR('U', 100, struct usbtest_param_64) + +/*-------------------------------------------------------------------------*/ + +#define GENERIC /* let probe() bind using module params */ + +/* Some devices that can be used for testing will have "real" drivers. + * Entries for those need to be enabled here by hand, after disabling + * that "real" driver. + */ +//#define IBOT2 /* grab iBOT2 webcams */ +//#define KEYSPAN_19Qi /* grab un-renumerated serial adapter */ + +/*-------------------------------------------------------------------------*/ + +struct usbtest_info { + const char *name; + u8 ep_in; /* bulk/intr source */ + u8 ep_out; /* bulk/intr sink */ + unsigned autoconf:1; + unsigned ctrl_out:1; + unsigned iso:1; /* try iso in/out */ + unsigned intr:1; /* try interrupt in/out */ + int alt; +}; + +/* this is accessed only through usbfs ioctl calls. + * one ioctl to issue a test ... one lock per device. + * tests create other threads if they need them. + * urbs and buffers are allocated dynamically, + * and data generated deterministically. + */ +struct usbtest_dev { + struct usb_interface *intf; + struct usbtest_info *info; + int in_pipe; + int out_pipe; + int in_iso_pipe; + int out_iso_pipe; + int in_int_pipe; + int out_int_pipe; + struct usb_endpoint_descriptor *iso_in, *iso_out; + struct usb_endpoint_descriptor *int_in, *int_out; + struct mutex lock; + +#define TBUF_SIZE 256 + u8 *buf; +}; + +static struct usb_device *testdev_to_usbdev(struct usbtest_dev *test) +{ + return interface_to_usbdev(test->intf); +} + +/* set up all urbs so they can be used with either bulk or interrupt */ +#define INTERRUPT_RATE 1 /* msec/transfer */ + +#define ERROR(tdev, fmt, args...) \ + dev_err(&(tdev)->intf->dev , fmt , ## args) +#define WARNING(tdev, fmt, args...) \ + dev_warn(&(tdev)->intf->dev , fmt , ## args) + +#define GUARD_BYTE 0xA5 +#define MAX_SGLEN 128 + +/*-------------------------------------------------------------------------*/ + +static inline void endpoint_update(int edi, + struct usb_host_endpoint **in, + struct usb_host_endpoint **out, + struct usb_host_endpoint *e) +{ + if (edi) { + if (!*in) + *in = e; + } else { + if (!*out) + *out = e; + } +} + +static int +get_endpoints(struct usbtest_dev *dev, struct usb_interface *intf) +{ + int tmp; + struct usb_host_interface *alt; + struct usb_host_endpoint *in, *out; + struct usb_host_endpoint *iso_in, *iso_out; + struct usb_host_endpoint *int_in, *int_out; + struct usb_device *udev; + + for (tmp = 0; tmp < intf->num_altsetting; tmp++) { + unsigned ep; + + in = out = NULL; + iso_in = iso_out = NULL; + int_in = int_out = NULL; + alt = intf->altsetting + tmp; + + if (override_alt >= 0 && + override_alt != alt->desc.bAlternateSetting) + continue; + + /* take the first altsetting with in-bulk + out-bulk; + * ignore other endpoints and altsettings. + */ + for (ep = 0; ep < alt->desc.bNumEndpoints; ep++) { + struct usb_host_endpoint *e; + int edi; + + e = alt->endpoint + ep; + edi = usb_endpoint_dir_in(&e->desc); + + switch (usb_endpoint_type(&e->desc)) { + case USB_ENDPOINT_XFER_BULK: + endpoint_update(edi, &in, &out, e); + continue; + case USB_ENDPOINT_XFER_INT: + if (dev->info->intr) + endpoint_update(edi, &int_in, &int_out, e); + continue; + case USB_ENDPOINT_XFER_ISOC: + if (dev->info->iso) + endpoint_update(edi, &iso_in, &iso_out, e); + fallthrough; + default: + continue; + } + } + if ((in && out) || iso_in || iso_out || int_in || int_out) + goto found; + } + return -EINVAL; + +found: + udev = testdev_to_usbdev(dev); + dev->info->alt = alt->desc.bAlternateSetting; + if (alt->desc.bAlternateSetting != 0) { + tmp = usb_set_interface(udev, + alt->desc.bInterfaceNumber, + alt->desc.bAlternateSetting); + if (tmp < 0) + return tmp; + } + + if (in) + dev->in_pipe = usb_rcvbulkpipe(udev, + in->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK); + if (out) + dev->out_pipe = usb_sndbulkpipe(udev, + out->desc.bEndpointAddress & USB_ENDPOINT_NUMBER_MASK); + + if (iso_in) { + dev->iso_in = &iso_in->desc; + dev->in_iso_pipe = usb_rcvisocpipe(udev, + iso_in->desc.bEndpointAddress + & USB_ENDPOINT_NUMBER_MASK); + } + + if (iso_out) { + dev->iso_out = &iso_out->desc; + dev->out_iso_pipe = usb_sndisocpipe(udev, + iso_out->desc.bEndpointAddress + & USB_ENDPOINT_NUMBER_MASK); + } + + if (int_in) { + dev->int_in = &int_in->desc; + dev->in_int_pipe = usb_rcvintpipe(udev, + int_in->desc.bEndpointAddress + & USB_ENDPOINT_NUMBER_MASK); + } + + if (int_out) { + dev->int_out = &int_out->desc; + dev->out_int_pipe = usb_sndintpipe(udev, + int_out->desc.bEndpointAddress + & USB_ENDPOINT_NUMBER_MASK); + } + return 0; +} + +/*-------------------------------------------------------------------------*/ + +/* Support for testing basic non-queued I/O streams. + * + * These just package urbs as requests that can be easily canceled. + * Each urb's data buffer is dynamically allocated; callers can fill + * them with non-zero test data (or test for it) when appropriate. + */ + +static void simple_callback(struct urb *urb) +{ + complete(urb->context); +} + +static struct urb *usbtest_alloc_urb( + struct usb_device *udev, + int pipe, + unsigned long bytes, + unsigned transfer_flags, + unsigned offset, + u8 bInterval, + usb_complete_t complete_fn) +{ + struct urb *urb; + + urb = usb_alloc_urb(0, GFP_KERNEL); + if (!urb) + return urb; + + if (bInterval) + usb_fill_int_urb(urb, udev, pipe, NULL, bytes, complete_fn, + NULL, bInterval); + else + usb_fill_bulk_urb(urb, udev, pipe, NULL, bytes, complete_fn, + NULL); + + urb->interval = (udev->speed == USB_SPEED_HIGH) + ? (INTERRUPT_RATE << 3) + : INTERRUPT_RATE; + urb->transfer_flags = transfer_flags; + if (usb_pipein(pipe)) + urb->transfer_flags |= URB_SHORT_NOT_OK; + + if ((bytes + offset) == 0) + return urb; + + if (urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP) + urb->transfer_buffer = usb_alloc_coherent(udev, bytes + offset, + GFP_KERNEL, &urb->transfer_dma); + else + urb->transfer_buffer = kmalloc(bytes + offset, GFP_KERNEL); + + if (!urb->transfer_buffer) { + usb_free_urb(urb); + return NULL; + } + + /* To test unaligned transfers add an offset and fill the + unused memory with a guard value */ + if (offset) { + memset(urb->transfer_buffer, GUARD_BYTE, offset); + urb->transfer_buffer += offset; + if (urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP) + urb->transfer_dma += offset; + } + + /* For inbound transfers use guard byte so that test fails if + data not correctly copied */ + memset(urb->transfer_buffer, + usb_pipein(urb->pipe) ? GUARD_BYTE : 0, + bytes); + return urb; +} + +static struct urb *simple_alloc_urb( + struct usb_device *udev, + int pipe, + unsigned long bytes, + u8 bInterval) +{ + return usbtest_alloc_urb(udev, pipe, bytes, URB_NO_TRANSFER_DMA_MAP, 0, + bInterval, simple_callback); +} + +static struct urb *complicated_alloc_urb( + struct usb_device *udev, + int pipe, + unsigned long bytes, + u8 bInterval) +{ + return usbtest_alloc_urb(udev, pipe, bytes, URB_NO_TRANSFER_DMA_MAP, 0, + bInterval, complicated_callback); +} + +static unsigned pattern; +static unsigned mod_pattern; +module_param_named(pattern, mod_pattern, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(mod_pattern, "i/o pattern (0 == zeroes)"); + +static unsigned get_maxpacket(struct usb_device *udev, int pipe) +{ + struct usb_host_endpoint *ep; + + ep = usb_pipe_endpoint(udev, pipe); + return le16_to_cpup(&ep->desc.wMaxPacketSize); +} + +static int ss_isoc_get_packet_num(struct usb_device *udev, int pipe) +{ + struct usb_host_endpoint *ep = usb_pipe_endpoint(udev, pipe); + + return USB_SS_MULT(ep->ss_ep_comp.bmAttributes) + * (1 + ep->ss_ep_comp.bMaxBurst); +} + +static void simple_fill_buf(struct urb *urb) +{ + unsigned i; + u8 *buf = urb->transfer_buffer; + unsigned len = urb->transfer_buffer_length; + unsigned maxpacket; + + switch (pattern) { + default: + fallthrough; + case 0: + memset(buf, 0, len); + break; + case 1: /* mod63 */ + maxpacket = get_maxpacket(urb->dev, urb->pipe); + for (i = 0; i < len; i++) + *buf++ = (u8) ((i % maxpacket) % 63); + break; + } +} + +static inline unsigned long buffer_offset(void *buf) +{ + return (unsigned long)buf & (ARCH_KMALLOC_MINALIGN - 1); +} + +static int check_guard_bytes(struct usbtest_dev *tdev, struct urb *urb) +{ + u8 *buf = urb->transfer_buffer; + u8 *guard = buf - buffer_offset(buf); + unsigned i; + + for (i = 0; guard < buf; i++, guard++) { + if (*guard != GUARD_BYTE) { + ERROR(tdev, "guard byte[%d] %d (not %d)\n", + i, *guard, GUARD_BYTE); + return -EINVAL; + } + } + return 0; +} + +static int simple_check_buf(struct usbtest_dev *tdev, struct urb *urb) +{ + unsigned i; + u8 expected; + u8 *buf = urb->transfer_buffer; + unsigned len = urb->actual_length; + unsigned maxpacket = get_maxpacket(urb->dev, urb->pipe); + + int ret = check_guard_bytes(tdev, urb); + if (ret) + return ret; + + for (i = 0; i < len; i++, buf++) { + switch (pattern) { + /* all-zeroes has no synchronization issues */ + case 0: + expected = 0; + break; + /* mod63 stays in sync with short-terminated transfers, + * or otherwise when host and gadget agree on how large + * each usb transfer request should be. resync is done + * with set_interface or set_config. + */ + case 1: /* mod63 */ + expected = (i % maxpacket) % 63; + break; + /* always fail unsupported patterns */ + default: + expected = !*buf; + break; + } + if (*buf == expected) + continue; + ERROR(tdev, "buf[%d] = %d (not %d)\n", i, *buf, expected); + return -EINVAL; + } + return 0; +} + +static void simple_free_urb(struct urb *urb) +{ + unsigned long offset = buffer_offset(urb->transfer_buffer); + + if (urb->transfer_flags & URB_NO_TRANSFER_DMA_MAP) + usb_free_coherent( + urb->dev, + urb->transfer_buffer_length + offset, + urb->transfer_buffer - offset, + urb->transfer_dma - offset); + else + kfree(urb->transfer_buffer - offset); + usb_free_urb(urb); +} + +static int simple_io( + struct usbtest_dev *tdev, + struct urb *urb, + int iterations, + int vary, + int expected, + const char *label +) +{ + struct usb_device *udev = urb->dev; + int max = urb->transfer_buffer_length; + struct completion completion; + int retval = 0; + unsigned long expire; + + urb->context = &completion; + while (retval == 0 && iterations-- > 0) { + init_completion(&completion); + if (usb_pipeout(urb->pipe)) { + simple_fill_buf(urb); + urb->transfer_flags |= URB_ZERO_PACKET; + } + retval = usb_submit_urb(urb, GFP_KERNEL); + if (retval != 0) + break; + + expire = msecs_to_jiffies(SIMPLE_IO_TIMEOUT); + if (!wait_for_completion_timeout(&completion, expire)) { + usb_kill_urb(urb); + retval = (urb->status == -ENOENT ? + -ETIMEDOUT : urb->status); + } else { + retval = urb->status; + } + + urb->dev = udev; + if (retval == 0 && usb_pipein(urb->pipe)) + retval = simple_check_buf(tdev, urb); + + if (vary) { + int len = urb->transfer_buffer_length; + + len += vary; + len %= max; + if (len == 0) + len = (vary < max) ? vary : max; + urb->transfer_buffer_length = len; + } + + /* FIXME if endpoint halted, clear halt (and log) */ + } + urb->transfer_buffer_length = max; + + if (expected != retval) + dev_err(&udev->dev, + "%s failed, iterations left %d, status %d (not %d)\n", + label, iterations, retval, expected); + return retval; +} + + +/*-------------------------------------------------------------------------*/ + +/* We use scatterlist primitives to test queued I/O. + * Yes, this also tests the scatterlist primitives. + */ + +static void free_sglist(struct scatterlist *sg, int nents) +{ + unsigned i; + + if (!sg) + return; + for (i = 0; i < nents; i++) { + if (!sg_page(&sg[i])) + continue; + kfree(sg_virt(&sg[i])); + } + kfree(sg); +} + +static struct scatterlist * +alloc_sglist(int nents, int max, int vary, struct usbtest_dev *dev, int pipe) +{ + struct scatterlist *sg; + unsigned int n_size = 0; + unsigned i; + unsigned size = max; + unsigned maxpacket = + get_maxpacket(interface_to_usbdev(dev->intf), pipe); + + if (max == 0) + return NULL; + + sg = kmalloc_array(nents, sizeof(*sg), GFP_KERNEL); + if (!sg) + return NULL; + sg_init_table(sg, nents); + + for (i = 0; i < nents; i++) { + char *buf; + unsigned j; + + buf = kzalloc(size, GFP_KERNEL); + if (!buf) { + free_sglist(sg, i); + return NULL; + } + + /* kmalloc pages are always physically contiguous! */ + sg_set_buf(&sg[i], buf, size); + + switch (pattern) { + case 0: + /* already zeroed */ + break; + case 1: + for (j = 0; j < size; j++) + *buf++ = (u8) (((j + n_size) % maxpacket) % 63); + n_size += size; + break; + } + + if (vary) { + size += vary; + size %= max; + if (size == 0) + size = (vary < max) ? vary : max; + } + } + + return sg; +} + +struct sg_timeout { + struct timer_list timer; + struct usb_sg_request *req; +}; + +static void sg_timeout(struct timer_list *t) +{ + struct sg_timeout *timeout = from_timer(timeout, t, timer); + + usb_sg_cancel(timeout->req); +} + +static int perform_sglist( + struct usbtest_dev *tdev, + unsigned iterations, + int pipe, + struct usb_sg_request *req, + struct scatterlist *sg, + int nents +) +{ + struct usb_device *udev = testdev_to_usbdev(tdev); + int retval = 0; + struct sg_timeout timeout = { + .req = req, + }; + + timer_setup_on_stack(&timeout.timer, sg_timeout, 0); + + while (retval == 0 && iterations-- > 0) { + retval = usb_sg_init(req, udev, pipe, + (udev->speed == USB_SPEED_HIGH) + ? (INTERRUPT_RATE << 3) + : INTERRUPT_RATE, + sg, nents, 0, GFP_KERNEL); + + if (retval) + break; + mod_timer(&timeout.timer, jiffies + + msecs_to_jiffies(SIMPLE_IO_TIMEOUT)); + usb_sg_wait(req); + if (!del_timer_sync(&timeout.timer)) + retval = -ETIMEDOUT; + else + retval = req->status; + destroy_timer_on_stack(&timeout.timer); + + /* FIXME check resulting data pattern */ + + /* FIXME if endpoint halted, clear halt (and log) */ + } + + /* FIXME for unlink or fault handling tests, don't report + * failure if retval is as we expected ... + */ + if (retval) + ERROR(tdev, "perform_sglist failed, " + "iterations left %d, status %d\n", + iterations, retval); + return retval; +} + + +/*-------------------------------------------------------------------------*/ + +/* unqueued control message testing + * + * there's a nice set of device functional requirements in chapter 9 of the + * usb 2.0 spec, which we can apply to ANY device, even ones that don't use + * special test firmware. + * + * we know the device is configured (or suspended) by the time it's visible + * through usbfs. we can't change that, so we won't test enumeration (which + * worked 'well enough' to get here, this time), power management (ditto), + * or remote wakeup (which needs human interaction). + */ + +static unsigned realworld = 1; +module_param(realworld, uint, 0); +MODULE_PARM_DESC(realworld, "clear to demand stricter spec compliance"); + +static int get_altsetting(struct usbtest_dev *dev) +{ + struct usb_interface *iface = dev->intf; + struct usb_device *udev = interface_to_usbdev(iface); + int retval; + + retval = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), + USB_REQ_GET_INTERFACE, USB_DIR_IN|USB_RECIP_INTERFACE, + 0, iface->altsetting[0].desc.bInterfaceNumber, + dev->buf, 1, USB_CTRL_GET_TIMEOUT); + switch (retval) { + case 1: + return dev->buf[0]; + case 0: + retval = -ERANGE; + fallthrough; + default: + return retval; + } +} + +static int set_altsetting(struct usbtest_dev *dev, int alternate) +{ + struct usb_interface *iface = dev->intf; + struct usb_device *udev; + + if (alternate < 0 || alternate >= 256) + return -EINVAL; + + udev = interface_to_usbdev(iface); + return usb_set_interface(udev, + iface->altsetting[0].desc.bInterfaceNumber, + alternate); +} + +static int is_good_config(struct usbtest_dev *tdev, int len) +{ + struct usb_config_descriptor *config; + + if (len < sizeof(*config)) + return 0; + config = (struct usb_config_descriptor *) tdev->buf; + + switch (config->bDescriptorType) { + case USB_DT_CONFIG: + case USB_DT_OTHER_SPEED_CONFIG: + if (config->bLength != 9) { + ERROR(tdev, "bogus config descriptor length\n"); + return 0; + } + /* this bit 'must be 1' but often isn't */ + if (!realworld && !(config->bmAttributes & 0x80)) { + ERROR(tdev, "high bit of config attributes not set\n"); + return 0; + } + if (config->bmAttributes & 0x1f) { /* reserved == 0 */ + ERROR(tdev, "reserved config bits set\n"); + return 0; + } + break; + default: + return 0; + } + + if (le16_to_cpu(config->wTotalLength) == len) /* read it all */ + return 1; + if (le16_to_cpu(config->wTotalLength) >= TBUF_SIZE) /* max partial read */ + return 1; + ERROR(tdev, "bogus config descriptor read size\n"); + return 0; +} + +static int is_good_ext(struct usbtest_dev *tdev, u8 *buf) +{ + struct usb_ext_cap_descriptor *ext; + u32 attr; + + ext = (struct usb_ext_cap_descriptor *) buf; + + if (ext->bLength != USB_DT_USB_EXT_CAP_SIZE) { + ERROR(tdev, "bogus usb 2.0 extension descriptor length\n"); + return 0; + } + + attr = le32_to_cpu(ext->bmAttributes); + /* bits[1:15] is used and others are reserved */ + if (attr & ~0xfffe) { /* reserved == 0 */ + ERROR(tdev, "reserved bits set\n"); + return 0; + } + + return 1; +} + +static int is_good_ss_cap(struct usbtest_dev *tdev, u8 *buf) +{ + struct usb_ss_cap_descriptor *ss; + + ss = (struct usb_ss_cap_descriptor *) buf; + + if (ss->bLength != USB_DT_USB_SS_CAP_SIZE) { + ERROR(tdev, "bogus superspeed device capability descriptor length\n"); + return 0; + } + + /* + * only bit[1] of bmAttributes is used for LTM and others are + * reserved + */ + if (ss->bmAttributes & ~0x02) { /* reserved == 0 */ + ERROR(tdev, "reserved bits set in bmAttributes\n"); + return 0; + } + + /* bits[0:3] of wSpeedSupported is used and others are reserved */ + if (le16_to_cpu(ss->wSpeedSupported) & ~0x0f) { /* reserved == 0 */ + ERROR(tdev, "reserved bits set in wSpeedSupported\n"); + return 0; + } + + return 1; +} + +static int is_good_con_id(struct usbtest_dev *tdev, u8 *buf) +{ + struct usb_ss_container_id_descriptor *con_id; + + con_id = (struct usb_ss_container_id_descriptor *) buf; + + if (con_id->bLength != USB_DT_USB_SS_CONTN_ID_SIZE) { + ERROR(tdev, "bogus container id descriptor length\n"); + return 0; + } + + if (con_id->bReserved) { /* reserved == 0 */ + ERROR(tdev, "reserved bits set\n"); + return 0; + } + + return 1; +} + +/* sanity test for standard requests working with usb_control_mesg() and some + * of the utility functions which use it. + * + * this doesn't test how endpoint halts behave or data toggles get set, since + * we won't do I/O to bulk/interrupt endpoints here (which is how to change + * halt or toggle). toggle testing is impractical without support from hcds. + * + * this avoids failing devices linux would normally work with, by not testing + * config/altsetting operations for devices that only support their defaults. + * such devices rarely support those needless operations. + * + * NOTE that since this is a sanity test, it's not examining boundary cases + * to see if usbcore, hcd, and device all behave right. such testing would + * involve varied read sizes and other operation sequences. + */ +static int ch9_postconfig(struct usbtest_dev *dev) +{ + struct usb_interface *iface = dev->intf; + struct usb_device *udev = interface_to_usbdev(iface); + int i, alt, retval; + + /* [9.2.3] if there's more than one altsetting, we need to be able to + * set and get each one. mostly trusts the descriptors from usbcore. + */ + for (i = 0; i < iface->num_altsetting; i++) { + + /* 9.2.3 constrains the range here */ + alt = iface->altsetting[i].desc.bAlternateSetting; + if (alt < 0 || alt >= iface->num_altsetting) { + dev_err(&iface->dev, + "invalid alt [%d].bAltSetting = %d\n", + i, alt); + } + + /* [real world] get/set unimplemented if there's only one */ + if (realworld && iface->num_altsetting == 1) + continue; + + /* [9.4.10] set_interface */ + retval = set_altsetting(dev, alt); + if (retval) { + dev_err(&iface->dev, "can't set_interface = %d, %d\n", + alt, retval); + return retval; + } + + /* [9.4.4] get_interface always works */ + retval = get_altsetting(dev); + if (retval != alt) { + dev_err(&iface->dev, "get alt should be %d, was %d\n", + alt, retval); + return (retval < 0) ? retval : -EDOM; + } + + } + + /* [real world] get_config unimplemented if there's only one */ + if (!realworld || udev->descriptor.bNumConfigurations != 1) { + int expected = udev->actconfig->desc.bConfigurationValue; + + /* [9.4.2] get_configuration always works + * ... although some cheap devices (like one TI Hub I've got) + * won't return config descriptors except before set_config. + */ + retval = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), + USB_REQ_GET_CONFIGURATION, + USB_DIR_IN | USB_RECIP_DEVICE, + 0, 0, dev->buf, 1, USB_CTRL_GET_TIMEOUT); + if (retval != 1 || dev->buf[0] != expected) { + dev_err(&iface->dev, "get config --> %d %d (1 %d)\n", + retval, dev->buf[0], expected); + return (retval < 0) ? retval : -EDOM; + } + } + + /* there's always [9.4.3] a device descriptor [9.6.1] */ + retval = usb_get_descriptor(udev, USB_DT_DEVICE, 0, + dev->buf, sizeof(udev->descriptor)); + if (retval != sizeof(udev->descriptor)) { + dev_err(&iface->dev, "dev descriptor --> %d\n", retval); + return (retval < 0) ? retval : -EDOM; + } + + /* + * there's always [9.4.3] a bos device descriptor [9.6.2] in USB + * 3.0 spec + */ + if (le16_to_cpu(udev->descriptor.bcdUSB) >= 0x0210) { + struct usb_bos_descriptor *bos = NULL; + struct usb_dev_cap_header *header = NULL; + unsigned total, num, length; + u8 *buf; + + retval = usb_get_descriptor(udev, USB_DT_BOS, 0, dev->buf, + sizeof(*udev->bos->desc)); + if (retval != sizeof(*udev->bos->desc)) { + dev_err(&iface->dev, "bos descriptor --> %d\n", retval); + return (retval < 0) ? retval : -EDOM; + } + + bos = (struct usb_bos_descriptor *)dev->buf; + total = le16_to_cpu(bos->wTotalLength); + num = bos->bNumDeviceCaps; + + if (total > TBUF_SIZE) + total = TBUF_SIZE; + + /* + * get generic device-level capability descriptors [9.6.2] + * in USB 3.0 spec + */ + retval = usb_get_descriptor(udev, USB_DT_BOS, 0, dev->buf, + total); + if (retval != total) { + dev_err(&iface->dev, "bos descriptor set --> %d\n", + retval); + return (retval < 0) ? retval : -EDOM; + } + + length = sizeof(*udev->bos->desc); + buf = dev->buf; + for (i = 0; i < num; i++) { + buf += length; + if (buf + sizeof(struct usb_dev_cap_header) > + dev->buf + total) + break; + + header = (struct usb_dev_cap_header *)buf; + length = header->bLength; + + if (header->bDescriptorType != + USB_DT_DEVICE_CAPABILITY) { + dev_warn(&udev->dev, "not device capability descriptor, skip\n"); + continue; + } + + switch (header->bDevCapabilityType) { + case USB_CAP_TYPE_EXT: + if (buf + USB_DT_USB_EXT_CAP_SIZE > + dev->buf + total || + !is_good_ext(dev, buf)) { + dev_err(&iface->dev, "bogus usb 2.0 extension descriptor\n"); + return -EDOM; + } + break; + case USB_SS_CAP_TYPE: + if (buf + USB_DT_USB_SS_CAP_SIZE > + dev->buf + total || + !is_good_ss_cap(dev, buf)) { + dev_err(&iface->dev, "bogus superspeed device capability descriptor\n"); + return -EDOM; + } + break; + case CONTAINER_ID_TYPE: + if (buf + USB_DT_USB_SS_CONTN_ID_SIZE > + dev->buf + total || + !is_good_con_id(dev, buf)) { + dev_err(&iface->dev, "bogus container id descriptor\n"); + return -EDOM; + } + break; + default: + break; + } + } + } + + /* there's always [9.4.3] at least one config descriptor [9.6.3] */ + for (i = 0; i < udev->descriptor.bNumConfigurations; i++) { + retval = usb_get_descriptor(udev, USB_DT_CONFIG, i, + dev->buf, TBUF_SIZE); + if (!is_good_config(dev, retval)) { + dev_err(&iface->dev, + "config [%d] descriptor --> %d\n", + i, retval); + return (retval < 0) ? retval : -EDOM; + } + + /* FIXME cross-checking udev->config[i] to make sure usbcore + * parsed it right (etc) would be good testing paranoia + */ + } + + /* and sometimes [9.2.6.6] speed dependent descriptors */ + if (le16_to_cpu(udev->descriptor.bcdUSB) == 0x0200) { + struct usb_qualifier_descriptor *d = NULL; + + /* device qualifier [9.6.2] */ + retval = usb_get_descriptor(udev, + USB_DT_DEVICE_QUALIFIER, 0, dev->buf, + sizeof(struct usb_qualifier_descriptor)); + if (retval == -EPIPE) { + if (udev->speed == USB_SPEED_HIGH) { + dev_err(&iface->dev, + "hs dev qualifier --> %d\n", + retval); + return retval; + } + /* usb2.0 but not high-speed capable; fine */ + } else if (retval != sizeof(struct usb_qualifier_descriptor)) { + dev_err(&iface->dev, "dev qualifier --> %d\n", retval); + return (retval < 0) ? retval : -EDOM; + } else + d = (struct usb_qualifier_descriptor *) dev->buf; + + /* might not have [9.6.2] any other-speed configs [9.6.4] */ + if (d) { + unsigned max = d->bNumConfigurations; + for (i = 0; i < max; i++) { + retval = usb_get_descriptor(udev, + USB_DT_OTHER_SPEED_CONFIG, i, + dev->buf, TBUF_SIZE); + if (!is_good_config(dev, retval)) { + dev_err(&iface->dev, + "other speed config --> %d\n", + retval); + return (retval < 0) ? retval : -EDOM; + } + } + } + } + /* FIXME fetch strings from at least the device descriptor */ + + /* [9.4.5] get_status always works */ + retval = usb_get_std_status(udev, USB_RECIP_DEVICE, 0, dev->buf); + if (retval) { + dev_err(&iface->dev, "get dev status --> %d\n", retval); + return retval; + } + + /* FIXME configuration.bmAttributes says if we could try to set/clear + * the device's remote wakeup feature ... if we can, test that here + */ + + retval = usb_get_std_status(udev, USB_RECIP_INTERFACE, + iface->altsetting[0].desc.bInterfaceNumber, dev->buf); + if (retval) { + dev_err(&iface->dev, "get interface status --> %d\n", retval); + return retval; + } + /* FIXME get status for each endpoint in the interface */ + + return 0; +} + +/*-------------------------------------------------------------------------*/ + +/* use ch9 requests to test whether: + * (a) queues work for control, keeping N subtests queued and + * active (auto-resubmit) for M loops through the queue. + * (b) protocol stalls (control-only) will autorecover. + * it's not like bulk/intr; no halt clearing. + * (c) short control reads are reported and handled. + * (d) queues are always processed in-order + */ + +struct ctrl_ctx { + spinlock_t lock; + struct usbtest_dev *dev; + struct completion complete; + unsigned count; + unsigned pending; + int status; + struct urb **urb; + struct usbtest_param_32 *param; + int last; +}; + +#define NUM_SUBCASES 16 /* how many test subcases here? */ + +struct subcase { + struct usb_ctrlrequest setup; + int number; + int expected; +}; + +static void ctrl_complete(struct urb *urb) +{ + struct ctrl_ctx *ctx = urb->context; + struct usb_ctrlrequest *reqp; + struct subcase *subcase; + int status = urb->status; + unsigned long flags; + + reqp = (struct usb_ctrlrequest *)urb->setup_packet; + subcase = container_of(reqp, struct subcase, setup); + + spin_lock_irqsave(&ctx->lock, flags); + ctx->count--; + ctx->pending--; + + /* queue must transfer and complete in fifo order, unless + * usb_unlink_urb() is used to unlink something not at the + * physical queue head (not tested). + */ + if (subcase->number > 0) { + if ((subcase->number - ctx->last) != 1) { + ERROR(ctx->dev, + "subcase %d completed out of order, last %d\n", + subcase->number, ctx->last); + status = -EDOM; + ctx->last = subcase->number; + goto error; + } + } + ctx->last = subcase->number; + + /* succeed or fault in only one way? */ + if (status == subcase->expected) + status = 0; + + /* async unlink for cleanup? */ + else if (status != -ECONNRESET) { + + /* some faults are allowed, not required */ + if (subcase->expected > 0 && ( + ((status == -subcase->expected /* happened */ + || status == 0)))) /* didn't */ + status = 0; + /* sometimes more than one fault is allowed */ + else if (subcase->number == 12 && status == -EPIPE) + status = 0; + else + ERROR(ctx->dev, "subtest %d error, status %d\n", + subcase->number, status); + } + + /* unexpected status codes mean errors; ideally, in hardware */ + if (status) { +error: + if (ctx->status == 0) { + int i; + + ctx->status = status; + ERROR(ctx->dev, "control queue %02x.%02x, err %d, " + "%d left, subcase %d, len %d/%d\n", + reqp->bRequestType, reqp->bRequest, + status, ctx->count, subcase->number, + urb->actual_length, + urb->transfer_buffer_length); + + /* FIXME this "unlink everything" exit route should + * be a separate test case. + */ + + /* unlink whatever's still pending */ + for (i = 1; i < ctx->param->sglen; i++) { + struct urb *u = ctx->urb[ + (i + subcase->number) + % ctx->param->sglen]; + + if (u == urb || !u->dev) + continue; + spin_unlock(&ctx->lock); + status = usb_unlink_urb(u); + spin_lock(&ctx->lock); + switch (status) { + case -EINPROGRESS: + case -EBUSY: + case -EIDRM: + continue; + default: + ERROR(ctx->dev, "urb unlink --> %d\n", + status); + } + } + status = ctx->status; + } + } + + /* resubmit if we need to, else mark this as done */ + if ((status == 0) && (ctx->pending < ctx->count)) { + status = usb_submit_urb(urb, GFP_ATOMIC); + if (status != 0) { + ERROR(ctx->dev, + "can't resubmit ctrl %02x.%02x, err %d\n", + reqp->bRequestType, reqp->bRequest, status); + urb->dev = NULL; + } else + ctx->pending++; + } else + urb->dev = NULL; + + /* signal completion when nothing's queued */ + if (ctx->pending == 0) + complete(&ctx->complete); + spin_unlock_irqrestore(&ctx->lock, flags); +} + +static int +test_ctrl_queue(struct usbtest_dev *dev, struct usbtest_param_32 *param) +{ + struct usb_device *udev = testdev_to_usbdev(dev); + struct urb **urb; + struct ctrl_ctx context; + int i; + + if (param->sglen == 0 || param->iterations > UINT_MAX / param->sglen) + return -EOPNOTSUPP; + + spin_lock_init(&context.lock); + context.dev = dev; + init_completion(&context.complete); + context.count = param->sglen * param->iterations; + context.pending = 0; + context.status = -ENOMEM; + context.param = param; + context.last = -1; + + /* allocate and init the urbs we'll queue. + * as with bulk/intr sglists, sglen is the queue depth; it also + * controls which subtests run (more tests than sglen) or rerun. + */ + urb = kcalloc(param->sglen, sizeof(struct urb *), GFP_KERNEL); + if (!urb) + return -ENOMEM; + for (i = 0; i < param->sglen; i++) { + int pipe = usb_rcvctrlpipe(udev, 0); + unsigned len; + struct urb *u; + struct usb_ctrlrequest req; + struct subcase *reqp; + + /* sign of this variable means: + * -: tested code must return this (negative) error code + * +: tested code may return this (negative too) error code + */ + int expected = 0; + + /* requests here are mostly expected to succeed on any + * device, but some are chosen to trigger protocol stalls + * or short reads. + */ + memset(&req, 0, sizeof(req)); + req.bRequest = USB_REQ_GET_DESCRIPTOR; + req.bRequestType = USB_DIR_IN|USB_RECIP_DEVICE; + + switch (i % NUM_SUBCASES) { + case 0: /* get device descriptor */ + req.wValue = cpu_to_le16(USB_DT_DEVICE << 8); + len = sizeof(struct usb_device_descriptor); + break; + case 1: /* get first config descriptor (only) */ + req.wValue = cpu_to_le16((USB_DT_CONFIG << 8) | 0); + len = sizeof(struct usb_config_descriptor); + break; + case 2: /* get altsetting (OFTEN STALLS) */ + req.bRequest = USB_REQ_GET_INTERFACE; + req.bRequestType = USB_DIR_IN|USB_RECIP_INTERFACE; + /* index = 0 means first interface */ + len = 1; + expected = EPIPE; + break; + case 3: /* get interface status */ + req.bRequest = USB_REQ_GET_STATUS; + req.bRequestType = USB_DIR_IN|USB_RECIP_INTERFACE; + /* interface 0 */ + len = 2; + break; + case 4: /* get device status */ + req.bRequest = USB_REQ_GET_STATUS; + req.bRequestType = USB_DIR_IN|USB_RECIP_DEVICE; + len = 2; + break; + case 5: /* get device qualifier (MAY STALL) */ + req.wValue = cpu_to_le16 (USB_DT_DEVICE_QUALIFIER << 8); + len = sizeof(struct usb_qualifier_descriptor); + if (udev->speed != USB_SPEED_HIGH) + expected = EPIPE; + break; + case 6: /* get first config descriptor, plus interface */ + req.wValue = cpu_to_le16((USB_DT_CONFIG << 8) | 0); + len = sizeof(struct usb_config_descriptor); + len += sizeof(struct usb_interface_descriptor); + break; + case 7: /* get interface descriptor (ALWAYS STALLS) */ + req.wValue = cpu_to_le16 (USB_DT_INTERFACE << 8); + /* interface == 0 */ + len = sizeof(struct usb_interface_descriptor); + expected = -EPIPE; + break; + /* NOTE: two consecutive stalls in the queue here. + * that tests fault recovery a bit more aggressively. */ + case 8: /* clear endpoint halt (MAY STALL) */ + req.bRequest = USB_REQ_CLEAR_FEATURE; + req.bRequestType = USB_RECIP_ENDPOINT; + /* wValue 0 == ep halt */ + /* wIndex 0 == ep0 (shouldn't halt!) */ + len = 0; + pipe = usb_sndctrlpipe(udev, 0); + expected = EPIPE; + break; + case 9: /* get endpoint status */ + req.bRequest = USB_REQ_GET_STATUS; + req.bRequestType = USB_DIR_IN|USB_RECIP_ENDPOINT; + /* endpoint 0 */ + len = 2; + break; + case 10: /* trigger short read (EREMOTEIO) */ + req.wValue = cpu_to_le16((USB_DT_CONFIG << 8) | 0); + len = 1024; + expected = -EREMOTEIO; + break; + /* NOTE: two consecutive _different_ faults in the queue. */ + case 11: /* get endpoint descriptor (ALWAYS STALLS) */ + req.wValue = cpu_to_le16(USB_DT_ENDPOINT << 8); + /* endpoint == 0 */ + len = sizeof(struct usb_interface_descriptor); + expected = EPIPE; + break; + /* NOTE: sometimes even a third fault in the queue! */ + case 12: /* get string 0 descriptor (MAY STALL) */ + req.wValue = cpu_to_le16(USB_DT_STRING << 8); + /* string == 0, for language IDs */ + len = sizeof(struct usb_interface_descriptor); + /* may succeed when > 4 languages */ + expected = EREMOTEIO; /* or EPIPE, if no strings */ + break; + case 13: /* short read, resembling case 10 */ + req.wValue = cpu_to_le16((USB_DT_CONFIG << 8) | 0); + /* last data packet "should" be DATA1, not DATA0 */ + if (udev->speed == USB_SPEED_SUPER) + len = 1024 - 512; + else + len = 1024 - udev->descriptor.bMaxPacketSize0; + expected = -EREMOTEIO; + break; + case 14: /* short read; try to fill the last packet */ + req.wValue = cpu_to_le16((USB_DT_DEVICE << 8) | 0); + /* device descriptor size == 18 bytes */ + len = udev->descriptor.bMaxPacketSize0; + if (udev->speed == USB_SPEED_SUPER) + len = 512; + switch (len) { + case 8: + len = 24; + break; + case 16: + len = 32; + break; + } + expected = -EREMOTEIO; + break; + case 15: + req.wValue = cpu_to_le16(USB_DT_BOS << 8); + if (udev->bos) + len = le16_to_cpu(udev->bos->desc->wTotalLength); + else + len = sizeof(struct usb_bos_descriptor); + if (le16_to_cpu(udev->descriptor.bcdUSB) < 0x0201) + expected = -EPIPE; + break; + default: + ERROR(dev, "bogus number of ctrl queue testcases!\n"); + context.status = -EINVAL; + goto cleanup; + } + req.wLength = cpu_to_le16(len); + urb[i] = u = simple_alloc_urb(udev, pipe, len, 0); + if (!u) + goto cleanup; + + reqp = kmalloc(sizeof(*reqp), GFP_KERNEL); + if (!reqp) + goto cleanup; + reqp->setup = req; + reqp->number = i % NUM_SUBCASES; + reqp->expected = expected; + u->setup_packet = (char *) &reqp->setup; + + u->context = &context; + u->complete = ctrl_complete; + } + + /* queue the urbs */ + context.urb = urb; + spin_lock_irq(&context.lock); + for (i = 0; i < param->sglen; i++) { + context.status = usb_submit_urb(urb[i], GFP_ATOMIC); + if (context.status != 0) { + ERROR(dev, "can't submit urb[%d], status %d\n", + i, context.status); + context.count = context.pending; + break; + } + context.pending++; + } + spin_unlock_irq(&context.lock); + + /* FIXME set timer and time out; provide a disconnect hook */ + + /* wait for the last one to complete */ + if (context.pending > 0) + wait_for_completion(&context.complete); + +cleanup: + for (i = 0; i < param->sglen; i++) { + if (!urb[i]) + continue; + urb[i]->dev = udev; + kfree(urb[i]->setup_packet); + simple_free_urb(urb[i]); + } + kfree(urb); + return context.status; +} +#undef NUM_SUBCASES + + +/*-------------------------------------------------------------------------*/ + +static void unlink1_callback(struct urb *urb) +{ + int status = urb->status; + + /* we "know" -EPIPE (stall) never happens */ + if (!status) + status = usb_submit_urb(urb, GFP_ATOMIC); + if (status) { + urb->status = status; + complete(urb->context); + } +} + +static int unlink1(struct usbtest_dev *dev, int pipe, int size, int async) +{ + struct urb *urb; + struct completion completion; + int retval = 0; + + init_completion(&completion); + urb = simple_alloc_urb(testdev_to_usbdev(dev), pipe, size, 0); + if (!urb) + return -ENOMEM; + urb->context = &completion; + urb->complete = unlink1_callback; + + if (usb_pipeout(urb->pipe)) { + simple_fill_buf(urb); + urb->transfer_flags |= URB_ZERO_PACKET; + } + + /* keep the endpoint busy. there are lots of hc/hcd-internal + * states, and testing should get to all of them over time. + * + * FIXME want additional tests for when endpoint is STALLing + * due to errors, or is just NAKing requests. + */ + retval = usb_submit_urb(urb, GFP_KERNEL); + if (retval != 0) { + dev_err(&dev->intf->dev, "submit fail %d\n", retval); + return retval; + } + + /* unlinking that should always work. variable delay tests more + * hcd states and code paths, even with little other system load. + */ + msleep(jiffies % (2 * INTERRUPT_RATE)); + if (async) { + while (!completion_done(&completion)) { + retval = usb_unlink_urb(urb); + + if (retval == 0 && usb_pipein(urb->pipe)) + retval = simple_check_buf(dev, urb); + + switch (retval) { + case -EBUSY: + case -EIDRM: + /* we can't unlink urbs while they're completing + * or if they've completed, and we haven't + * resubmitted. "normal" drivers would prevent + * resubmission, but since we're testing unlink + * paths, we can't. + */ + ERROR(dev, "unlink retry\n"); + continue; + case 0: + case -EINPROGRESS: + break; + + default: + dev_err(&dev->intf->dev, + "unlink fail %d\n", retval); + return retval; + } + + break; + } + } else + usb_kill_urb(urb); + + wait_for_completion(&completion); + retval = urb->status; + simple_free_urb(urb); + + if (async) + return (retval == -ECONNRESET) ? 0 : retval - 1000; + else + return (retval == -ENOENT || retval == -EPERM) ? + 0 : retval - 2000; +} + +static int unlink_simple(struct usbtest_dev *dev, int pipe, int len) +{ + int retval = 0; + + /* test sync and async paths */ + retval = unlink1(dev, pipe, len, 1); + if (!retval) + retval = unlink1(dev, pipe, len, 0); + return retval; +} + +/*-------------------------------------------------------------------------*/ + +struct queued_ctx { + struct completion complete; + atomic_t pending; + unsigned num; + int status; + struct urb **urbs; +}; + +static void unlink_queued_callback(struct urb *urb) +{ + int status = urb->status; + struct queued_ctx *ctx = urb->context; + + if (ctx->status) + goto done; + if (urb == ctx->urbs[ctx->num - 4] || urb == ctx->urbs[ctx->num - 2]) { + if (status == -ECONNRESET) + goto done; + /* What error should we report if the URB completed normally? */ + } + if (status != 0) + ctx->status = status; + + done: + if (atomic_dec_and_test(&ctx->pending)) + complete(&ctx->complete); +} + +static int unlink_queued(struct usbtest_dev *dev, int pipe, unsigned num, + unsigned size) +{ + struct queued_ctx ctx; + struct usb_device *udev = testdev_to_usbdev(dev); + void *buf; + dma_addr_t buf_dma; + int i; + int retval = -ENOMEM; + + init_completion(&ctx.complete); + atomic_set(&ctx.pending, 1); /* One more than the actual value */ + ctx.num = num; + ctx.status = 0; + + buf = usb_alloc_coherent(udev, size, GFP_KERNEL, &buf_dma); + if (!buf) + return retval; + memset(buf, 0, size); + + /* Allocate and init the urbs we'll queue */ + ctx.urbs = kcalloc(num, sizeof(struct urb *), GFP_KERNEL); + if (!ctx.urbs) + goto free_buf; + for (i = 0; i < num; i++) { + ctx.urbs[i] = usb_alloc_urb(0, GFP_KERNEL); + if (!ctx.urbs[i]) + goto free_urbs; + usb_fill_bulk_urb(ctx.urbs[i], udev, pipe, buf, size, + unlink_queued_callback, &ctx); + ctx.urbs[i]->transfer_dma = buf_dma; + ctx.urbs[i]->transfer_flags = URB_NO_TRANSFER_DMA_MAP; + + if (usb_pipeout(ctx.urbs[i]->pipe)) { + simple_fill_buf(ctx.urbs[i]); + ctx.urbs[i]->transfer_flags |= URB_ZERO_PACKET; + } + } + + /* Submit all the URBs and then unlink URBs num - 4 and num - 2. */ + for (i = 0; i < num; i++) { + atomic_inc(&ctx.pending); + retval = usb_submit_urb(ctx.urbs[i], GFP_KERNEL); + if (retval != 0) { + dev_err(&dev->intf->dev, "submit urbs[%d] fail %d\n", + i, retval); + atomic_dec(&ctx.pending); + ctx.status = retval; + break; + } + } + if (i == num) { + usb_unlink_urb(ctx.urbs[num - 4]); + usb_unlink_urb(ctx.urbs[num - 2]); + } else { + while (--i >= 0) + usb_unlink_urb(ctx.urbs[i]); + } + + if (atomic_dec_and_test(&ctx.pending)) /* The extra count */ + complete(&ctx.complete); + wait_for_completion(&ctx.complete); + retval = ctx.status; + + free_urbs: + for (i = 0; i < num; i++) + usb_free_urb(ctx.urbs[i]); + kfree(ctx.urbs); + free_buf: + usb_free_coherent(udev, size, buf, buf_dma); + return retval; +} + +/*-------------------------------------------------------------------------*/ + +static int verify_not_halted(struct usbtest_dev *tdev, int ep, struct urb *urb) +{ + int retval; + u16 status; + + /* shouldn't look or act halted */ + retval = usb_get_std_status(urb->dev, USB_RECIP_ENDPOINT, ep, &status); + if (retval < 0) { + ERROR(tdev, "ep %02x couldn't get no-halt status, %d\n", + ep, retval); + return retval; + } + if (status != 0) { + ERROR(tdev, "ep %02x bogus status: %04x != 0\n", ep, status); + return -EINVAL; + } + retval = simple_io(tdev, urb, 1, 0, 0, __func__); + if (retval != 0) + return -EINVAL; + return 0; +} + +static int verify_halted(struct usbtest_dev *tdev, int ep, struct urb *urb) +{ + int retval; + u16 status; + + /* should look and act halted */ + retval = usb_get_std_status(urb->dev, USB_RECIP_ENDPOINT, ep, &status); + if (retval < 0) { + ERROR(tdev, "ep %02x couldn't get halt status, %d\n", + ep, retval); + return retval; + } + if (status != 1) { + ERROR(tdev, "ep %02x bogus status: %04x != 1\n", ep, status); + return -EINVAL; + } + retval = simple_io(tdev, urb, 1, 0, -EPIPE, __func__); + if (retval != -EPIPE) + return -EINVAL; + retval = simple_io(tdev, urb, 1, 0, -EPIPE, "verify_still_halted"); + if (retval != -EPIPE) + return -EINVAL; + return 0; +} + +static int test_halt(struct usbtest_dev *tdev, int ep, struct urb *urb) +{ + int retval; + + /* shouldn't look or act halted now */ + retval = verify_not_halted(tdev, ep, urb); + if (retval < 0) + return retval; + + /* set halt (protocol test only), verify it worked */ + retval = usb_control_msg(urb->dev, usb_sndctrlpipe(urb->dev, 0), + USB_REQ_SET_FEATURE, USB_RECIP_ENDPOINT, + USB_ENDPOINT_HALT, ep, + NULL, 0, USB_CTRL_SET_TIMEOUT); + if (retval < 0) { + ERROR(tdev, "ep %02x couldn't set halt, %d\n", ep, retval); + return retval; + } + retval = verify_halted(tdev, ep, urb); + if (retval < 0) { + int ret; + + /* clear halt anyways, else further tests will fail */ + ret = usb_clear_halt(urb->dev, urb->pipe); + if (ret) + ERROR(tdev, "ep %02x couldn't clear halt, %d\n", + ep, ret); + + return retval; + } + + /* clear halt (tests API + protocol), verify it worked */ + retval = usb_clear_halt(urb->dev, urb->pipe); + if (retval < 0) { + ERROR(tdev, "ep %02x couldn't clear halt, %d\n", ep, retval); + return retval; + } + retval = verify_not_halted(tdev, ep, urb); + if (retval < 0) + return retval; + + /* NOTE: could also verify SET_INTERFACE clear halts ... */ + + return 0; +} + +static int test_toggle_sync(struct usbtest_dev *tdev, int ep, struct urb *urb) +{ + int retval; + + /* clear initial data toggle to DATA0 */ + retval = usb_clear_halt(urb->dev, urb->pipe); + if (retval < 0) { + ERROR(tdev, "ep %02x couldn't clear halt, %d\n", ep, retval); + return retval; + } + + /* transfer 3 data packets, should be DATA0, DATA1, DATA0 */ + retval = simple_io(tdev, urb, 1, 0, 0, __func__); + if (retval != 0) + return -EINVAL; + + /* clear halt resets device side data toggle, host should react to it */ + retval = usb_clear_halt(urb->dev, urb->pipe); + if (retval < 0) { + ERROR(tdev, "ep %02x couldn't clear halt, %d\n", ep, retval); + return retval; + } + + /* host should use DATA0 again after clear halt */ + retval = simple_io(tdev, urb, 1, 0, 0, __func__); + + return retval; +} + +static int halt_simple(struct usbtest_dev *dev) +{ + int ep; + int retval = 0; + struct urb *urb; + struct usb_device *udev = testdev_to_usbdev(dev); + + if (udev->speed == USB_SPEED_SUPER) + urb = simple_alloc_urb(udev, 0, 1024, 0); + else + urb = simple_alloc_urb(udev, 0, 512, 0); + if (urb == NULL) + return -ENOMEM; + + if (dev->in_pipe) { + ep = usb_pipeendpoint(dev->in_pipe) | USB_DIR_IN; + urb->pipe = dev->in_pipe; + retval = test_halt(dev, ep, urb); + if (retval < 0) + goto done; + } + + if (dev->out_pipe) { + ep = usb_pipeendpoint(dev->out_pipe); + urb->pipe = dev->out_pipe; + retval = test_halt(dev, ep, urb); + } +done: + simple_free_urb(urb); + return retval; +} + +static int toggle_sync_simple(struct usbtest_dev *dev) +{ + int ep; + int retval = 0; + struct urb *urb; + struct usb_device *udev = testdev_to_usbdev(dev); + unsigned maxp = get_maxpacket(udev, dev->out_pipe); + + /* + * Create a URB that causes a transfer of uneven amount of data packets + * This way the clear toggle has an impact on the data toggle sequence. + * Use 2 maxpacket length packets and one zero packet. + */ + urb = simple_alloc_urb(udev, 0, 2 * maxp, 0); + if (urb == NULL) + return -ENOMEM; + + urb->transfer_flags |= URB_ZERO_PACKET; + + ep = usb_pipeendpoint(dev->out_pipe); + urb->pipe = dev->out_pipe; + retval = test_toggle_sync(dev, ep, urb); + + simple_free_urb(urb); + return retval; +} + +/*-------------------------------------------------------------------------*/ + +/* Control OUT tests use the vendor control requests from Intel's + * USB 2.0 compliance test device: write a buffer, read it back. + * + * Intel's spec only _requires_ that it work for one packet, which + * is pretty weak. Some HCDs place limits here; most devices will + * need to be able to handle more than one OUT data packet. We'll + * try whatever we're told to try. + */ +static int ctrl_out(struct usbtest_dev *dev, + unsigned count, unsigned length, unsigned vary, unsigned offset) +{ + unsigned i, j, len; + int retval; + u8 *buf; + char *what = "?"; + struct usb_device *udev; + + if (length < 1 || length > 0xffff || vary >= length) + return -EINVAL; + + buf = kmalloc(length + offset, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + buf += offset; + udev = testdev_to_usbdev(dev); + len = length; + retval = 0; + + /* NOTE: hardware might well act differently if we pushed it + * with lots back-to-back queued requests. + */ + for (i = 0; i < count; i++) { + /* write patterned data */ + for (j = 0; j < len; j++) + buf[j] = (u8)(i + j); + retval = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + 0x5b, USB_DIR_OUT|USB_TYPE_VENDOR, + 0, 0, buf, len, USB_CTRL_SET_TIMEOUT); + if (retval != len) { + what = "write"; + if (retval >= 0) { + ERROR(dev, "ctrl_out, wlen %d (expected %d)\n", + retval, len); + retval = -EBADMSG; + } + break; + } + + /* read it back -- assuming nothing intervened!! */ + retval = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), + 0x5c, USB_DIR_IN|USB_TYPE_VENDOR, + 0, 0, buf, len, USB_CTRL_GET_TIMEOUT); + if (retval != len) { + what = "read"; + if (retval >= 0) { + ERROR(dev, "ctrl_out, rlen %d (expected %d)\n", + retval, len); + retval = -EBADMSG; + } + break; + } + + /* fail if we can't verify */ + for (j = 0; j < len; j++) { + if (buf[j] != (u8)(i + j)) { + ERROR(dev, "ctrl_out, byte %d is %d not %d\n", + j, buf[j], (u8)(i + j)); + retval = -EBADMSG; + break; + } + } + if (retval < 0) { + what = "verify"; + break; + } + + len += vary; + + /* [real world] the "zero bytes IN" case isn't really used. + * hardware can easily trip up in this weird case, since its + * status stage is IN, not OUT like other ep0in transfers. + */ + if (len > length) + len = realworld ? 1 : 0; + } + + if (retval < 0) + ERROR(dev, "ctrl_out %s failed, code %d, count %d\n", + what, retval, i); + + kfree(buf - offset); + return retval; +} + +/*-------------------------------------------------------------------------*/ + +/* ISO/BULK tests ... mimics common usage + * - buffer length is split into N packets (mostly maxpacket sized) + * - multi-buffers according to sglen + */ + +struct transfer_context { + unsigned count; + unsigned pending; + spinlock_t lock; + struct completion done; + int submit_error; + unsigned long errors; + unsigned long packet_count; + struct usbtest_dev *dev; + bool is_iso; +}; + +static void complicated_callback(struct urb *urb) +{ + struct transfer_context *ctx = urb->context; + unsigned long flags; + + spin_lock_irqsave(&ctx->lock, flags); + ctx->count--; + + ctx->packet_count += urb->number_of_packets; + if (urb->error_count > 0) + ctx->errors += urb->error_count; + else if (urb->status != 0) + ctx->errors += (ctx->is_iso ? urb->number_of_packets : 1); + else if (urb->actual_length != urb->transfer_buffer_length) + ctx->errors++; + else if (check_guard_bytes(ctx->dev, urb) != 0) + ctx->errors++; + + if (urb->status == 0 && ctx->count > (ctx->pending - 1) + && !ctx->submit_error) { + int status = usb_submit_urb(urb, GFP_ATOMIC); + switch (status) { + case 0: + goto done; + default: + dev_err(&ctx->dev->intf->dev, + "resubmit err %d\n", + status); + fallthrough; + case -ENODEV: /* disconnected */ + case -ESHUTDOWN: /* endpoint disabled */ + ctx->submit_error = 1; + break; + } + } + + ctx->pending--; + if (ctx->pending == 0) { + if (ctx->errors) + dev_err(&ctx->dev->intf->dev, + "during the test, %lu errors out of %lu\n", + ctx->errors, ctx->packet_count); + complete(&ctx->done); + } +done: + spin_unlock_irqrestore(&ctx->lock, flags); +} + +static struct urb *iso_alloc_urb( + struct usb_device *udev, + int pipe, + struct usb_endpoint_descriptor *desc, + long bytes, + unsigned offset +) +{ + struct urb *urb; + unsigned i, maxp, packets; + + if (bytes < 0 || !desc) + return NULL; + + maxp = usb_endpoint_maxp(desc); + if (udev->speed >= USB_SPEED_SUPER) + maxp *= ss_isoc_get_packet_num(udev, pipe); + else + maxp *= usb_endpoint_maxp_mult(desc); + + packets = DIV_ROUND_UP(bytes, maxp); + + urb = usb_alloc_urb(packets, GFP_KERNEL); + if (!urb) + return urb; + urb->dev = udev; + urb->pipe = pipe; + + urb->number_of_packets = packets; + urb->transfer_buffer_length = bytes; + urb->transfer_buffer = usb_alloc_coherent(udev, bytes + offset, + GFP_KERNEL, + &urb->transfer_dma); + if (!urb->transfer_buffer) { + usb_free_urb(urb); + return NULL; + } + if (offset) { + memset(urb->transfer_buffer, GUARD_BYTE, offset); + urb->transfer_buffer += offset; + urb->transfer_dma += offset; + } + /* For inbound transfers use guard byte so that test fails if + data not correctly copied */ + memset(urb->transfer_buffer, + usb_pipein(urb->pipe) ? GUARD_BYTE : 0, + bytes); + + for (i = 0; i < packets; i++) { + /* here, only the last packet will be short */ + urb->iso_frame_desc[i].length = min((unsigned) bytes, maxp); + bytes -= urb->iso_frame_desc[i].length; + + urb->iso_frame_desc[i].offset = maxp * i; + } + + urb->complete = complicated_callback; + /* urb->context = SET BY CALLER */ + urb->interval = 1 << (desc->bInterval - 1); + urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP; + return urb; +} + +static int +test_queue(struct usbtest_dev *dev, struct usbtest_param_32 *param, + int pipe, struct usb_endpoint_descriptor *desc, unsigned offset) +{ + struct transfer_context context; + struct usb_device *udev; + unsigned i; + unsigned long packets = 0; + int status = 0; + struct urb **urbs; + + if (!param->sglen || param->iterations > UINT_MAX / param->sglen) + return -EINVAL; + + if (param->sglen > MAX_SGLEN) + return -EINVAL; + + urbs = kcalloc(param->sglen, sizeof(*urbs), GFP_KERNEL); + if (!urbs) + return -ENOMEM; + + memset(&context, 0, sizeof(context)); + context.count = param->iterations * param->sglen; + context.dev = dev; + context.is_iso = !!desc; + init_completion(&context.done); + spin_lock_init(&context.lock); + + udev = testdev_to_usbdev(dev); + + for (i = 0; i < param->sglen; i++) { + if (context.is_iso) + urbs[i] = iso_alloc_urb(udev, pipe, desc, + param->length, offset); + else + urbs[i] = complicated_alloc_urb(udev, pipe, + param->length, 0); + + if (!urbs[i]) { + status = -ENOMEM; + goto fail; + } + packets += urbs[i]->number_of_packets; + urbs[i]->context = &context; + } + packets *= param->iterations; + + if (context.is_iso) { + int transaction_num; + + if (udev->speed >= USB_SPEED_SUPER) + transaction_num = ss_isoc_get_packet_num(udev, pipe); + else + transaction_num = usb_endpoint_maxp_mult(desc); + + dev_info(&dev->intf->dev, + "iso period %d %sframes, wMaxPacket %d, transactions: %d\n", + 1 << (desc->bInterval - 1), + (udev->speed >= USB_SPEED_HIGH) ? "micro" : "", + usb_endpoint_maxp(desc), + transaction_num); + + dev_info(&dev->intf->dev, + "total %lu msec (%lu packets)\n", + (packets * (1 << (desc->bInterval - 1))) + / ((udev->speed >= USB_SPEED_HIGH) ? 8 : 1), + packets); + } + + spin_lock_irq(&context.lock); + for (i = 0; i < param->sglen; i++) { + ++context.pending; + status = usb_submit_urb(urbs[i], GFP_ATOMIC); + if (status < 0) { + ERROR(dev, "submit iso[%d], error %d\n", i, status); + if (i == 0) { + spin_unlock_irq(&context.lock); + goto fail; + } + + simple_free_urb(urbs[i]); + urbs[i] = NULL; + context.pending--; + context.submit_error = 1; + break; + } + } + spin_unlock_irq(&context.lock); + + wait_for_completion(&context.done); + + for (i = 0; i < param->sglen; i++) { + if (urbs[i]) + simple_free_urb(urbs[i]); + } + /* + * Isochronous transfers are expected to fail sometimes. As an + * arbitrary limit, we will report an error if any submissions + * fail or if the transfer failure rate is > 10%. + */ + if (status != 0) + ; + else if (context.submit_error) + status = -EACCES; + else if (context.errors > + (context.is_iso ? context.packet_count / 10 : 0)) + status = -EIO; + + kfree(urbs); + return status; + +fail: + for (i = 0; i < param->sglen; i++) { + if (urbs[i]) + simple_free_urb(urbs[i]); + } + + kfree(urbs); + return status; +} + +static int test_unaligned_bulk( + struct usbtest_dev *tdev, + int pipe, + unsigned length, + int iterations, + unsigned transfer_flags, + const char *label) +{ + int retval; + struct urb *urb = usbtest_alloc_urb(testdev_to_usbdev(tdev), + pipe, length, transfer_flags, 1, 0, simple_callback); + + if (!urb) + return -ENOMEM; + + retval = simple_io(tdev, urb, iterations, 0, 0, label); + simple_free_urb(urb); + return retval; +} + +/* Run tests. */ +static int +usbtest_do_ioctl(struct usb_interface *intf, struct usbtest_param_32 *param) +{ + struct usbtest_dev *dev = usb_get_intfdata(intf); + struct usb_device *udev = testdev_to_usbdev(dev); + struct urb *urb; + struct scatterlist *sg; + struct usb_sg_request req; + unsigned i; + int retval = -EOPNOTSUPP; + + if (param->iterations <= 0) + return -EINVAL; + if (param->sglen > MAX_SGLEN) + return -EINVAL; + /* + * Just a bunch of test cases that every HCD is expected to handle. + * + * Some may need specific firmware, though it'd be good to have + * one firmware image to handle all the test cases. + * + * FIXME add more tests! cancel requests, verify the data, control + * queueing, concurrent read+write threads, and so on. + */ + switch (param->test_num) { + + case 0: + dev_info(&intf->dev, "TEST 0: NOP\n"); + retval = 0; + break; + + /* Simple non-queued bulk I/O tests */ + case 1: + if (dev->out_pipe == 0) + break; + dev_info(&intf->dev, + "TEST 1: write %d bytes %u times\n", + param->length, param->iterations); + urb = simple_alloc_urb(udev, dev->out_pipe, param->length, 0); + if (!urb) { + retval = -ENOMEM; + break; + } + /* FIRMWARE: bulk sink (maybe accepts short writes) */ + retval = simple_io(dev, urb, param->iterations, 0, 0, "test1"); + simple_free_urb(urb); + break; + case 2: + if (dev->in_pipe == 0) + break; + dev_info(&intf->dev, + "TEST 2: read %d bytes %u times\n", + param->length, param->iterations); + urb = simple_alloc_urb(udev, dev->in_pipe, param->length, 0); + if (!urb) { + retval = -ENOMEM; + break; + } + /* FIRMWARE: bulk source (maybe generates short writes) */ + retval = simple_io(dev, urb, param->iterations, 0, 0, "test2"); + simple_free_urb(urb); + break; + case 3: + if (dev->out_pipe == 0 || param->vary == 0) + break; + dev_info(&intf->dev, + "TEST 3: write/%d 0..%d bytes %u times\n", + param->vary, param->length, param->iterations); + urb = simple_alloc_urb(udev, dev->out_pipe, param->length, 0); + if (!urb) { + retval = -ENOMEM; + break; + } + /* FIRMWARE: bulk sink (maybe accepts short writes) */ + retval = simple_io(dev, urb, param->iterations, param->vary, + 0, "test3"); + simple_free_urb(urb); + break; + case 4: + if (dev->in_pipe == 0 || param->vary == 0) + break; + dev_info(&intf->dev, + "TEST 4: read/%d 0..%d bytes %u times\n", + param->vary, param->length, param->iterations); + urb = simple_alloc_urb(udev, dev->in_pipe, param->length, 0); + if (!urb) { + retval = -ENOMEM; + break; + } + /* FIRMWARE: bulk source (maybe generates short writes) */ + retval = simple_io(dev, urb, param->iterations, param->vary, + 0, "test4"); + simple_free_urb(urb); + break; + + /* Queued bulk I/O tests */ + case 5: + if (dev->out_pipe == 0 || param->sglen == 0) + break; + dev_info(&intf->dev, + "TEST 5: write %d sglists %d entries of %d bytes\n", + param->iterations, + param->sglen, param->length); + sg = alloc_sglist(param->sglen, param->length, + 0, dev, dev->out_pipe); + if (!sg) { + retval = -ENOMEM; + break; + } + /* FIRMWARE: bulk sink (maybe accepts short writes) */ + retval = perform_sglist(dev, param->iterations, dev->out_pipe, + &req, sg, param->sglen); + free_sglist(sg, param->sglen); + break; + + case 6: + if (dev->in_pipe == 0 || param->sglen == 0) + break; + dev_info(&intf->dev, + "TEST 6: read %d sglists %d entries of %d bytes\n", + param->iterations, + param->sglen, param->length); + sg = alloc_sglist(param->sglen, param->length, + 0, dev, dev->in_pipe); + if (!sg) { + retval = -ENOMEM; + break; + } + /* FIRMWARE: bulk source (maybe generates short writes) */ + retval = perform_sglist(dev, param->iterations, dev->in_pipe, + &req, sg, param->sglen); + free_sglist(sg, param->sglen); + break; + case 7: + if (dev->out_pipe == 0 || param->sglen == 0 || param->vary == 0) + break; + dev_info(&intf->dev, + "TEST 7: write/%d %d sglists %d entries 0..%d bytes\n", + param->vary, param->iterations, + param->sglen, param->length); + sg = alloc_sglist(param->sglen, param->length, + param->vary, dev, dev->out_pipe); + if (!sg) { + retval = -ENOMEM; + break; + } + /* FIRMWARE: bulk sink (maybe accepts short writes) */ + retval = perform_sglist(dev, param->iterations, dev->out_pipe, + &req, sg, param->sglen); + free_sglist(sg, param->sglen); + break; + case 8: + if (dev->in_pipe == 0 || param->sglen == 0 || param->vary == 0) + break; + dev_info(&intf->dev, + "TEST 8: read/%d %d sglists %d entries 0..%d bytes\n", + param->vary, param->iterations, + param->sglen, param->length); + sg = alloc_sglist(param->sglen, param->length, + param->vary, dev, dev->in_pipe); + if (!sg) { + retval = -ENOMEM; + break; + } + /* FIRMWARE: bulk source (maybe generates short writes) */ + retval = perform_sglist(dev, param->iterations, dev->in_pipe, + &req, sg, param->sglen); + free_sglist(sg, param->sglen); + break; + + /* non-queued sanity tests for control (chapter 9 subset) */ + case 9: + retval = 0; + dev_info(&intf->dev, + "TEST 9: ch9 (subset) control tests, %d times\n", + param->iterations); + for (i = param->iterations; retval == 0 && i--; /* NOP */) + retval = ch9_postconfig(dev); + if (retval) + dev_err(&intf->dev, "ch9 subset failed, " + "iterations left %d\n", i); + break; + + /* queued control messaging */ + case 10: + retval = 0; + dev_info(&intf->dev, + "TEST 10: queue %d control calls, %d times\n", + param->sglen, + param->iterations); + retval = test_ctrl_queue(dev, param); + break; + + /* simple non-queued unlinks (ring with one urb) */ + case 11: + if (dev->in_pipe == 0 || !param->length) + break; + retval = 0; + dev_info(&intf->dev, "TEST 11: unlink %d reads of %d\n", + param->iterations, param->length); + for (i = param->iterations; retval == 0 && i--; /* NOP */) + retval = unlink_simple(dev, dev->in_pipe, + param->length); + if (retval) + dev_err(&intf->dev, "unlink reads failed %d, " + "iterations left %d\n", retval, i); + break; + case 12: + if (dev->out_pipe == 0 || !param->length) + break; + retval = 0; + dev_info(&intf->dev, "TEST 12: unlink %d writes of %d\n", + param->iterations, param->length); + for (i = param->iterations; retval == 0 && i--; /* NOP */) + retval = unlink_simple(dev, dev->out_pipe, + param->length); + if (retval) + dev_err(&intf->dev, "unlink writes failed %d, " + "iterations left %d\n", retval, i); + break; + + /* ep halt tests */ + case 13: + if (dev->out_pipe == 0 && dev->in_pipe == 0) + break; + retval = 0; + dev_info(&intf->dev, "TEST 13: set/clear %d halts\n", + param->iterations); + for (i = param->iterations; retval == 0 && i--; /* NOP */) + retval = halt_simple(dev); + + if (retval) + ERROR(dev, "halts failed, iterations left %d\n", i); + break; + + /* control write tests */ + case 14: + if (!dev->info->ctrl_out) + break; + dev_info(&intf->dev, "TEST 14: %d ep0out, %d..%d vary %d\n", + param->iterations, + realworld ? 1 : 0, param->length, + param->vary); + retval = ctrl_out(dev, param->iterations, + param->length, param->vary, 0); + break; + + /* iso write tests */ + case 15: + if (dev->out_iso_pipe == 0 || param->sglen == 0) + break; + dev_info(&intf->dev, + "TEST 15: write %d iso, %d entries of %d bytes\n", + param->iterations, + param->sglen, param->length); + /* FIRMWARE: iso sink */ + retval = test_queue(dev, param, + dev->out_iso_pipe, dev->iso_out, 0); + break; + + /* iso read tests */ + case 16: + if (dev->in_iso_pipe == 0 || param->sglen == 0) + break; + dev_info(&intf->dev, + "TEST 16: read %d iso, %d entries of %d bytes\n", + param->iterations, + param->sglen, param->length); + /* FIRMWARE: iso source */ + retval = test_queue(dev, param, + dev->in_iso_pipe, dev->iso_in, 0); + break; + + /* FIXME scatterlist cancel (needs helper thread) */ + + /* Tests for bulk I/O using DMA mapping by core and odd address */ + case 17: + if (dev->out_pipe == 0) + break; + dev_info(&intf->dev, + "TEST 17: write odd addr %d bytes %u times core map\n", + param->length, param->iterations); + + retval = test_unaligned_bulk( + dev, dev->out_pipe, + param->length, param->iterations, + 0, "test17"); + break; + + case 18: + if (dev->in_pipe == 0) + break; + dev_info(&intf->dev, + "TEST 18: read odd addr %d bytes %u times core map\n", + param->length, param->iterations); + + retval = test_unaligned_bulk( + dev, dev->in_pipe, + param->length, param->iterations, + 0, "test18"); + break; + + /* Tests for bulk I/O using premapped coherent buffer and odd address */ + case 19: + if (dev->out_pipe == 0) + break; + dev_info(&intf->dev, + "TEST 19: write odd addr %d bytes %u times premapped\n", + param->length, param->iterations); + + retval = test_unaligned_bulk( + dev, dev->out_pipe, + param->length, param->iterations, + URB_NO_TRANSFER_DMA_MAP, "test19"); + break; + + case 20: + if (dev->in_pipe == 0) + break; + dev_info(&intf->dev, + "TEST 20: read odd addr %d bytes %u times premapped\n", + param->length, param->iterations); + + retval = test_unaligned_bulk( + dev, dev->in_pipe, + param->length, param->iterations, + URB_NO_TRANSFER_DMA_MAP, "test20"); + break; + + /* control write tests with unaligned buffer */ + case 21: + if (!dev->info->ctrl_out) + break; + dev_info(&intf->dev, + "TEST 21: %d ep0out odd addr, %d..%d vary %d\n", + param->iterations, + realworld ? 1 : 0, param->length, + param->vary); + retval = ctrl_out(dev, param->iterations, + param->length, param->vary, 1); + break; + + /* unaligned iso tests */ + case 22: + if (dev->out_iso_pipe == 0 || param->sglen == 0) + break; + dev_info(&intf->dev, + "TEST 22: write %d iso odd, %d entries of %d bytes\n", + param->iterations, + param->sglen, param->length); + retval = test_queue(dev, param, + dev->out_iso_pipe, dev->iso_out, 1); + break; + + case 23: + if (dev->in_iso_pipe == 0 || param->sglen == 0) + break; + dev_info(&intf->dev, + "TEST 23: read %d iso odd, %d entries of %d bytes\n", + param->iterations, + param->sglen, param->length); + retval = test_queue(dev, param, + dev->in_iso_pipe, dev->iso_in, 1); + break; + + /* unlink URBs from a bulk-OUT queue */ + case 24: + if (dev->out_pipe == 0 || !param->length || param->sglen < 4) + break; + retval = 0; + dev_info(&intf->dev, "TEST 24: unlink from %d queues of " + "%d %d-byte writes\n", + param->iterations, param->sglen, param->length); + for (i = param->iterations; retval == 0 && i > 0; --i) { + retval = unlink_queued(dev, dev->out_pipe, + param->sglen, param->length); + if (retval) { + dev_err(&intf->dev, + "unlink queued writes failed %d, " + "iterations left %d\n", retval, i); + break; + } + } + break; + + /* Simple non-queued interrupt I/O tests */ + case 25: + if (dev->out_int_pipe == 0) + break; + dev_info(&intf->dev, + "TEST 25: write %d bytes %u times\n", + param->length, param->iterations); + urb = simple_alloc_urb(udev, dev->out_int_pipe, param->length, + dev->int_out->bInterval); + if (!urb) { + retval = -ENOMEM; + break; + } + /* FIRMWARE: interrupt sink (maybe accepts short writes) */ + retval = simple_io(dev, urb, param->iterations, 0, 0, "test25"); + simple_free_urb(urb); + break; + case 26: + if (dev->in_int_pipe == 0) + break; + dev_info(&intf->dev, + "TEST 26: read %d bytes %u times\n", + param->length, param->iterations); + urb = simple_alloc_urb(udev, dev->in_int_pipe, param->length, + dev->int_in->bInterval); + if (!urb) { + retval = -ENOMEM; + break; + } + /* FIRMWARE: interrupt source (maybe generates short writes) */ + retval = simple_io(dev, urb, param->iterations, 0, 0, "test26"); + simple_free_urb(urb); + break; + case 27: + /* We do performance test, so ignore data compare */ + if (dev->out_pipe == 0 || param->sglen == 0 || pattern != 0) + break; + dev_info(&intf->dev, + "TEST 27: bulk write %dMbytes\n", (param->iterations * + param->sglen * param->length) / (1024 * 1024)); + retval = test_queue(dev, param, + dev->out_pipe, NULL, 0); + break; + case 28: + if (dev->in_pipe == 0 || param->sglen == 0 || pattern != 0) + break; + dev_info(&intf->dev, + "TEST 28: bulk read %dMbytes\n", (param->iterations * + param->sglen * param->length) / (1024 * 1024)); + retval = test_queue(dev, param, + dev->in_pipe, NULL, 0); + break; + /* Test data Toggle/seq_nr clear between bulk out transfers */ + case 29: + if (dev->out_pipe == 0) + break; + retval = 0; + dev_info(&intf->dev, "TEST 29: Clear toggle between bulk writes %d times\n", + param->iterations); + for (i = param->iterations; retval == 0 && i > 0; --i) + retval = toggle_sync_simple(dev); + + if (retval) + ERROR(dev, "toggle sync failed, iterations left %d\n", + i); + break; + } + return retval; +} + +/*-------------------------------------------------------------------------*/ + +/* We only have this one interface to user space, through usbfs. + * User mode code can scan usbfs to find N different devices (maybe on + * different busses) to use when testing, and allocate one thread per + * test. So discovery is simplified, and we have no device naming issues. + * + * Don't use these only as stress/load tests. Use them along with + * other USB bus activity: plugging, unplugging, mousing, mp3 playback, + * video capture, and so on. Run different tests at different times, in + * different sequences. Nothing here should interact with other devices, + * except indirectly by consuming USB bandwidth and CPU resources for test + * threads and request completion. But the only way to know that for sure + * is to test when HC queues are in use by many devices. + * + * WARNING: Because usbfs grabs udev->dev.sem before calling this ioctl(), + * it locks out usbcore in certain code paths. Notably, if you disconnect + * the device-under-test, hub_wq will wait block forever waiting for the + * ioctl to complete ... so that usb_disconnect() can abort the pending + * urbs and then call usbtest_disconnect(). To abort a test, you're best + * off just killing the userspace task and waiting for it to exit. + */ + +static int +usbtest_ioctl(struct usb_interface *intf, unsigned int code, void *buf) +{ + + struct usbtest_dev *dev = usb_get_intfdata(intf); + struct usbtest_param_64 *param_64 = buf; + struct usbtest_param_32 temp; + struct usbtest_param_32 *param_32 = buf; + struct timespec64 start; + struct timespec64 end; + struct timespec64 duration; + int retval = -EOPNOTSUPP; + + /* FIXME USBDEVFS_CONNECTINFO doesn't say how fast the device is. */ + + pattern = mod_pattern; + + if (mutex_lock_interruptible(&dev->lock)) + return -ERESTARTSYS; + + /* FIXME: What if a system sleep starts while a test is running? */ + + /* some devices, like ez-usb default devices, need a non-default + * altsetting to have any active endpoints. some tests change + * altsettings; force a default so most tests don't need to check. + */ + if (dev->info->alt >= 0) { + if (intf->altsetting->desc.bInterfaceNumber) { + retval = -ENODEV; + goto free_mutex; + } + retval = set_altsetting(dev, dev->info->alt); + if (retval) { + dev_err(&intf->dev, + "set altsetting to %d failed, %d\n", + dev->info->alt, retval); + goto free_mutex; + } + } + + switch (code) { + case USBTEST_REQUEST_64: + temp.test_num = param_64->test_num; + temp.iterations = param_64->iterations; + temp.length = param_64->length; + temp.sglen = param_64->sglen; + temp.vary = param_64->vary; + param_32 = &temp; + break; + + case USBTEST_REQUEST_32: + break; + + default: + retval = -EOPNOTSUPP; + goto free_mutex; + } + + ktime_get_ts64(&start); + + retval = usbtest_do_ioctl(intf, param_32); + if (retval < 0) + goto free_mutex; + + ktime_get_ts64(&end); + + duration = timespec64_sub(end, start); + + temp.duration_sec = duration.tv_sec; + temp.duration_usec = duration.tv_nsec/NSEC_PER_USEC; + + switch (code) { + case USBTEST_REQUEST_32: + param_32->duration_sec = temp.duration_sec; + param_32->duration_usec = temp.duration_usec; + break; + + case USBTEST_REQUEST_64: + param_64->duration_sec = temp.duration_sec; + param_64->duration_usec = temp.duration_usec; + break; + } + +free_mutex: + mutex_unlock(&dev->lock); + return retval; +} + +/*-------------------------------------------------------------------------*/ + +static unsigned force_interrupt; +module_param(force_interrupt, uint, 0); +MODULE_PARM_DESC(force_interrupt, "0 = test default; else interrupt"); + +#ifdef GENERIC +static unsigned short vendor; +module_param(vendor, ushort, 0); +MODULE_PARM_DESC(vendor, "vendor code (from usb-if)"); + +static unsigned short product; +module_param(product, ushort, 0); +MODULE_PARM_DESC(product, "product code (from vendor)"); +#endif + +static int +usbtest_probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + struct usb_device *udev; + struct usbtest_dev *dev; + struct usbtest_info *info; + char *rtest, *wtest; + char *irtest, *iwtest; + char *intrtest, *intwtest; + + udev = interface_to_usbdev(intf); + +#ifdef GENERIC + /* specify devices by module parameters? */ + if (id->match_flags == 0) { + /* vendor match required, product match optional */ + if (!vendor || le16_to_cpu(udev->descriptor.idVendor) != (u16)vendor) + return -ENODEV; + if (product && le16_to_cpu(udev->descriptor.idProduct) != (u16)product) + return -ENODEV; + dev_info(&intf->dev, "matched module params, " + "vend=0x%04x prod=0x%04x\n", + le16_to_cpu(udev->descriptor.idVendor), + le16_to_cpu(udev->descriptor.idProduct)); + } +#endif + + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + return -ENOMEM; + info = (struct usbtest_info *) id->driver_info; + dev->info = info; + mutex_init(&dev->lock); + + dev->intf = intf; + + /* cacheline-aligned scratch for i/o */ + dev->buf = kmalloc(TBUF_SIZE, GFP_KERNEL); + if (dev->buf == NULL) { + kfree(dev); + return -ENOMEM; + } + + /* NOTE this doesn't yet test the handful of difference that are + * visible with high speed interrupts: bigger maxpacket (1K) and + * "high bandwidth" modes (up to 3 packets/uframe). + */ + rtest = wtest = ""; + irtest = iwtest = ""; + intrtest = intwtest = ""; + if (force_interrupt || udev->speed == USB_SPEED_LOW) { + if (info->ep_in) { + dev->in_pipe = usb_rcvintpipe(udev, info->ep_in); + rtest = " intr-in"; + } + if (info->ep_out) { + dev->out_pipe = usb_sndintpipe(udev, info->ep_out); + wtest = " intr-out"; + } + } else { + if (override_alt >= 0 || info->autoconf) { + int status; + + status = get_endpoints(dev, intf); + if (status < 0) { + WARNING(dev, "couldn't get endpoints, %d\n", + status); + kfree(dev->buf); + kfree(dev); + return status; + } + /* may find bulk or ISO pipes */ + } else { + if (info->ep_in) + dev->in_pipe = usb_rcvbulkpipe(udev, + info->ep_in); + if (info->ep_out) + dev->out_pipe = usb_sndbulkpipe(udev, + info->ep_out); + } + if (dev->in_pipe) + rtest = " bulk-in"; + if (dev->out_pipe) + wtest = " bulk-out"; + if (dev->in_iso_pipe) + irtest = " iso-in"; + if (dev->out_iso_pipe) + iwtest = " iso-out"; + if (dev->in_int_pipe) + intrtest = " int-in"; + if (dev->out_int_pipe) + intwtest = " int-out"; + } + + usb_set_intfdata(intf, dev); + dev_info(&intf->dev, "%s\n", info->name); + dev_info(&intf->dev, "%s {control%s%s%s%s%s%s%s} tests%s\n", + usb_speed_string(udev->speed), + info->ctrl_out ? " in/out" : "", + rtest, wtest, + irtest, iwtest, + intrtest, intwtest, + info->alt >= 0 ? " (+alt)" : ""); + return 0; +} + +static int usbtest_suspend(struct usb_interface *intf, pm_message_t message) +{ + return 0; +} + +static int usbtest_resume(struct usb_interface *intf) +{ + return 0; +} + + +static void usbtest_disconnect(struct usb_interface *intf) +{ + struct usbtest_dev *dev = usb_get_intfdata(intf); + + usb_set_intfdata(intf, NULL); + dev_dbg(&intf->dev, "disconnect\n"); + kfree(dev->buf); + kfree(dev); +} + +/* Basic testing only needs a device that can source or sink bulk traffic. + * Any device can test control transfers (default with GENERIC binding). + * + * Several entries work with the default EP0 implementation that's built + * into EZ-USB chips. There's a default vendor ID which can be overridden + * by (very) small config EEPROMS, but otherwise all these devices act + * identically until firmware is loaded: only EP0 works. It turns out + * to be easy to make other endpoints work, without modifying that EP0 + * behavior. For now, we expect that kind of firmware. + */ + +/* an21xx or fx versions of ez-usb */ +static struct usbtest_info ez1_info = { + .name = "EZ-USB device", + .ep_in = 2, + .ep_out = 2, + .alt = 1, +}; + +/* fx2 version of ez-usb */ +static struct usbtest_info ez2_info = { + .name = "FX2 device", + .ep_in = 6, + .ep_out = 2, + .alt = 1, +}; + +/* ezusb family device with dedicated usb test firmware, + */ +static struct usbtest_info fw_info = { + .name = "usb test device", + .ep_in = 2, + .ep_out = 2, + .alt = 1, + .autoconf = 1, /* iso and ctrl_out need autoconf */ + .ctrl_out = 1, + .iso = 1, /* iso_ep's are #8 in/out */ +}; + +/* peripheral running Linux and 'zero.c' test firmware, or + * its user-mode cousin. different versions of this use + * different hardware with the same vendor/product codes. + * host side MUST rely on the endpoint descriptors. + */ +static struct usbtest_info gz_info = { + .name = "Linux gadget zero", + .autoconf = 1, + .ctrl_out = 1, + .iso = 1, + .intr = 1, + .alt = 0, +}; + +static struct usbtest_info um_info = { + .name = "Linux user mode test driver", + .autoconf = 1, + .alt = -1, +}; + +static struct usbtest_info um2_info = { + .name = "Linux user mode ISO test driver", + .autoconf = 1, + .iso = 1, + .alt = -1, +}; + +#ifdef IBOT2 +/* this is a nice source of high speed bulk data; + * uses an FX2, with firmware provided in the device + */ +static struct usbtest_info ibot2_info = { + .name = "iBOT2 webcam", + .ep_in = 2, + .alt = -1, +}; +#endif + +#ifdef GENERIC +/* we can use any device to test control traffic */ +static struct usbtest_info generic_info = { + .name = "Generic USB device", + .alt = -1, +}; +#endif + + +static const struct usb_device_id id_table[] = { + + /*-------------------------------------------------------------*/ + + /* EZ-USB devices which download firmware to replace (or in our + * case augment) the default device implementation. + */ + + /* generic EZ-USB FX controller */ + { USB_DEVICE(0x0547, 0x2235), + .driver_info = (unsigned long) &ez1_info, + }, + + /* CY3671 development board with EZ-USB FX */ + { USB_DEVICE(0x0547, 0x0080), + .driver_info = (unsigned long) &ez1_info, + }, + + /* generic EZ-USB FX2 controller (or development board) */ + { USB_DEVICE(0x04b4, 0x8613), + .driver_info = (unsigned long) &ez2_info, + }, + + /* re-enumerated usb test device firmware */ + { USB_DEVICE(0xfff0, 0xfff0), + .driver_info = (unsigned long) &fw_info, + }, + + /* "Gadget Zero" firmware runs under Linux */ + { USB_DEVICE(0x0525, 0xa4a0), + .driver_info = (unsigned long) &gz_info, + }, + + /* so does a user-mode variant */ + { USB_DEVICE(0x0525, 0xa4a4), + .driver_info = (unsigned long) &um_info, + }, + + /* ... and a user-mode variant that talks iso */ + { USB_DEVICE(0x0525, 0xa4a3), + .driver_info = (unsigned long) &um2_info, + }, + +#ifdef KEYSPAN_19Qi + /* Keyspan 19qi uses an21xx (original EZ-USB) */ + /* this does not coexist with the real Keyspan 19qi driver! */ + { USB_DEVICE(0x06cd, 0x010b), + .driver_info = (unsigned long) &ez1_info, + }, +#endif + + /*-------------------------------------------------------------*/ + +#ifdef IBOT2 + /* iBOT2 makes a nice source of high speed bulk-in data */ + /* this does not coexist with a real iBOT2 driver! */ + { USB_DEVICE(0x0b62, 0x0059), + .driver_info = (unsigned long) &ibot2_info, + }, +#endif + + /*-------------------------------------------------------------*/ + +#ifdef GENERIC + /* module params can specify devices to use for control tests */ + { .driver_info = (unsigned long) &generic_info, }, +#endif + + /*-------------------------------------------------------------*/ + + { } +}; +MODULE_DEVICE_TABLE(usb, id_table); + +static struct usb_driver usbtest_driver = { + .name = "usbtest", + .id_table = id_table, + .probe = usbtest_probe, + .unlocked_ioctl = usbtest_ioctl, + .disconnect = usbtest_disconnect, + .suspend = usbtest_suspend, + .resume = usbtest_resume, +}; + +/*-------------------------------------------------------------------------*/ + +static int __init usbtest_init(void) +{ +#ifdef GENERIC + if (vendor) + pr_debug("params: vend=0x%04x prod=0x%04x\n", vendor, product); +#endif + return usb_register(&usbtest_driver); +} +module_init(usbtest_init); + +static void __exit usbtest_exit(void) +{ + usb_deregister(&usbtest_driver); +} +module_exit(usbtest_exit); + +MODULE_DESCRIPTION("USB Core/HCD Testing Driver"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/usb/misc/uss720.c b/drivers/usb/misc/uss720.c new file mode 100644 index 0000000000..b00d92db5d --- /dev/null +++ b/drivers/usb/misc/uss720.c @@ -0,0 +1,824 @@ +// SPDX-License-Identifier: GPL-2.0+ +/*****************************************************************************/ + +/* + * uss720.c -- USS720 USB Parport Cable. + * + * Copyright (C) 1999, 2005, 2010 + * Thomas Sailer (t.sailer@alumni.ethz.ch) + * + * Based on parport_pc.c + * + * History: + * 0.1 04.08.1999 Created + * 0.2 07.08.1999 Some fixes mainly suggested by Tim Waugh + * Interrupt handling currently disabled because + * usb_request_irq crashes somewhere within ohci.c + * for no apparent reason (that is for me, anyway) + * ECP currently untested + * 0.3 10.08.1999 fixing merge errors + * 0.4 13.08.1999 Added Vendor/Product ID of Brad Hard's cable + * 0.5 20.09.1999 usb_control_msg wrapper used + * Nov01.2000 usb_device_table support by Adam J. Richter + * 08.04.2001 Identify version on module load. gb + * 0.6 02.09.2005 Fix "scheduling in interrupt" problem by making save/restore + * context asynchronous + * + */ + +/*****************************************************************************/ + +#include <linux/module.h> +#include <linux/socket.h> +#include <linux/parport.h> +#include <linux/init.h> +#include <linux/usb.h> +#include <linux/delay.h> +#include <linux/completion.h> +#include <linux/kref.h> +#include <linux/slab.h> +#include <linux/sched/signal.h> + +#define DRIVER_AUTHOR "Thomas M. Sailer, t.sailer@alumni.ethz.ch" +#define DRIVER_DESC "USB Parport Cable driver for Cables using the Lucent Technologies USS720 Chip" + +/* --------------------------------------------------------------------- */ + +struct parport_uss720_private { + struct usb_device *usbdev; + struct parport *pp; + struct kref ref_count; + __u8 reg[7]; /* USB registers */ + struct list_head asynclist; + spinlock_t asynclock; +}; + +struct uss720_async_request { + struct parport_uss720_private *priv; + struct kref ref_count; + struct list_head asynclist; + struct completion compl; + struct urb *urb; + struct usb_ctrlrequest *dr; + __u8 reg[7]; +}; + +/* --------------------------------------------------------------------- */ + +static void destroy_priv(struct kref *kref) +{ + struct parport_uss720_private *priv = container_of(kref, struct parport_uss720_private, ref_count); + + dev_dbg(&priv->usbdev->dev, "destroying priv datastructure\n"); + usb_put_dev(priv->usbdev); + priv->usbdev = NULL; + kfree(priv); +} + +static void destroy_async(struct kref *kref) +{ + struct uss720_async_request *rq = container_of(kref, struct uss720_async_request, ref_count); + struct parport_uss720_private *priv = rq->priv; + unsigned long flags; + + if (likely(rq->urb)) + usb_free_urb(rq->urb); + kfree(rq->dr); + spin_lock_irqsave(&priv->asynclock, flags); + list_del_init(&rq->asynclist); + spin_unlock_irqrestore(&priv->asynclock, flags); + kfree(rq); + kref_put(&priv->ref_count, destroy_priv); +} + +/* --------------------------------------------------------------------- */ + +static void async_complete(struct urb *urb) +{ + struct uss720_async_request *rq; + struct parport *pp; + struct parport_uss720_private *priv; + int status = urb->status; + + rq = urb->context; + priv = rq->priv; + pp = priv->pp; + if (status) { + dev_err(&urb->dev->dev, "async_complete: urb error %d\n", + status); + } else if (rq->dr->bRequest == 3) { + memcpy(priv->reg, rq->reg, sizeof(priv->reg)); +#if 0 + dev_dbg(&priv->usbdev->dev, "async_complete regs %7ph\n", + priv->reg); +#endif + /* if nAck interrupts are enabled and we have an interrupt, call the interrupt procedure */ + if (rq->reg[2] & rq->reg[1] & 0x10 && pp) + parport_generic_irq(pp); + } + complete(&rq->compl); + kref_put(&rq->ref_count, destroy_async); +} + +static struct uss720_async_request *submit_async_request(struct parport_uss720_private *priv, + __u8 request, __u8 requesttype, __u16 value, __u16 index, + gfp_t mem_flags) +{ + struct usb_device *usbdev; + struct uss720_async_request *rq; + unsigned long flags; + int ret; + + if (!priv) + return NULL; + usbdev = priv->usbdev; + if (!usbdev) + return NULL; + rq = kzalloc(sizeof(struct uss720_async_request), mem_flags); + if (!rq) + return NULL; + kref_init(&rq->ref_count); + INIT_LIST_HEAD(&rq->asynclist); + init_completion(&rq->compl); + kref_get(&priv->ref_count); + rq->priv = priv; + rq->urb = usb_alloc_urb(0, mem_flags); + if (!rq->urb) { + kref_put(&rq->ref_count, destroy_async); + return NULL; + } + rq->dr = kmalloc(sizeof(*rq->dr), mem_flags); + if (!rq->dr) { + kref_put(&rq->ref_count, destroy_async); + return NULL; + } + rq->dr->bRequestType = requesttype; + rq->dr->bRequest = request; + rq->dr->wValue = cpu_to_le16(value); + rq->dr->wIndex = cpu_to_le16(index); + rq->dr->wLength = cpu_to_le16((request == 3) ? sizeof(rq->reg) : 0); + usb_fill_control_urb(rq->urb, usbdev, (requesttype & 0x80) ? usb_rcvctrlpipe(usbdev, 0) : usb_sndctrlpipe(usbdev, 0), + (unsigned char *)rq->dr, + (request == 3) ? rq->reg : NULL, (request == 3) ? sizeof(rq->reg) : 0, async_complete, rq); + /* rq->urb->transfer_flags |= URB_ASYNC_UNLINK; */ + spin_lock_irqsave(&priv->asynclock, flags); + list_add_tail(&rq->asynclist, &priv->asynclist); + spin_unlock_irqrestore(&priv->asynclock, flags); + kref_get(&rq->ref_count); + ret = usb_submit_urb(rq->urb, mem_flags); + if (!ret) + return rq; + destroy_async(&rq->ref_count); + dev_err(&usbdev->dev, "submit_async_request submit_urb failed with %d\n", ret); + return NULL; +} + +static unsigned int kill_all_async_requests_priv(struct parport_uss720_private *priv) +{ + struct uss720_async_request *rq; + unsigned long flags; + unsigned int ret = 0; + + spin_lock_irqsave(&priv->asynclock, flags); + list_for_each_entry(rq, &priv->asynclist, asynclist) { + usb_unlink_urb(rq->urb); + ret++; + } + spin_unlock_irqrestore(&priv->asynclock, flags); + return ret; +} + +/* --------------------------------------------------------------------- */ + +static int get_1284_register(struct parport *pp, unsigned char reg, unsigned char *val, gfp_t mem_flags) +{ + struct parport_uss720_private *priv; + struct uss720_async_request *rq; + static const unsigned char regindex[9] = { + 4, 0, 1, 5, 5, 0, 2, 3, 6 + }; + int ret; + + if (!pp) + return -EIO; + priv = pp->private_data; + rq = submit_async_request(priv, 3, 0xc0, ((unsigned int)reg) << 8, 0, mem_flags); + if (!rq) { + dev_err(&priv->usbdev->dev, "get_1284_register(%u) failed", + (unsigned int)reg); + return -EIO; + } + if (!val) { + kref_put(&rq->ref_count, destroy_async); + return 0; + } + if (wait_for_completion_timeout(&rq->compl, HZ)) { + ret = rq->urb->status; + *val = priv->reg[(reg >= 9) ? 0 : regindex[reg]]; + if (ret) + printk(KERN_WARNING "get_1284_register: " + "usb error %d\n", ret); + kref_put(&rq->ref_count, destroy_async); + return ret; + } + printk(KERN_WARNING "get_1284_register timeout\n"); + kill_all_async_requests_priv(priv); + return -EIO; +} + +static int set_1284_register(struct parport *pp, unsigned char reg, unsigned char val, gfp_t mem_flags) +{ + struct parport_uss720_private *priv; + struct uss720_async_request *rq; + + if (!pp) + return -EIO; + priv = pp->private_data; + rq = submit_async_request(priv, 4, 0x40, (((unsigned int)reg) << 8) | val, 0, mem_flags); + if (!rq) { + dev_err(&priv->usbdev->dev, "set_1284_register(%u,%u) failed", + (unsigned int)reg, (unsigned int)val); + return -EIO; + } + kref_put(&rq->ref_count, destroy_async); + return 0; +} + +/* --------------------------------------------------------------------- */ + +/* ECR modes */ +#define ECR_SPP 00 +#define ECR_PS2 01 +#define ECR_PPF 02 +#define ECR_ECP 03 +#define ECR_EPP 04 + +/* Safely change the mode bits in the ECR */ +static int change_mode(struct parport *pp, int m) +{ + struct parport_uss720_private *priv = pp->private_data; + int mode; + __u8 reg; + + if (get_1284_register(pp, 6, ®, 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, ®, 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, ®, GFP_KERNEL)) + return -EIO; + return 0; +} + +/* + * Clear TIMEOUT BIT in EPP MODE + */ +static int clear_epp_timeout(struct parport *pp) +{ + unsigned char stat; + + if (get_1284_register(pp, 1, &stat, GFP_KERNEL)) + return 1; + return stat & 1; +} + +/* + * Access functions. + */ +#if 0 +static int uss720_irq(int usbstatus, void *buffer, int len, void *dev_id) +{ + struct parport *pp = (struct parport *)dev_id; + struct parport_uss720_private *priv = pp->private_data; + + if (usbstatus != 0 || len < 4 || !buffer) + return 1; + memcpy(priv->reg, buffer, 4); + /* if nAck interrupts are enabled and we have an interrupt, call the interrupt procedure */ + if (priv->reg[2] & priv->reg[1] & 0x10) + parport_generic_irq(pp); + return 1; +} +#endif + +static void parport_uss720_write_data(struct parport *pp, unsigned char d) +{ + set_1284_register(pp, 0, d, GFP_KERNEL); +} + +static unsigned char parport_uss720_read_data(struct parport *pp) +{ + unsigned char ret; + + if (get_1284_register(pp, 0, &ret, GFP_KERNEL)) + return 0; + return ret; +} + +static void parport_uss720_write_control(struct parport *pp, unsigned char d) +{ + struct parport_uss720_private *priv = pp->private_data; + + d = (d & 0xf) | (priv->reg[1] & 0xf0); + if (set_1284_register(pp, 2, d, GFP_KERNEL)) + return; + priv->reg[1] = d; +} + +static unsigned char parport_uss720_read_control(struct parport *pp) +{ + struct parport_uss720_private *priv = pp->private_data; + return priv->reg[1] & 0xf; /* Use soft copy */ +} + +static unsigned char parport_uss720_frob_control(struct parport *pp, unsigned char mask, unsigned char val) +{ + struct parport_uss720_private *priv = pp->private_data; + unsigned char d; + + mask &= 0x0f; + val &= 0x0f; + d = (priv->reg[1] & (~mask)) ^ val; + if (set_1284_register(pp, 2, d, GFP_ATOMIC)) + return 0; + priv->reg[1] = d; + return d & 0xf; +} + +static unsigned char parport_uss720_read_status(struct parport *pp) +{ + unsigned char ret; + + if (get_1284_register(pp, 1, &ret, GFP_ATOMIC)) + return 0; + return ret & 0xf8; +} + +static void parport_uss720_disable_irq(struct parport *pp) +{ + struct parport_uss720_private *priv = pp->private_data; + unsigned char d; + + d = priv->reg[1] & ~0x10; + if (set_1284_register(pp, 2, d, GFP_KERNEL)) + return; + priv->reg[1] = d; +} + +static void parport_uss720_enable_irq(struct parport *pp) +{ + struct parport_uss720_private *priv = pp->private_data; + unsigned char d; + + d = priv->reg[1] | 0x10; + if (set_1284_register(pp, 2, d, GFP_KERNEL)) + return; + priv->reg[1] = d; +} + +static void parport_uss720_data_forward (struct parport *pp) +{ + struct parport_uss720_private *priv = pp->private_data; + unsigned char d; + + d = priv->reg[1] & ~0x20; + if (set_1284_register(pp, 2, d, GFP_KERNEL)) + return; + priv->reg[1] = d; +} + +static void parport_uss720_data_reverse (struct parport *pp) +{ + struct parport_uss720_private *priv = pp->private_data; + unsigned char d; + + d = priv->reg[1] | 0x20; + if (set_1284_register(pp, 2, d, GFP_KERNEL)) + return; + priv->reg[1] = d; +} + +static void parport_uss720_init_state(struct pardevice *dev, struct parport_state *s) +{ + s->u.pc.ctr = 0xc | (dev->irq_func ? 0x10 : 0x0); + s->u.pc.ecr = 0x24; +} + +static void parport_uss720_save_state(struct parport *pp, struct parport_state *s) +{ + struct parport_uss720_private *priv = pp->private_data; + +#if 0 + if (get_1284_register(pp, 2, NULL, GFP_ATOMIC)) + return; +#endif + s->u.pc.ctr = priv->reg[1]; + s->u.pc.ecr = priv->reg[2]; +} + +static void parport_uss720_restore_state(struct parport *pp, struct parport_state *s) +{ + struct parport_uss720_private *priv = pp->private_data; + + set_1284_register(pp, 2, s->u.pc.ctr, GFP_ATOMIC); + set_1284_register(pp, 6, s->u.pc.ecr, GFP_ATOMIC); + get_1284_register(pp, 2, NULL, GFP_ATOMIC); + priv->reg[1] = s->u.pc.ctr; + priv->reg[2] = s->u.pc.ecr; +} + +static size_t parport_uss720_epp_read_data(struct parport *pp, void *buf, size_t length, int flags) +{ + struct parport_uss720_private *priv = pp->private_data; + size_t got = 0; + + if (change_mode(pp, ECR_EPP)) + return 0; + for (; got < length; got++) { + if (get_1284_register(pp, 4, (char *)buf, GFP_KERNEL)) + break; + buf++; + if (priv->reg[0] & 0x01) { + clear_epp_timeout(pp); + break; + } + } + change_mode(pp, ECR_PS2); + return got; +} + +static size_t parport_uss720_epp_write_data(struct parport *pp, const void *buf, size_t length, int flags) +{ +#if 0 + struct parport_uss720_private *priv = pp->private_data; + size_t written = 0; + + if (change_mode(pp, ECR_EPP)) + return 0; + for (; written < length; written++) { + if (set_1284_register(pp, 4, (char *)buf, GFP_KERNEL)) + break; + ((char*)buf)++; + if (get_1284_register(pp, 1, NULL, GFP_KERNEL)) + break; + if (priv->reg[0] & 0x01) { + clear_epp_timeout(pp); + break; + } + } + change_mode(pp, ECR_PS2); + return written; +#else + struct parport_uss720_private *priv = pp->private_data; + struct usb_device *usbdev = priv->usbdev; + int rlen = 0; + int i; + + if (!usbdev) + return 0; + if (change_mode(pp, ECR_EPP)) + return 0; + i = usb_bulk_msg(usbdev, usb_sndbulkpipe(usbdev, 1), (void *)buf, length, &rlen, 20000); + if (i) + printk(KERN_ERR "uss720: sendbulk ep 1 buf %p len %zu rlen %u\n", buf, length, rlen); + change_mode(pp, ECR_PS2); + return rlen; +#endif +} + +static size_t parport_uss720_epp_read_addr(struct parport *pp, void *buf, size_t length, int flags) +{ + struct parport_uss720_private *priv = pp->private_data; + size_t got = 0; + + if (change_mode(pp, ECR_EPP)) + return 0; + for (; got < length; got++) { + if (get_1284_register(pp, 3, (char *)buf, GFP_KERNEL)) + break; + buf++; + if (priv->reg[0] & 0x01) { + clear_epp_timeout(pp); + break; + } + } + change_mode(pp, ECR_PS2); + return got; +} + +static size_t parport_uss720_epp_write_addr(struct parport *pp, const void *buf, size_t length, int flags) +{ + struct parport_uss720_private *priv = pp->private_data; + size_t written = 0; + + if (change_mode(pp, ECR_EPP)) + return 0; + for (; written < length; written++) { + if (set_1284_register(pp, 3, *(char *)buf, GFP_KERNEL)) + break; + buf++; + if (get_1284_register(pp, 1, NULL, GFP_KERNEL)) + break; + if (priv->reg[0] & 0x01) { + clear_epp_timeout(pp); + break; + } + } + change_mode(pp, ECR_PS2); + return written; +} + +static size_t parport_uss720_ecp_write_data(struct parport *pp, const void *buffer, size_t len, int flags) +{ + struct parport_uss720_private *priv = pp->private_data; + struct usb_device *usbdev = priv->usbdev; + int rlen = 0; + int i; + + if (!usbdev) + return 0; + if (change_mode(pp, ECR_ECP)) + return 0; + i = usb_bulk_msg(usbdev, usb_sndbulkpipe(usbdev, 1), (void *)buffer, len, &rlen, 20000); + if (i) + printk(KERN_ERR "uss720: sendbulk ep 1 buf %p len %zu rlen %u\n", buffer, len, rlen); + change_mode(pp, ECR_PS2); + return rlen; +} + +static size_t parport_uss720_ecp_read_data(struct parport *pp, void *buffer, size_t len, int flags) +{ + struct parport_uss720_private *priv = pp->private_data; + struct usb_device *usbdev = priv->usbdev; + int rlen = 0; + int i; + + if (!usbdev) + return 0; + if (change_mode(pp, ECR_ECP)) + return 0; + i = usb_bulk_msg(usbdev, usb_rcvbulkpipe(usbdev, 2), buffer, len, &rlen, 20000); + if (i) + printk(KERN_ERR "uss720: recvbulk ep 2 buf %p len %zu rlen %u\n", buffer, len, rlen); + change_mode(pp, ECR_PS2); + return rlen; +} + +static size_t parport_uss720_ecp_write_addr(struct parport *pp, const void *buffer, size_t len, int flags) +{ + size_t written = 0; + + if (change_mode(pp, ECR_ECP)) + return 0; + for (; written < len; written++) { + if (set_1284_register(pp, 5, *(char *)buffer, GFP_KERNEL)) + break; + buffer++; + } + change_mode(pp, ECR_PS2); + return written; +} + +static size_t parport_uss720_write_compat(struct parport *pp, const void *buffer, size_t len, int flags) +{ + struct parport_uss720_private *priv = pp->private_data; + struct usb_device *usbdev = priv->usbdev; + int rlen = 0; + int i; + + if (!usbdev) + return 0; + if (change_mode(pp, ECR_PPF)) + return 0; + i = usb_bulk_msg(usbdev, usb_sndbulkpipe(usbdev, 1), (void *)buffer, len, &rlen, 20000); + if (i) + printk(KERN_ERR "uss720: sendbulk ep 1 buf %p len %zu rlen %u\n", buffer, len, rlen); + change_mode(pp, ECR_PS2); + return rlen; +} + +/* --------------------------------------------------------------------- */ + +static struct parport_operations parport_uss720_ops = +{ + .owner = THIS_MODULE, + .write_data = parport_uss720_write_data, + .read_data = parport_uss720_read_data, + + .write_control = parport_uss720_write_control, + .read_control = parport_uss720_read_control, + .frob_control = parport_uss720_frob_control, + + .read_status = parport_uss720_read_status, + + .enable_irq = parport_uss720_enable_irq, + .disable_irq = parport_uss720_disable_irq, + + .data_forward = parport_uss720_data_forward, + .data_reverse = parport_uss720_data_reverse, + + .init_state = parport_uss720_init_state, + .save_state = parport_uss720_save_state, + .restore_state = parport_uss720_restore_state, + + .epp_write_data = parport_uss720_epp_write_data, + .epp_read_data = parport_uss720_epp_read_data, + .epp_write_addr = parport_uss720_epp_write_addr, + .epp_read_addr = parport_uss720_epp_read_addr, + + .ecp_write_data = parport_uss720_ecp_write_data, + .ecp_read_data = parport_uss720_ecp_read_data, + .ecp_write_addr = parport_uss720_ecp_write_addr, + + .compat_write_data = parport_uss720_write_compat, + .nibble_read_data = parport_ieee1284_read_nibble, + .byte_read_data = parport_ieee1284_read_byte, +}; + +/* --------------------------------------------------------------------- */ + +static int uss720_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device *usbdev = usb_get_dev(interface_to_usbdev(intf)); + struct usb_host_interface *interface; + struct usb_endpoint_descriptor *epd; + struct parport_uss720_private *priv; + struct parport *pp; + unsigned char reg; + int i; + + dev_dbg(&intf->dev, "probe: vendor id 0x%x, device id 0x%x\n", + le16_to_cpu(usbdev->descriptor.idVendor), + le16_to_cpu(usbdev->descriptor.idProduct)); + + /* our known interfaces have 3 alternate settings */ + if (intf->num_altsetting != 3) { + usb_put_dev(usbdev); + return -ENODEV; + } + i = usb_set_interface(usbdev, intf->altsetting->desc.bInterfaceNumber, 2); + dev_dbg(&intf->dev, "set interface result %d\n", i); + + interface = intf->cur_altsetting; + + if (interface->desc.bNumEndpoints < 3) { + usb_put_dev(usbdev); + return -ENODEV; + } + + /* + * Allocate parport interface + */ + priv = kzalloc(sizeof(struct parport_uss720_private), GFP_KERNEL); + if (!priv) { + usb_put_dev(usbdev); + return -ENOMEM; + } + priv->pp = NULL; + priv->usbdev = usbdev; + kref_init(&priv->ref_count); + spin_lock_init(&priv->asynclock); + INIT_LIST_HEAD(&priv->asynclist); + pp = parport_register_port(0, PARPORT_IRQ_NONE, PARPORT_DMA_NONE, &parport_uss720_ops); + if (!pp) { + printk(KERN_WARNING "uss720: could not register parport\n"); + goto probe_abort; + } + + priv->pp = pp; + pp->private_data = priv; + pp->modes = PARPORT_MODE_PCSPP | PARPORT_MODE_TRISTATE | PARPORT_MODE_EPP | PARPORT_MODE_ECP | PARPORT_MODE_COMPAT; + + /* set the USS720 control register to manual mode, no ECP compression, enable all ints */ + set_1284_register(pp, 7, 0x00, GFP_KERNEL); + set_1284_register(pp, 6, 0x30, GFP_KERNEL); /* PS/2 mode */ + set_1284_register(pp, 2, 0x0c, GFP_KERNEL); + /* debugging */ + get_1284_register(pp, 0, ®, GFP_KERNEL); + dev_dbg(&intf->dev, "reg: %7ph\n", priv->reg); + + i = usb_find_last_int_in_endpoint(interface, &epd); + if (!i) { + dev_dbg(&intf->dev, "epaddr %d interval %d\n", + epd->bEndpointAddress, epd->bInterval); + } + parport_announce_port(pp); + + usb_set_intfdata(intf, pp); + return 0; + +probe_abort: + kill_all_async_requests_priv(priv); + kref_put(&priv->ref_count, destroy_priv); + return -ENODEV; +} + +static void uss720_disconnect(struct usb_interface *intf) +{ + struct parport *pp = usb_get_intfdata(intf); + struct parport_uss720_private *priv; + + dev_dbg(&intf->dev, "disconnect\n"); + usb_set_intfdata(intf, NULL); + if (pp) { + priv = pp->private_data; + priv->pp = NULL; + dev_dbg(&intf->dev, "parport_remove_port\n"); + parport_remove_port(pp); + parport_put_port(pp); + kill_all_async_requests_priv(priv); + kref_put(&priv->ref_count, destroy_priv); + } + dev_dbg(&intf->dev, "disconnect done\n"); +} + +/* table of cables that work through this driver */ +static const struct usb_device_id uss720_table[] = { + { USB_DEVICE(0x047e, 0x1001) }, + { USB_DEVICE(0x04b8, 0x0002) }, + { USB_DEVICE(0x04b8, 0x0003) }, + { USB_DEVICE(0x050d, 0x0002) }, + { USB_DEVICE(0x050d, 0x1202) }, + { USB_DEVICE(0x0557, 0x2001) }, + { USB_DEVICE(0x05ab, 0x0002) }, + { USB_DEVICE(0x06c6, 0x0100) }, + { USB_DEVICE(0x0729, 0x1284) }, + { USB_DEVICE(0x1293, 0x0002) }, + { } /* Terminating entry */ +}; + +MODULE_DEVICE_TABLE (usb, uss720_table); + + +static struct usb_driver uss720_driver = { + .name = "uss720", + .probe = uss720_probe, + .disconnect = uss720_disconnect, + .id_table = uss720_table, +}; + +/* --------------------------------------------------------------------- */ + +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); + +static int __init uss720_init(void) +{ + int retval; + retval = usb_register(&uss720_driver); + if (retval) + goto out; + + printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_DESC "\n"); + printk(KERN_INFO KBUILD_MODNAME ": NOTE: this is a special purpose " + "driver to allow nonstandard\n"); + printk(KERN_INFO KBUILD_MODNAME ": protocols (eg. bitbang) over " + "USS720 usb to parallel cables\n"); + printk(KERN_INFO KBUILD_MODNAME ": If you just want to connect to a " + "printer, use usblp instead\n"); +out: + return retval; +} + +static void __exit uss720_cleanup(void) +{ + usb_deregister(&uss720_driver); +} + +module_init(uss720_init); +module_exit(uss720_cleanup); + +/* --------------------------------------------------------------------- */ diff --git a/drivers/usb/misc/yurex.c b/drivers/usb/misc/yurex.c new file mode 100644 index 0000000000..c640f98d20 --- /dev/null +++ b/drivers/usb/misc/yurex.c @@ -0,0 +1,530 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Driver for Meywa-Denki & KAYAC YUREX + * + * Copyright (C) 2010 Tomoki Sekiyama (tomoki.sekiyama@gmail.com) + */ + +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/kref.h> +#include <linux/mutex.h> +#include <linux/uaccess.h> +#include <linux/usb.h> +#include <linux/hid.h> + +#define DRIVER_AUTHOR "Tomoki Sekiyama" +#define DRIVER_DESC "Driver for Meywa-Denki & KAYAC YUREX" + +#define YUREX_VENDOR_ID 0x0c45 +#define YUREX_PRODUCT_ID 0x1010 + +#define CMD_ACK '!' +#define CMD_ANIMATE 'A' +#define CMD_COUNT 'C' +#define CMD_LED 'L' +#define CMD_READ 'R' +#define CMD_SET 'S' +#define CMD_VERSION 'V' +#define CMD_EOF 0x0d +#define CMD_PADDING 0xff + +#define YUREX_BUF_SIZE 8 +#define YUREX_WRITE_TIMEOUT (HZ*2) + +/* table of devices that work with this driver */ +static struct usb_device_id yurex_table[] = { + { USB_DEVICE(YUREX_VENDOR_ID, YUREX_PRODUCT_ID) }, + { } /* Terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, yurex_table); + +#ifdef CONFIG_USB_DYNAMIC_MINORS +#define YUREX_MINOR_BASE 0 +#else +#define YUREX_MINOR_BASE 192 +#endif + +/* Structure to hold all of our device specific stuff */ +struct usb_yurex { + struct usb_device *udev; + struct usb_interface *interface; + __u8 int_in_endpointAddr; + struct urb *urb; /* URB for interrupt in */ + unsigned char *int_buffer; /* buffer for intterupt in */ + struct urb *cntl_urb; /* URB for control msg */ + struct usb_ctrlrequest *cntl_req; /* req for control msg */ + unsigned char *cntl_buffer; /* buffer for control msg */ + + struct kref kref; + struct mutex io_mutex; + unsigned long disconnected:1; + struct fasync_struct *async_queue; + wait_queue_head_t waitq; + + spinlock_t lock; + __s64 bbu; /* BBU from device */ +}; +#define to_yurex_dev(d) container_of(d, struct usb_yurex, kref) + +static struct usb_driver yurex_driver; +static const struct file_operations yurex_fops; + + +static void yurex_control_callback(struct urb *urb) +{ + struct usb_yurex *dev = urb->context; + int status = urb->status; + + if (status) { + dev_err(&urb->dev->dev, "%s - control failed: %d\n", + __func__, status); + wake_up_interruptible(&dev->waitq); + return; + } + /* on success, sender woken up by CMD_ACK int in, or timeout */ +} + +static void yurex_delete(struct kref *kref) +{ + struct usb_yurex *dev = to_yurex_dev(kref); + + dev_dbg(&dev->interface->dev, "%s\n", __func__); + + if (dev->cntl_urb) { + usb_kill_urb(dev->cntl_urb); + kfree(dev->cntl_req); + usb_free_coherent(dev->udev, YUREX_BUF_SIZE, + dev->cntl_buffer, dev->cntl_urb->transfer_dma); + usb_free_urb(dev->cntl_urb); + } + if (dev->urb) { + usb_kill_urb(dev->urb); + usb_free_coherent(dev->udev, YUREX_BUF_SIZE, + dev->int_buffer, dev->urb->transfer_dma); + usb_free_urb(dev->urb); + } + usb_put_intf(dev->interface); + usb_put_dev(dev->udev); + kfree(dev); +} + +/* + * usb class driver info in order to get a minor number from the usb core, + * and to have the device registered with the driver core + */ +static struct usb_class_driver yurex_class = { + .name = "yurex%d", + .fops = &yurex_fops, + .minor_base = YUREX_MINOR_BASE, +}; + +static void yurex_interrupt(struct urb *urb) +{ + struct usb_yurex *dev = urb->context; + unsigned char *buf = dev->int_buffer; + int status = urb->status; + unsigned long flags; + int retval, i; + + switch (status) { + case 0: /*success*/ + break; + /* The device is terminated or messed up, give up */ + case -EOVERFLOW: + dev_err(&dev->interface->dev, + "%s - overflow with length %d, actual length is %d\n", + __func__, YUREX_BUF_SIZE, dev->urb->actual_length); + return; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + case -EILSEQ: + case -EPROTO: + case -ETIME: + return; + default: + dev_err(&dev->interface->dev, + "%s - unknown status received: %d\n", __func__, status); + return; + } + + /* handle received message */ + switch (buf[0]) { + case CMD_COUNT: + case CMD_READ: + if (buf[6] == CMD_EOF) { + spin_lock_irqsave(&dev->lock, flags); + dev->bbu = 0; + for (i = 1; i < 6; i++) { + dev->bbu += buf[i]; + if (i != 5) + dev->bbu <<= 8; + } + dev_dbg(&dev->interface->dev, "%s count: %lld\n", + __func__, dev->bbu); + spin_unlock_irqrestore(&dev->lock, flags); + + kill_fasync(&dev->async_queue, SIGIO, POLL_IN); + } + else + dev_dbg(&dev->interface->dev, + "data format error - no EOF\n"); + break; + case CMD_ACK: + dev_dbg(&dev->interface->dev, "%s ack: %c\n", + __func__, buf[1]); + wake_up_interruptible(&dev->waitq); + break; + } + + retval = usb_submit_urb(dev->urb, GFP_ATOMIC); + if (retval) { + dev_err(&dev->interface->dev, "%s - usb_submit_urb failed: %d\n", + __func__, retval); + } +} + +static int yurex_probe(struct usb_interface *interface, const struct usb_device_id *id) +{ + struct usb_yurex *dev; + struct usb_host_interface *iface_desc; + struct usb_endpoint_descriptor *endpoint; + int retval = -ENOMEM; + DEFINE_WAIT(wait); + int res; + + /* allocate memory for our device state and initialize it */ + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (!dev) + goto error; + kref_init(&dev->kref); + mutex_init(&dev->io_mutex); + spin_lock_init(&dev->lock); + init_waitqueue_head(&dev->waitq); + + dev->udev = usb_get_dev(interface_to_usbdev(interface)); + dev->interface = usb_get_intf(interface); + + /* set up the endpoint information */ + iface_desc = interface->cur_altsetting; + res = usb_find_int_in_endpoint(iface_desc, &endpoint); + if (res) { + dev_err(&interface->dev, "Could not find endpoints\n"); + retval = res; + goto error; + } + + dev->int_in_endpointAddr = endpoint->bEndpointAddress; + + /* allocate control URB */ + dev->cntl_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!dev->cntl_urb) + goto error; + + /* allocate buffer for control req */ + dev->cntl_req = kmalloc(YUREX_BUF_SIZE, GFP_KERNEL); + if (!dev->cntl_req) + goto error; + + /* allocate buffer for control msg */ + dev->cntl_buffer = usb_alloc_coherent(dev->udev, YUREX_BUF_SIZE, + GFP_KERNEL, + &dev->cntl_urb->transfer_dma); + if (!dev->cntl_buffer) { + dev_err(&interface->dev, "Could not allocate cntl_buffer\n"); + goto error; + } + + /* configure control URB */ + dev->cntl_req->bRequestType = USB_DIR_OUT | USB_TYPE_CLASS | + USB_RECIP_INTERFACE; + dev->cntl_req->bRequest = HID_REQ_SET_REPORT; + dev->cntl_req->wValue = cpu_to_le16((HID_OUTPUT_REPORT + 1) << 8); + dev->cntl_req->wIndex = cpu_to_le16(iface_desc->desc.bInterfaceNumber); + dev->cntl_req->wLength = cpu_to_le16(YUREX_BUF_SIZE); + + usb_fill_control_urb(dev->cntl_urb, dev->udev, + usb_sndctrlpipe(dev->udev, 0), + (void *)dev->cntl_req, dev->cntl_buffer, + YUREX_BUF_SIZE, yurex_control_callback, dev); + dev->cntl_urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + + + /* allocate interrupt URB */ + dev->urb = usb_alloc_urb(0, GFP_KERNEL); + if (!dev->urb) + goto error; + + /* allocate buffer for interrupt in */ + dev->int_buffer = usb_alloc_coherent(dev->udev, YUREX_BUF_SIZE, + GFP_KERNEL, &dev->urb->transfer_dma); + if (!dev->int_buffer) { + dev_err(&interface->dev, "Could not allocate int_buffer\n"); + goto error; + } + + /* configure interrupt URB */ + usb_fill_int_urb(dev->urb, dev->udev, + usb_rcvintpipe(dev->udev, dev->int_in_endpointAddr), + dev->int_buffer, YUREX_BUF_SIZE, yurex_interrupt, + dev, 1); + dev->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + if (usb_submit_urb(dev->urb, GFP_KERNEL)) { + retval = -EIO; + dev_err(&interface->dev, "Could not submitting URB\n"); + goto error; + } + + /* save our data pointer in this interface device */ + usb_set_intfdata(interface, dev); + dev->bbu = -1; + + /* we can register the device now, as it is ready */ + retval = usb_register_dev(interface, &yurex_class); + if (retval) { + dev_err(&interface->dev, + "Not able to get a minor for this device.\n"); + usb_set_intfdata(interface, NULL); + goto error; + } + + dev_info(&interface->dev, + "USB YUREX device now attached to Yurex #%d\n", + interface->minor); + + return 0; + +error: + if (dev) + /* this frees allocated memory */ + kref_put(&dev->kref, yurex_delete); + return retval; +} + +static void yurex_disconnect(struct usb_interface *interface) +{ + struct usb_yurex *dev; + int minor = interface->minor; + + dev = usb_get_intfdata(interface); + usb_set_intfdata(interface, NULL); + + /* give back our minor */ + usb_deregister_dev(interface, &yurex_class); + + /* prevent more I/O from starting */ + usb_poison_urb(dev->urb); + usb_poison_urb(dev->cntl_urb); + mutex_lock(&dev->io_mutex); + dev->disconnected = 1; + mutex_unlock(&dev->io_mutex); + + /* wakeup waiters */ + kill_fasync(&dev->async_queue, SIGIO, POLL_IN); + wake_up_interruptible(&dev->waitq); + + /* decrement our usage count */ + kref_put(&dev->kref, yurex_delete); + + dev_info(&interface->dev, "USB YUREX #%d now disconnected\n", minor); +} + +static struct usb_driver yurex_driver = { + .name = "yurex", + .probe = yurex_probe, + .disconnect = yurex_disconnect, + .id_table = yurex_table, +}; + + +static int yurex_fasync(int fd, struct file *file, int on) +{ + struct usb_yurex *dev; + + dev = file->private_data; + return fasync_helper(fd, file, on, &dev->async_queue); +} + +static int yurex_open(struct inode *inode, struct file *file) +{ + struct usb_yurex *dev; + struct usb_interface *interface; + int subminor; + int retval = 0; + + subminor = iminor(inode); + + interface = usb_find_interface(&yurex_driver, subminor); + if (!interface) { + printk(KERN_ERR "%s - error, can't find device for minor %d", + __func__, subminor); + retval = -ENODEV; + goto exit; + } + + dev = usb_get_intfdata(interface); + if (!dev) { + retval = -ENODEV; + goto exit; + } + + /* increment our usage count for the device */ + kref_get(&dev->kref); + + /* save our object in the file's private structure */ + mutex_lock(&dev->io_mutex); + file->private_data = dev; + mutex_unlock(&dev->io_mutex); + +exit: + return retval; +} + +static int yurex_release(struct inode *inode, struct file *file) +{ + struct usb_yurex *dev; + + dev = file->private_data; + if (dev == NULL) + return -ENODEV; + + /* decrement the count on our device */ + kref_put(&dev->kref, yurex_delete); + return 0; +} + +static ssize_t yurex_read(struct file *file, char __user *buffer, size_t count, + loff_t *ppos) +{ + struct usb_yurex *dev; + int len = 0; + char in_buffer[20]; + unsigned long flags; + + dev = file->private_data; + + mutex_lock(&dev->io_mutex); + if (dev->disconnected) { /* already disconnected */ + mutex_unlock(&dev->io_mutex); + return -ENODEV; + } + + spin_lock_irqsave(&dev->lock, flags); + len = snprintf(in_buffer, 20, "%lld\n", dev->bbu); + spin_unlock_irqrestore(&dev->lock, flags); + mutex_unlock(&dev->io_mutex); + + if (WARN_ON_ONCE(len >= sizeof(in_buffer))) + return -EIO; + + return simple_read_from_buffer(buffer, count, ppos, in_buffer, len); +} + +static ssize_t yurex_write(struct file *file, const char __user *user_buffer, + size_t count, loff_t *ppos) +{ + struct usb_yurex *dev; + int i, set = 0, retval = 0; + char buffer[16 + 1]; + char *data = buffer; + unsigned long long c, c2 = 0; + signed long timeout = 0; + DEFINE_WAIT(wait); + + count = min(sizeof(buffer) - 1, count); + dev = file->private_data; + + /* verify that we actually have some data to write */ + if (count == 0) + goto error; + + mutex_lock(&dev->io_mutex); + if (dev->disconnected) { /* already disconnected */ + mutex_unlock(&dev->io_mutex); + retval = -ENODEV; + goto error; + } + + if (copy_from_user(buffer, user_buffer, count)) { + mutex_unlock(&dev->io_mutex); + retval = -EFAULT; + goto error; + } + buffer[count] = 0; + memset(dev->cntl_buffer, CMD_PADDING, YUREX_BUF_SIZE); + + switch (buffer[0]) { + case CMD_ANIMATE: + case CMD_LED: + dev->cntl_buffer[0] = buffer[0]; + dev->cntl_buffer[1] = buffer[1]; + dev->cntl_buffer[2] = CMD_EOF; + break; + case CMD_READ: + case CMD_VERSION: + dev->cntl_buffer[0] = buffer[0]; + dev->cntl_buffer[1] = 0x00; + dev->cntl_buffer[2] = CMD_EOF; + break; + case CMD_SET: + data++; + fallthrough; + case '0' ... '9': + set = 1; + c = c2 = simple_strtoull(data, NULL, 0); + dev->cntl_buffer[0] = CMD_SET; + for (i = 1; i < 6; i++) { + dev->cntl_buffer[i] = (c>>32) & 0xff; + c <<= 8; + } + buffer[6] = CMD_EOF; + break; + default: + mutex_unlock(&dev->io_mutex); + return -EINVAL; + } + + /* send the data as the control msg */ + prepare_to_wait(&dev->waitq, &wait, TASK_INTERRUPTIBLE); + dev_dbg(&dev->interface->dev, "%s - submit %c\n", __func__, + dev->cntl_buffer[0]); + retval = usb_submit_urb(dev->cntl_urb, GFP_ATOMIC); + if (retval >= 0) + timeout = schedule_timeout(YUREX_WRITE_TIMEOUT); + finish_wait(&dev->waitq, &wait); + + /* make sure URB is idle after timeout or (spurious) CMD_ACK */ + usb_kill_urb(dev->cntl_urb); + + mutex_unlock(&dev->io_mutex); + + if (retval < 0) { + dev_err(&dev->interface->dev, + "%s - failed to send bulk msg, error %d\n", + __func__, retval); + goto error; + } + if (set && timeout) + dev->bbu = c2; + return timeout ? count : -EIO; + +error: + return retval; +} + +static const struct file_operations yurex_fops = { + .owner = THIS_MODULE, + .read = yurex_read, + .write = yurex_write, + .open = yurex_open, + .release = yurex_release, + .fasync = yurex_fasync, + .llseek = default_llseek, +}; + +module_usb_driver(yurex_driver); + +MODULE_LICENSE("GPL"); |